cec9e630c9307e107915cd1b02f715cb51e2fb91
[odoo/odoo.git] / bin / report / report_sxw.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 from interface import  report_rml
24 import StringIO
25 import base64
26 import copy
27 import ir
28 import locale
29 import mx.DateTime
30 import netsvc
31 import os
32 import osv
33 import pooler
34 import re
35 import time
36 import tools
37 import warnings
38 import xml.dom.minidom
39 import zipfile
40
41 DT_FORMAT = '%Y-%m-%d'
42 DHM_FORMAT = '%Y-%m-%d %H:%M:%S'
43 HM_FORMAT = '%H:%M:%S'
44
45 if not hasattr(locale, 'nl_langinfo'):
46     locale.nl_langinfo = lambda *a: '%x'
47
48 if not hasattr(locale, 'D_FMT'):
49     locale.D_FMT = None
50
51 rml_parents = {
52     'tr':1,
53     'li':1,
54     'story': 0,
55     'section': 0
56 }
57
58 rml_tag="para"
59
60 sxw_parents = {
61     'table-row': 1,
62     'list-item': 1,
63     'body': 0,
64     'section': 0,
65 }
66
67 sxw_tag = "p"
68
69 rml2sxw = {
70     'para': 'p',
71 }
72
73 _LOCALE2WIN32 = {
74     'af_ZA': 'Afrikaans_South Africa',
75     'sq_AL': 'Albanian_Albania',
76     'ar_SA': 'Arabic_Saudi Arabia',
77     'eu_ES': 'Basque_Spain',
78     'be_BY': 'Belarusian_Belarus',
79     'bs_BA': 'Serbian (Latin)',
80     'bg_BG': 'Bulgarian_Bulgaria',
81     'ca_ES': 'Catalan_Spain',
82     'hr_HR': 'Croatian_Croatia',
83     'zh_CN': 'Chinese_China',
84     'zh_TW': 'Chinese_Taiwan',
85     'cs_CZ': 'Czech_Czech Republic',
86     'da_DK': 'Danish_Denmark',
87     'nl_NL': 'Dutch_Netherlands',
88     'et_EE': 'Estonian_Estonia',
89     'fa_IR': 'Farsi_Iran',
90     'ph_PH': 'Filipino_Philippines',
91     'fi_FI': 'Finnish_Finland',
92     'fr_FR': 'French_France',
93     'fr_BE': 'French_France',
94     'fr_CH': 'French_France',
95     'fr_CA': 'French_France',
96     'ga': 'Scottish Gaelic',
97     'gl_ES': 'Galician_Spain',
98     'ka_GE': 'Georgian_Georgia',
99     'de_DE': 'German_Germany',
100     'el_GR': 'Greek_Greece',
101     'gu': 'Gujarati_India',
102     'he_IL': 'Hebrew_Israel',
103     'hi_IN': 'Hindi',
104     'hu': 'Hungarian_Hungary',
105     'is_IS': 'Icelandic_Iceland',
106     'id_ID': 'Indonesian_indonesia',
107     'it_IT': 'Italian_Italy',
108     'ja_JP': 'Japanese_Japan',
109     'kn_IN': 'Kannada',
110     'km_KH': 'Khmer',
111     'ko_KR': 'Korean_Korea',
112     'lo_LA': 'Lao_Laos',
113     'lt_LT': 'Lithuanian_Lithuania',
114     'lat': 'Latvian_Latvia',
115     'ml_IN': 'Malayalam_India',
116     'id_ID': 'Indonesian_indonesia',
117     'mi_NZ': 'Maori',
118     'mn': 'Cyrillic_Mongolian',
119     'no_NO': 'Norwegian_Norway',
120     'nn_NO': 'Norwegian-Nynorsk_Norway',
121     'pl': 'Polish_Poland',
122     'pt_PT': 'Portuguese_Portugal',
123     'pt_BR': 'Portuguese_Brazil',
124     'ro_RO': 'Romanian_Romania',
125     'ru_RU': 'Russian_Russia',
126     'mi_NZ': 'Maori',
127     'sr_CS': 'Serbian (Cyrillic)_Serbia and Montenegro',
128     'sk_SK': 'Slovak_Slovakia',
129     'sl_SI': 'Slovenian_Slovenia',
130     'es_ES': 'Spanish_Spain',
131     'sv_SE': 'Swedish_Sweden',
132     'ta_IN': 'English_Australia',
133     'th_TH': 'Thai_Thailand',
134     'mi_NZ': 'Maori',
135     'tr_TR': 'Turkish_Turkey',
136     'uk_UA': 'Ukrainian_Ukraine',
137     'vi_VN': 'Vietnamese_Viet Nam',
138 }
139
140 class _format(object):
141     def set_value(self, cr, uid, name, object, field):
142         #super(_date_format, self).__init__(self)
143         pool_lang = pooler.get_pool(cr.dbname).get('res.lang')
144         self.object = object
145         self._field = field
146         self.name = name
147 #        lc, encoding = locale.getdefaultlocale()
148 #        if not encoding:
149 #            encoding = 'UTF-8'
150 #        if encoding == 'utf':
151 #            encoding = 'UTF-8'
152 #        if encoding == 'cp1252':
153 #            encoding= '1252'
154         lang = self.object._context.get('lang', 'en_US') or 'en_US'
155         self.lang_obj = pool_lang.browse(cr, uid,pool_lang.search(cr, uid,[('code','=',lang)])[0])
156
157 #        try:
158 #            if os.name == 'nt':
159 #                locale.setlocale(locale.LC_ALL, _LOCALE2WIN32.get(lang, lang) + '.' + encoding)
160 #            else:
161 #                locale.setlocale(locale.LC_ALL,str( lang + '.' + encoding))
162 #        except Exception:
163 #            netsvc.Logger().notifyChannel('report', netsvc.LOG_WARNING,
164 #                    'report %s: unable to set locale "%s"' % (self.name,
165 #                        self.object._context.get('lang', 'en_US') or 'en_US'))
166
167
168 class _float_format(float, _format):
169     def __str__(self):
170         digits = 2
171         if self._field and hasattr(self._field, 'digits') and self._field.digits:
172             digits = self._field.digits[1]
173         return self.lang_obj.format('%.' + str(digits) + 'f', self.name, True)
174         
175 #        if not self.object._context:
176 #            return locale.format('%f', self.name, True)
177 #        digit = 2
178 #        if hasattr(self._field, 'digits') and self._field.digits:
179 #            digit = self._field.digits[1]
180 #        return locale.format('%.' + str(digit) + 'f', self.name, True)
181
182
183 class _int_format(int, _format):
184     def __str__(self):
185         return self.lang_obj.format('%.d', self.name, True)
186 #        return locale.format('%d', self.name, True)
187
188
189 class _date_format(str, _format):
190     def __str__(self):
191         if self.name:
192             date = mx.DateTime.strptime(self.name,DT_FORMAT)
193             return date.strftime(self.lang_obj.date_format)
194         return ''    
195 #        if not self.object._context:
196 #            return self.name
197 #
198 #        if self.name:
199 #            try :
200 #                datedata = time.strptime(self.name, DT_FORMAT)
201 #                return time.strftime(locale.nl_langinfo(locale.D_FMT).replace('%y', '%Y'),
202 #                    datedata)
203 #            except :
204 #                pass
205 #        return ''
206
207 class _dttime_format(str, _format):
208     def __str__(self):
209         if self.name:
210             datetime = mx.DateTime.strptime(self.name,DHM_FORMAT)
211             return datetime.strftime(self.lang_obj.date_format+ " " + self.lang_obj.time_format)
212         return '' 
213
214
215 _fields_process = {
216     'float': _float_format,
217     'date': _date_format,
218     'integer': _int_format,
219     'datetime' : _dttime_format
220 }
221
222 #
223 # Context: {'node': node.dom}
224 #
225 class browse_record_list(list):
226     def __init__(self, lst, context):
227         super(browse_record_list, self).__init__(lst)
228         self.context = context
229
230     def __getattr__(self, name):
231         res = browse_record_list([getattr(x,name) for x in self], self.context)
232         return res
233
234     def __str__(self):
235         return "browse_record_list("+str(len(self))+")"
236
237     def repeatIn(self, name):
238         warnings.warn('Use repeatIn(object_list, \'variable\')', DeprecationWarning)
239         node = self.context['_node']
240         parents = self.context.get('parents', rml_parents)
241         node.data = ''
242         while True:
243             if not node.parentNode:
244                 break
245             node = node.parentNode
246             if node.nodeType == node.ELEMENT_NODE and node.localName in parents:
247                 break
248         parent_node = node
249         if not len(self):
250             return None
251         nodes = [(0,node)]
252         for i in range(1,len(self)):
253             newnode = parent_node.cloneNode(1)
254             n = parent_node.parentNode
255             n.insertBefore(newnode, parent_node)
256             nodes.append((i,newnode))
257         for i,node in nodes:
258             self.context[name] = self[i]
259             self.context['_self']._parse_node(node)
260         return None
261
262 class rml_parse(object):
263     def __init__(self, cr, uid, name, parents=rml_parents, tag=rml_tag, context=None):
264         if not context:
265             context={}
266         self.cr = cr
267         self.uid = uid
268         self.pool = pooler.get_pool(cr.dbname)
269         user = self.pool.get('res.users').browse(cr, uid, uid, fields_process=_fields_process)
270         self.localcontext = {
271             'user': user,
272             'company': user.company_id,
273             'repeatIn': self.repeatIn,
274             'setLang': self.setLang,
275             'setTag': self.setTag,
276             'removeParentNode': self.removeParentNode,
277             'format': self.format,
278             'formatLang': self.formatLang,
279             'logo' : user.company_id.logo,
280             'lang' : user.company_id.partner_id.lang,
281         }
282         self.localcontext.update(context)
283         self.rml_header = user.company_id.rml_header
284         self.rml_header2 = user.company_id.rml_header2
285         self.logo = user.company_id.logo
286         self.name = name
287         self._regex = re.compile('\[\[(.+?)\]\]')
288         self._transl_regex = re.compile('(\[\[.+?\]\])')
289         self._node = None
290         self.parents = parents
291         self.tag = tag
292         self._lang_cache = {}
293 #       self.already = {}
294
295     def setTag(self, oldtag, newtag, attrs=None):
296         if not attrs:
297             attrs={}
298         node = self._find_parent(self._node, [oldtag])
299         if node:
300             node.tagName = newtag
301             for key, val in attrs.items():
302                 node.setAttribute(key, val)
303         return None
304
305     def format(self, text, oldtag=None):
306         if not oldtag:
307             oldtag = self.tag
308         self._node.data = ''
309         node = self._find_parent(self._node, [oldtag])
310         ns = None
311         if node:
312             pp = node.parentNode
313             ns = node.nextSibling
314             pp.removeChild(node)
315             self._node = pp
316             
317         lst = tools.ustr(text).split('\n')
318         if not (text and lst):
319             return None
320         nodes = []
321         for i in range(len(lst)):
322             newnode = node.cloneNode(1)
323             newnode.tagName=rml_tag
324             newnode.childNodes[0].data = lst[i]
325             if ns:
326                 pp.insertBefore(newnode, ns)
327             else:
328                 pp.appendChild(newnode)
329             nodes.append((i, newnode))
330
331     def removeParentNode(self, tag=None):
332         if not tag:
333             tag = self.tag
334         if self.tag == sxw_tag and rml2sxw.get(tag, False):
335             tag = rml2sxw[tag]
336         node = self._find_parent(self._node, [tag])
337         if node:
338             parentNode = node.parentNode
339             parentNode.removeChild(node)
340             self._node = parentNode
341
342     def setLang(self, lang):
343         self.localcontext['lang'] = lang
344         for obj in self.objects:
345             obj._context['lang'] = lang
346             for table in obj._cache:
347                 for id in obj._cache[table]:
348                     self._lang_cache.setdefault(obj._context['lang'], {}).setdefault(table,
349                             {}).update(obj._cache[table][id])
350                     if lang in self._lang_cache \
351                             and table in self._lang_cache[lang] \
352                             and id in self._lang_cache[lang][table]:
353                         obj._cache[table][id] = self._lang_cache[lang][table][id]
354                     else:
355                         obj._cache[table][id] = {'id': id}
356
357
358     def formatLang(self, value, digits=2, date=False,date_time=False, grouping=True, monetary=False, currency=None):
359         if isinstance(value, (str, unicode)) and not value:
360             return ''
361         pool_lang = self.pool.get('res.lang')
362         lang = self.localcontext.get('lang', 'en_US') or 'en_US'
363         lang_obj = pool_lang.browse(self.cr,self.uid,pool_lang.search(self.cr,self.uid,[('code','=',lang)])[0])
364         if date or date_time:
365             date_format = lang_obj.date_format
366             if date_time:
367                 date_format = lang_obj.date_format + " " + lang_obj.time_format
368             parse_format = date_format
369             
370             # filtering time.strftime('%Y-%m-%d')
371             if type(value) == type(''):
372                 parse_format = DHM_FORMAT
373                 if (not date_time): 
374                     return value
375             
376             if not isinstance(value, time.struct_time):
377                 # assume string, parse it
378 #                if len(str(value)) == 10:
379 #                    # length of date like 2001-01-01 is ten
380 #                    # assume format '%Y-%m-%d'
381 #                    date = mx.DateTime.strptime(value,DT_FORMAT)
382 #                else:
383 #                    # assume format '%Y-%m-%d %H:%M:%S'
384 #                    value = str(value)[:19]
385 #                    date = mx.DateTime.strptime(str(value),DHM_FORMAT)
386                 date = mx.DateTime.strptime(str(value),parse_format)
387             else:
388                 date = mx.DateTime.DateTime(*(value.timetuple()[:6]))
389             return date.strftime(date_format)
390         return lang_obj.format('%.' + str(digits) + 'f', value, grouping=grouping, monetary=monetary)
391
392     def repeatIn(self, lst, name, nodes_parent=False):
393         self._node.data = ''
394         node = self._find_parent(self._node, nodes_parent or self.parents)
395
396         pp = node.parentNode
397         ns = node.nextSibling
398         pp.removeChild(node)
399         self._node = pp
400
401         if not len(lst):
402             return None
403         nodes = []
404         for i in range(len(lst)):
405             newnode = node.cloneNode(1)
406             if ns:
407                 pp.insertBefore(newnode, ns)
408             else:
409                 pp.appendChild(newnode)
410             nodes.append((i, newnode))
411         for i, node in nodes:
412             self.node_context[node] = {name: lst[i]}
413         return None
414
415     def _eval(self, expr):
416         try:
417             res = eval(expr, self.localcontext)
418             if (res is None) or (res=='') or (res is False):
419                 res = ''
420         except Exception,e:
421             import traceback, sys
422             tb_s = reduce(lambda x, y: x+y, traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback))
423             netsvc.Logger().notifyChannel('report', netsvc.LOG_ERROR,
424                     'report %s:\n%s\n%s\nexpr: %s' % (self.name, tb_s, str(e),
425                         expr.encode('utf-8')))
426             res = ''
427         return res
428
429     def _find_parent(self, node, parents):
430         while True:
431             if not node.parentNode:
432                 return False
433             node = node.parentNode
434             if node.nodeType == node.ELEMENT_NODE and node.localName in parents:
435                 break
436         return node
437
438     def _parse_text(self, text, level=None):
439         if not level:
440             level=[]
441         res = self._regex.findall(text)
442         todo = []
443         # translate the text
444         # the "split [[]] if not match [[]]" is not very nice, but I
445         # don't see how I could do it better...
446         # what I'd like to do is a re.sub(NOT pattern, func, string)
447         # but I don't know how to do that...
448         # translate the RML file
449         if 'lang' in self.localcontext:
450             lang = self.localcontext['lang']
451             if lang and text and not text.isspace():
452                 transl_obj = self.pool.get('ir.translation')
453                 piece_list = self._transl_regex.split(text)
454                 for pn in range(len(piece_list)):
455                     if not self._transl_regex.match(piece_list[pn]):
456                         source_string = piece_list[pn].replace('\n', ' ').strip()
457                         if len(source_string):
458                             translated_string = transl_obj._get_source(self.cr, self.uid, self.name, 'rml', lang, source_string)
459                             if translated_string:
460                                 piece_list[pn] = piece_list[pn].replace(source_string, translated_string)
461                 text = ''.join(piece_list)
462         for key in res:
463             newtext = self._eval(key)
464             for i in range(len(level)):
465                 if isinstance(newtext, list):
466                     newtext = newtext[level[i]]
467             if isinstance(newtext, list):
468                 todo.append((key, newtext))
469             else:
470                 # if there are two [[]] blocks the same, it will replace both
471                 # but it's ok because it should evaluate to the same thing
472                 # anyway
473                 newtext = tools.ustr(newtext)
474                 text = text.replace('[['+key+']]', newtext)
475         self._node.data = text
476         if len(todo):
477             for key, newtext in todo:
478                 parent_node = self._find_parent(self._node, parents)
479                 assert parents.get(parent_node.localName, False), 'No parent node found !'
480                 nodes = [parent_node]
481                 for i in range(len(newtext) - 1):
482                     newnode = parent_node.cloneNode(1)
483                     if parents.get(parent_node.localName, False):
484                         n = parent_node.parentNode
485                         parent_node.parentNode.insertAfter(newnode, parent_node)
486                         nodes.append(newnode)
487             return False
488         return text
489
490     def _parse_node(self):
491         level = []
492         while True:
493             if self._node.nodeType==self._node.ELEMENT_NODE:
494                 if self._node.hasAttribute('expr'):
495                     newattrs = self._eval(self._node.getAttribute('expr'))
496                     for key,val in newattrs.items():
497                         self._node.setAttribute(key,val)
498
499             if self._node.hasChildNodes():
500                 self._node = self._node.firstChild
501             elif self._node.nextSibling:
502                 self._node = self._node.nextSibling
503             else:
504                 while self._node and not self._node.nextSibling:
505                     self._node = self._node.parentNode
506                 if not self._node:
507                     break
508                 self._node = self._node.nextSibling
509             if self._node in self.node_context:
510                 self.localcontext.update(self.node_context[self._node])
511             if self._node.nodeType in (self._node.CDATA_SECTION_NODE, self._node.TEXT_NODE):
512 #               if self._node in self.already:
513 #                   self.already[self._node] += 1
514 #                   print "second pass!", self.already[self._node], '---%s---' % self._node.data
515 #               else:
516 #                   self.already[self._node] = 0
517                 self._parse_text(self._node.data, level)
518         return True
519
520     def _find_node(self, node, localname):
521         if node.localName==localname:
522             return node
523         for tag in node.childNodes:
524             if tag.nodeType==tag.ELEMENT_NODE:
525                 found = self._find_node(tag, localname)
526                 if found:
527                     return found
528         return False
529
530     def _add_header(self, node, header=1):
531         if header==2:
532             rml_head =  self.rml_header2
533         else:
534             rml_head =  self.rml_header
535
536         # Refactor this patch, to use the minidom interface
537         if self.logo and (rml_head.find('company.logo')<0 or rml_head.find('<image')<0) and rml_head.find('<!--image')<0:
538             rml_head =  rml_head.replace('<pageGraphics>','''<pageGraphics> <image x="10" y="26cm" height="70" width="90" >[[company.logo]] </image> ''')
539         if not self.logo and rml_head.find('company.logo')>=0:
540             rml_head = rml_head.replace('<image','<!--image')
541             rml_head = rml_head.replace('</image>','</image-->')
542
543         head_dom = xml.dom.minidom.parseString(rml_head)
544         #for frame in head_dom.getElementsByTagName('frame'):
545         #   frame.parentNode.removeChild(frame)
546         node2 = head_dom.documentElement
547         for tag in node2.childNodes:
548             if tag.nodeType==tag.ELEMENT_NODE:
549                 found = self._find_node(node, tag.localName)
550         #       rml_frames = found.getElementsByTagName('frame')
551                 if found:
552                     if tag.hasAttribute('position') and (tag.getAttribute('position')=='inside'):
553                         found.appendChild(tag)
554                     else:
555                         found.parentNode.replaceChild(tag, found)
556         #       for frame in rml_frames:
557         #           tag.appendChild(frame)
558         return True
559
560     def preprocess(self, objects, data, ids):
561         self.localcontext['data'] = data
562         self.localcontext['objects'] = objects
563         self.datas = data
564         self.ids = ids
565         self.objects = objects
566
567     def _parse(self, rml_dom, objects, data, header=0):
568         self.node_context = {}
569         self.dom = rml_dom
570         self._node = self.dom.documentElement
571         if header:
572             self._add_header(self._node, header)
573         self._parse_node()
574         res = self.dom.documentElement.toxml('utf-8')
575         return res
576
577 class report_sxw(report_rml):
578     def __init__(self, name, table, rml, parser=rml_parse, header=True, store=False):
579         report_rml.__init__(self, name, table, rml, '')
580         self.name = name
581         self.parser = parser
582         self.header = header
583         self.store = store
584
585     def getObjects(self, cr, uid, ids, context):
586         table_obj = pooler.get_pool(cr.dbname).get(self.table)
587         return table_obj.browse(cr, uid, ids, list_class=browse_record_list, context=context,
588             fields_process=_fields_process)
589
590     def create(self, cr, uid, ids, data, context=None):
591         if not context:
592             context={}
593         pool = pooler.get_pool(cr.dbname)
594         ir_obj = pool.get('ir.actions.report.xml')
595         report_xml_ids = ir_obj.search(cr, uid,
596                 [('report_name', '=', self.name[7:])], context=context)
597
598         if report_xml_ids:
599             report_xml = ir_obj.browse(cr, uid, report_xml_ids[0],
600                     context=context)
601             attach = report_xml.attachment
602         else:
603             ir_menu_report_obj = pool.get('ir.ui.menu')
604             report_menu_ids = ir_menu_report_obj.search(cr, uid,
605                     [('id', 'in', ids)], context=context)
606             title = ''
607             if report_menu_ids:
608                 report_name = ir_menu_report_obj.browse(cr, uid, report_menu_ids[0],
609                     context=context)
610                 title = report_name.name
611             rml = tools.file_open(self.tmpl, subdir=None).read()
612             report_type= data.get('report_type', 'pdf')
613             class a(object):
614                 def __init__(self, *args, **argv):
615                     for key,arg in argv.items():
616                         setattr(self, key, arg)
617             report_xml = a(title=title, report_type=report_type, report_rml_content=rml, name=title, attachment=False, header=self.header)
618             attach = False
619
620         if attach:
621             objs = self.getObjects(cr, uid, ids, context)
622             results = []
623             for obj in objs:
624                 aname = eval(attach, {'object':obj, 'time':time})
625                 result = False
626                 if report_xml.attachment_use and aname and context.get('attachment_use', True):
627                     aids = pool.get('ir.attachment').search(cr, uid, [('datas_fname','=',aname+'.pdf'),('res_model','=',self.table),('res_id','=',obj.id)])
628                     if aids:
629                         d = base64.decodestring(pool.get('ir.attachment').browse(cr, uid, aids[0]).datas)
630                         results.append((d,'pdf'))
631                         continue
632
633                 result = self.create_single(cr, uid, [obj.id], data, report_xml, context)
634                 if aname:
635                     name = aname+'.'+result[1]
636                     pool.get('ir.attachment').create(cr, uid, {
637                         'name': aname,
638                         'datas': base64.encodestring(result[0]),
639                         'datas_fname': name,
640                         'res_model': self.table,
641                         'res_id': obj.id,
642                         }, context=context
643                     )
644                     cr.commit()
645                 results.append(result)
646
647             if results[0][1]=='pdf':
648                 from pyPdf import PdfFileWriter, PdfFileReader
649                 import cStringIO
650                 output = PdfFileWriter()
651                 for r in results:
652                     reader = PdfFileReader(cStringIO.StringIO(r[0]))
653                     for page in range(reader.getNumPages()):
654                         output.addPage(reader.getPage(page))
655                 s = cStringIO.StringIO()
656                 output.write(s)
657                 return s.getvalue(), results[0][1]
658         return self.create_single(cr, uid, ids, data, report_xml, context)
659
660     def create_single(self, cr, uid, ids, data, report_xml, context={}):
661         logo = None
662         context = context.copy()
663         pool = pooler.get_pool(cr.dbname)
664         want_header = self.header
665         title = report_xml.name
666         attach = report_xml.attachment
667         report_type = report_xml.report_type
668         want_header = report_xml.header
669
670         if report_type in ['sxw','odt']:
671             context['parents'] = sxw_parents
672             sxw_io = StringIO.StringIO(report_xml.report_sxw_content)
673             sxw_z = zipfile.ZipFile(sxw_io, mode='r')
674             rml = sxw_z.read('content.xml')
675             meta = sxw_z.read('meta.xml')
676             sxw_z.close()
677             rml_parser = self.parser(cr, uid, self.name2, context)
678             rml_parser.parents = sxw_parents
679             rml_parser.tag = sxw_tag
680             objs = self.getObjects(cr, uid, ids, context)
681             rml_parser.preprocess(objs, data, ids)
682             rml_dom = xml.dom.minidom.parseString(rml)
683
684             node = rml_dom.documentElement
685
686             elements = node.getElementsByTagName("text:p")
687
688             for pe in elements:
689                 e = pe.getElementsByTagName("text:drop-down")
690                 for de in e:
691                     pp=de.parentNode
692                     for cnd in de.childNodes:
693                         if cnd.nodeType in (cnd.CDATA_SECTION_NODE, cnd.TEXT_NODE):
694                             pe.appendChild(cnd)
695                             pp.removeChild(de)
696
697             # Add Information : Resource ID and Model
698             rml_dom_meta = xml.dom.minidom.parseString(meta)
699             node = rml_dom_meta.documentElement
700             elements = node.getElementsByTagName("meta:user-defined")
701             for pe in elements:
702                 if pe.hasAttribute("meta:name"):
703                     if pe.getAttribute("meta:name") == "Info 3":
704                         pe.childNodes[0].data=data['id']
705                     if pe.getAttribute("meta:name") == "Info 4":
706                         pe.childNodes[0].data=data['model']
707             meta = rml_dom_meta.documentElement.toxml('utf-8')
708
709             rml2 = rml_parser._parse(rml_dom, objs, data, header=want_header)
710             sxw_z = zipfile.ZipFile(sxw_io, mode='a')
711             sxw_z.writestr('content.xml', "<?xml version='1.0' encoding='UTF-8'?>" + \
712                     rml2)
713             sxw_z.writestr('meta.xml', "<?xml version='1.0' encoding='UTF-8'?>" + \
714                     meta)
715
716             if want_header:
717                 #Add corporate header/footer
718                 if report_type=='odt':
719                     rml = tools.file_open('custom/corporate_odt_header.xml').read()
720                 if report_type=='sxw':
721                     rml = tools.file_open('custom/corporate_sxw_header.xml').read()
722                 rml_parser = self.parser(cr, uid, self.name2, context)
723                 rml_parser.parents = sxw_parents
724                 rml_parser.tag = sxw_tag
725                 objs = self.getObjects(cr, uid, ids, context)
726                 rml_parser.preprocess(objs, data, ids)
727                 rml_dom = xml.dom.minidom.parseString(rml)
728                 rml2 = rml_parser._parse(rml_dom, objs, data, header=want_header)
729                 sxw_z.writestr('styles.xml',"<?xml version='1.0' encoding='UTF-8'?>" + \
730                         rml2)
731             sxw_z.close()
732             rml2 = sxw_io.getvalue()
733             sxw_io.close()
734         else:
735             rml = report_xml.report_rml_content
736             context['parents'] = rml_parents
737             rml_parser = self.parser(cr, uid, self.name2, context)
738             rml_parser.parents = rml_parents
739             rml_parser.tag = rml_tag
740             objs = self.getObjects(cr, uid, ids, context)
741             rml_parser.preprocess(objs, data, ids)
742             rml_dom = xml.dom.minidom.parseString(rml)
743             rml2 = rml_parser._parse(rml_dom, objs, data, header=want_header)
744             if rml_parser.logo:
745                 logo = base64.decodestring(rml_parser.logo)
746
747         create_doc = self.generators[report_type]
748         pdf = create_doc(rml2, logo, title.encode('utf8'))
749
750         return (pdf, report_type)
751