[REF] Refactoring according to the review of CHS
[odoo/odoo.git] / openerp / report / report_sxw.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21 from lxml import etree
22 import StringIO
23 import cStringIO
24 import base64
25 from datetime import datetime
26 import os
27 import re
28 import time
29 from interface import report_rml
30 import preprocess
31 import logging
32 import openerp.tools as tools
33 import zipfile
34 import common
35
36 import openerp
37 from openerp.osv.fields import float as float_field, function as function_field, datetime as datetime_field
38 from openerp.tools.translate import _
39 from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
40
41 _logger = logging.getLogger(__name__)
42
43 rml_parents = {
44     'tr':1,
45     'li':1,
46     'story': 0,
47     'section': 0
48 }
49
50 rml_tag="para"
51
52 sxw_parents = {
53     'table-row': 1,
54     'list-item': 1,
55     'body': 0,
56     'section': 0,
57 }
58
59 html_parents = {
60     'tr' : 1,
61     'body' : 0,
62     'div' : 0
63     }
64 sxw_tag = "p"
65
66 rml2sxw = {
67     'para': 'p',
68 }
69
70 def get_date_length(date_format=DEFAULT_SERVER_DATE_FORMAT):
71     return len((datetime.now()).strftime(date_format))
72
73 class _format(object):
74     def set_value(self, cr, uid, name, object, field, lang_obj):
75         self.object = object
76         self._field = field
77         self.name = name
78         self.lang_obj = lang_obj
79
80 class _float_format(float, _format):
81     def __init__(self,value):
82         super(_float_format, self).__init__()
83         self.val = value or 0.0
84
85     def __str__(self):
86         digits = 2
87         if hasattr(self,'_field') and getattr(self._field, 'digits', None):
88             digits = self._field.digits[1]
89         if hasattr(self, 'lang_obj'):
90             return self.lang_obj.format('%.' + str(digits) + 'f', self.name, True)
91         return str(self.val)
92
93 class _int_format(int, _format):
94     def __init__(self,value):
95         super(_int_format, self).__init__()
96         self.val = value or 0
97
98     def __str__(self):
99         if hasattr(self,'lang_obj'):
100             return self.lang_obj.format('%.d', self.name, True)
101         return str(self.val)
102
103 class _date_format(str, _format):
104     def __init__(self,value):
105         super(_date_format, self).__init__()
106         self.val = value and str(value) or ''
107
108     def __str__(self):
109         if self.val:
110             if getattr(self,'name', None):
111                 date = datetime.strptime(self.name[:get_date_length()], DEFAULT_SERVER_DATE_FORMAT)
112                 return date.strftime(self.lang_obj.date_format.encode('utf-8'))
113         return self.val
114
115 class _dttime_format(str, _format):
116     def __init__(self,value):
117         super(_dttime_format, self).__init__()
118         self.val = value and str(value) or ''
119
120     def __str__(self):
121         if self.val and getattr(self,'name', None):
122             return datetime.strptime(self.name, DEFAULT_SERVER_DATETIME_FORMAT)\
123                    .strftime("%s %s"%((self.lang_obj.date_format).encode('utf-8'),
124                                       (self.lang_obj.time_format).encode('utf-8')))
125         return self.val
126
127
128 _fields_process = {
129     'float': _float_format,
130     'date': _date_format,
131     'integer': _int_format,
132     'datetime' : _dttime_format
133 }
134
135 #
136 # Context: {'node': node.dom}
137 #
138 class browse_record_list(list):
139     def __init__(self, lst, context):
140         super(browse_record_list, self).__init__(lst)
141         self.context = context
142
143     def __getattr__(self, name):
144         res = browse_record_list([getattr(x,name) for x in self], self.context)
145         return res
146
147     def __str__(self):
148         return "browse_record_list("+str(len(self))+")"
149
150 class rml_parse(object):
151     def __init__(self, cr, uid, name, parents=rml_parents, tag=rml_tag, context=None):
152         if not context:
153             context={}
154         self.cr = cr
155         self.uid = uid
156         self.pool = openerp.registry(cr.dbname)
157         user = self.pool['res.users'].browse(cr, uid, uid, context=context)
158         self.localcontext = {
159             'user': user,
160             'setCompany': self.setCompany,
161             'repeatIn': self.repeatIn,
162             'setLang': self.setLang,
163             'setTag': self.setTag,
164             'removeParentNode': self.removeParentNode,
165             'format': self.format,
166             'formatLang': self.formatLang,
167             'lang' : user.company_id.partner_id.lang,
168             'translate' : self._translate,
169             'setHtmlImage' : self.set_html_image,
170             'strip_name' : self._strip_name,
171             'time' : time,
172             'display_address': self.display_address,
173             # more context members are setup in setCompany() below:
174             #  - company_id
175             #  - logo
176         }
177         self.setCompany(user.company_id)
178         self.localcontext.update(context)
179         self.name = name
180         self._node = None
181         self.parents = parents
182         self.tag = tag
183         self._lang_cache = {}
184         self.lang_dict = {}
185         self.default_lang = {}
186         self.lang_dict_called = False
187         self._transl_regex = re.compile('(\[\[.+?\]\])')
188
189     def setTag(self, oldtag, newtag, attrs=None):
190         return newtag, attrs
191
192     def _ellipsis(self, char, size=100, truncation_str='...'):
193         if not char:
194             return ''
195         if len(char) <= size:
196             return char
197         return char[:size-len(truncation_str)] + truncation_str
198
199     def setCompany(self, company_id):
200         if company_id:
201             self.localcontext['company'] = company_id
202             self.localcontext['logo'] = company_id.logo
203             self.rml_header = company_id.rml_header
204             self.rml_header2 = company_id.rml_header2
205             self.rml_header3 = company_id.rml_header3
206             self.logo = company_id.logo
207
208     def _strip_name(self, name, maxlen=50):
209         return self._ellipsis(name, maxlen)
210
211     def format(self, text, oldtag=None):
212         return text.strip()
213
214     def removeParentNode(self, tag=None):
215         raise GeneratorExit('Skip')
216
217     def set_html_image(self,id,model=None,field=None,context=None):
218         if not id :
219             return ''
220         if not model:
221             model = 'ir.attachment'
222         try :
223             id = int(id)
224             res = self.pool[model].read(self.cr,self.uid,id)
225             if field :
226                 return res[field]
227             elif model =='ir.attachment' :
228                 return res['datas']
229             else :
230                 return ''
231         except Exception:
232             return ''
233
234     def setLang(self, lang):
235         self.localcontext['lang'] = lang
236         self.lang_dict_called = False
237         for obj in self.objects:
238             obj._context['lang'] = lang
239
240     def _get_lang_dict(self):
241         pool_lang = self.pool['res.lang']
242         lang = self.localcontext.get('lang', 'en_US') or 'en_US'
243         lang_ids = pool_lang.search(self.cr,self.uid,[('code','=',lang)])[0]
244         lang_obj = pool_lang.browse(self.cr,self.uid,lang_ids)
245         self.lang_dict.update({'lang_obj':lang_obj,'date_format':lang_obj.date_format,'time_format':lang_obj.time_format})
246         self.default_lang[lang] = self.lang_dict.copy()
247         return True
248
249     def digits_fmt(self, obj=None, f=None, dp=None):
250         digits = self.get_digits(obj, f, dp)
251         return "%%.%df" % (digits, )
252
253     def get_digits(self, obj=None, f=None, dp=None):
254         d = DEFAULT_DIGITS = 2
255         if dp:
256             decimal_precision_obj = self.pool['decimal.precision']
257             ids = decimal_precision_obj.search(self.cr, self.uid, [('name', '=', dp)])
258             if ids:
259                 d = decimal_precision_obj.browse(self.cr, self.uid, ids)[0].digits
260         elif obj and f:
261             res_digits = getattr(obj._columns[f], 'digits', lambda x: ((16, DEFAULT_DIGITS)))
262             if isinstance(res_digits, tuple):
263                 d = res_digits[1]
264             else:
265                 d = res_digits(self.cr)[1]
266         elif (hasattr(obj, '_field') and\
267                 isinstance(obj._field, (float_field, function_field)) and\
268                 obj._field.digits):
269                 d = obj._field.digits[1] or DEFAULT_DIGITS
270         return d
271
272     def formatLang(self, value, digits=None, date=False, date_time=False, grouping=True, monetary=False, dp=False, currency_obj=False):
273         """
274             Assuming 'Account' decimal.precision=3:
275                 formatLang(value) -> digits=2 (default)
276                 formatLang(value, digits=4) -> digits=4
277                 formatLang(value, dp='Account') -> digits=3
278                 formatLang(value, digits=5, dp='Account') -> digits=5
279         """
280         if digits is None:
281             if dp:
282                 digits = self.get_digits(dp=dp)
283             else:
284                 digits = self.get_digits(value)
285
286         if isinstance(value, (str, unicode)) and not value:
287             return ''
288
289         if not self.lang_dict_called:
290             self._get_lang_dict()
291             self.lang_dict_called = True
292
293         if date or date_time:
294             if not str(value):
295                 return ''
296
297             date_format = self.lang_dict['date_format']
298             parse_format = DEFAULT_SERVER_DATE_FORMAT
299             if date_time:
300                 value = value.split('.')[0]
301                 date_format = date_format + " " + self.lang_dict['time_format']
302                 parse_format = DEFAULT_SERVER_DATETIME_FORMAT
303             if isinstance(value, basestring):
304                 # FIXME: the trimming is probably unreliable if format includes day/month names
305                 #        and those would need to be translated anyway.
306                 date = datetime.strptime(value[:get_date_length(parse_format)], parse_format)
307             elif isinstance(value, time.struct_time):
308                 date = datetime(*value[:6])
309             else:
310                 date = datetime(*value.timetuple()[:6])
311             if date_time:
312                 # Convert datetime values to the expected client/context timezone
313                 date = datetime_field.context_timestamp(self.cr, self.uid,
314                                                         timestamp=date,
315                                                         context=self.localcontext)
316             return date.strftime(date_format.encode('utf-8'))
317
318         res = self.lang_dict['lang_obj'].format('%.' + str(digits) + 'f', value, grouping=grouping, monetary=monetary)
319         if currency_obj:
320             if currency_obj.position == 'after':
321                 res='%s %s'%(res,currency_obj.symbol)
322             elif currency_obj and currency_obj.position == 'before':
323                 res='%s %s'%(currency_obj.symbol, res)
324         return res
325
326     def display_address(self, address_browse_record):
327         return self.pool['res.partner']._display_address(self.cr, self.uid, address_browse_record)
328
329     def repeatIn(self, lst, name,nodes_parent=False):
330         ret_lst = []
331         for id in lst:
332             ret_lst.append({name:id})
333         return ret_lst
334
335     def _translate(self,text):
336         lang = self.localcontext['lang']
337         if lang and text and not text.isspace():
338             transl_obj = self.pool['ir.translation']
339             piece_list = self._transl_regex.split(text)
340             for pn in range(len(piece_list)):
341                 if not self._transl_regex.match(piece_list[pn]):
342                     source_string = piece_list[pn].replace('\n', ' ').strip()
343                     if len(source_string):
344                         translated_string = transl_obj._get_source(self.cr, self.uid, self.name, ('report', 'rml'), lang, source_string)
345                         if translated_string:
346                             piece_list[pn] = piece_list[pn].replace(source_string, translated_string)
347             text = ''.join(piece_list)
348         return text
349
350     def _add_header(self, rml_dom, header='external'):
351         if header=='internal':
352             rml_head =  self.rml_header2
353         elif header=='internal landscape':
354             rml_head =  self.rml_header3
355         else:
356             rml_head =  self.rml_header
357
358         head_dom = etree.XML(rml_head)
359         for tag in head_dom:
360             found = rml_dom.find('.//'+tag.tag)
361             if found is not None and len(found):
362                 if tag.get('position'):
363                     found.append(tag)
364                 else :
365                     found.getparent().replace(found,tag)
366         return True
367
368     def set_context(self, objects, data, ids, report_type = None):
369         self.localcontext['data'] = data
370         self.localcontext['objects'] = objects
371         self.localcontext['digits_fmt'] = self.digits_fmt
372         self.localcontext['get_digits'] = self.get_digits
373         self.datas = data
374         self.ids = ids
375         self.objects = objects
376         if report_type:
377             if report_type=='odt' :
378                 self.localcontext.update({'name_space' :common.odt_namespace})
379             else:
380                 self.localcontext.update({'name_space' :common.sxw_namespace})
381
382         # WARNING: the object[0].exists() call below is slow but necessary because
383         # some broken reporting wizards pass incorrect IDs (e.g. ir.ui.menu ids)
384         if objects and len(objects) == 1 and \
385             objects[0].exists() and 'company_id' in objects[0] and objects[0].company_id:
386             # When we print only one record, we can auto-set the correct
387             # company in the localcontext. For other cases the report
388             # will have to call setCompany() inside the main repeatIn loop.
389             self.setCompany(objects[0].company_id)
390
391 class report_sxw(report_rml, preprocess.report):
392     """
393     The register=True kwarg has been added to help remove the
394     openerp.netsvc.LocalService() indirection and the related
395     openerp.report.interface.report_int._reports dictionary:
396     report_sxw registered in XML with auto=False are also registered in Python.
397     In that case, they are registered in the above dictionary. Since
398     registration is automatically done upon instanciation, and that
399     instanciation is needed before rendering, a way was needed to
400     instanciate-without-register a report. In the future, no report
401     should be registered in the above dictionary and it will be dropped.
402     """
403     def __init__(self, name, table, rml=False, parser=rml_parse, header='external', store=False, register=True):
404         report_rml.__init__(self, name, table, rml, '', register=register)
405         self.name = name
406         self.parser = parser
407         self.header = header
408         self.store = store
409         self.internal_header=False
410         if header=='internal' or header=='internal landscape':
411             self.internal_header=True
412
413     def getObjects(self, cr, uid, ids, context):
414         table_obj = openerp.registry(cr.dbname)[self.table]
415         return table_obj.browse(cr, uid, ids, list_class=browse_record_list, context=context, fields_process=_fields_process)
416
417     def create(self, cr, uid, ids, data, context=None):
418         if context is None:
419             context = {}
420         if self.internal_header:
421             context.update(internal_header=self.internal_header)
422         # skip osv.fields.sanitize_binary_value() because we want the raw bytes in all cases
423         context.update(bin_raw=True)
424         registry = openerp.registry(cr.dbname)
425         ir_obj = registry['ir.actions.report.xml']
426         report_xml_ids = ir_obj.search(cr, uid,
427                 [('report_name', '=', self.name[7:])], context=context)
428         if report_xml_ids:
429             report_xml = ir_obj.browse(cr, uid, report_xml_ids[0], context=context)
430         else:
431             title = ''
432             report_file = tools.file_open(self.tmpl, subdir=None)
433             try:
434                 rml = report_file.read()
435                 report_type= data.get('report_type', 'pdf')
436                 class a(object):
437                     def __init__(self, *args, **argv):
438                         for key,arg in argv.items():
439                             setattr(self, key, arg)
440                 report_xml = a(title=title, report_type=report_type, report_rml_content=rml, name=title, attachment=False, header=self.header)
441             finally:
442                 report_file.close()
443         if report_xml.header:
444             report_xml.header = self.header
445         report_type = report_xml.report_type
446         if report_type in ['sxw','odt']:
447             fnct = self.create_source_odt
448         elif report_type in ['pdf','raw','txt','html']:
449             fnct = self.create_source_pdf
450         elif report_type=='html2html':
451             fnct = self.create_source_html2html
452         elif report_type=='mako2html':
453             fnct = self.create_source_mako2html
454         else:
455             raise NotImplementedError(_('Unknown report type: %s') % report_type)
456         fnct_ret = fnct(cr, uid, ids, data, report_xml, context)
457         if not fnct_ret:
458             return False, False
459         return fnct_ret
460
461     def create_source_odt(self, cr, uid, ids, data, report_xml, context=None):
462         return self.create_single_odt(cr, uid, ids, data, report_xml, context or {})
463
464     def create_source_html2html(self, cr, uid, ids, data, report_xml, context=None):
465         return self.create_single_html2html(cr, uid, ids, data, report_xml, context or {})
466
467     def create_source_mako2html(self, cr, uid, ids, data, report_xml, context=None):
468         return self.create_single_mako2html(cr, uid, ids, data, report_xml, context or {})
469
470     def create_source_pdf(self, cr, uid, ids, data, report_xml, context=None):
471         if not context:
472             context={}
473         registry = openerp.registry(cr.dbname)
474         attach = report_xml.attachment
475         if attach:
476             objs = self.getObjects(cr, uid, ids, context)
477             results = []
478             for obj in objs:
479                 aname = eval(attach, {'object':obj, 'time':time})
480                 result = False
481                 if report_xml.attachment_use and aname and context.get('attachment_use', True):
482                     aids = registry['ir.attachment'].search(cr, uid, [('datas_fname','=',aname+'.pdf'),('res_model','=',self.table),('res_id','=',obj.id)])
483                     if aids:
484                         brow_rec = registry['ir.attachment'].browse(cr, uid, aids[0])
485                         if not brow_rec.datas:
486                             continue
487                         d = base64.decodestring(brow_rec.datas)
488                         results.append((d,'pdf'))
489                         continue
490                 result = self.create_single_pdf(cr, uid, [obj.id], data, report_xml, context)
491                 if not result:
492                     return False
493                 if aname:
494                     try:
495                         name = aname+'.'+result[1]
496                         # Remove the default_type entry from the context: this
497                         # is for instance used on the account.account_invoices
498                         # and is thus not intended for the ir.attachment type
499                         # field.
500                         ctx = dict(context)
501                         ctx.pop('default_type', None)
502                         registry['ir.attachment'].create(cr, uid, {
503                             'name': aname,
504                             'datas': base64.encodestring(result[0]),
505                             'datas_fname': name,
506                             'res_model': self.table,
507                             'res_id': obj.id,
508                             }, context=ctx
509                         )
510                     except Exception:
511                         #TODO: should probably raise a proper osv_except instead, shouldn't we? see LP bug #325632
512                         _logger.error('Could not create saved report attachment', exc_info=True)
513                 results.append(result)
514             if results:
515                 if results[0][1]=='pdf':
516                     from pyPdf import PdfFileWriter, PdfFileReader
517                     output = PdfFileWriter()
518                     for r in results:
519                         reader = PdfFileReader(cStringIO.StringIO(r[0]))
520                         for page in range(reader.getNumPages()):
521                             output.addPage(reader.getPage(page))
522                     s = cStringIO.StringIO()
523                     output.write(s)
524                     return s.getvalue(), results[0][1]
525         return self.create_single_pdf(cr, uid, ids, data, report_xml, context)
526
527     def create_single_pdf(self, cr, uid, ids, data, report_xml, context=None):
528         if not context:
529             context={}
530         logo = None
531         context = context.copy()
532         title = report_xml.name
533         rml = report_xml.report_rml_content
534         # if no rml file is found
535         if not rml:
536             return False
537         rml_parser = self.parser(cr, uid, self.name2, context=context)
538         objs = self.getObjects(cr, uid, ids, context)
539         rml_parser.set_context(objs, data, ids, report_xml.report_type)
540         processed_rml = etree.XML(rml)
541         if report_xml.header:
542             rml_parser._add_header(processed_rml, self.header)
543         processed_rml = self.preprocess_rml(processed_rml,report_xml.report_type)
544         if rml_parser.logo:
545             logo = base64.decodestring(rml_parser.logo)
546         create_doc = self.generators[report_xml.report_type]
547         pdf = create_doc(etree.tostring(processed_rml),rml_parser.localcontext,logo,title.encode('utf8'))
548         return pdf, report_xml.report_type
549
550     def create_single_odt(self, cr, uid, ids, data, report_xml, context=None):
551         if not context:
552             context={}
553         context = context.copy()
554         report_type = report_xml.report_type
555         context['parents'] = sxw_parents
556         binary_report_content = report_xml.report_sxw_content
557         if isinstance(report_xml.report_sxw_content, unicode):
558             # if binary content was passed as unicode, we must
559             # re-encode it as a 8-bit string using the pass-through
560             # 'latin1' encoding, to restore the original byte values.
561             # See also osv.fields.sanitize_binary_value()
562             binary_report_content = report_xml.report_sxw_content.encode("latin1")
563
564         sxw_io = StringIO.StringIO(binary_report_content)
565         sxw_z = zipfile.ZipFile(sxw_io, mode='r')
566         rml = sxw_z.read('content.xml')
567         meta = sxw_z.read('meta.xml')
568         mime_type = sxw_z.read('mimetype')
569         if mime_type == 'application/vnd.sun.xml.writer':
570             mime_type = 'sxw'
571         else :
572             mime_type = 'odt'
573         sxw_z.close()
574
575         rml_parser = self.parser(cr, uid, self.name2, context=context)
576         rml_parser.parents = sxw_parents
577         rml_parser.tag = sxw_tag
578         objs = self.getObjects(cr, uid, ids, context)
579         rml_parser.set_context(objs, data, ids, mime_type)
580
581         rml_dom_meta = node = etree.XML(meta)
582         elements = node.findall(rml_parser.localcontext['name_space']["meta"]+"user-defined")
583         for pe in elements:
584             if pe.get(rml_parser.localcontext['name_space']["meta"]+"name"):
585                 if pe.get(rml_parser.localcontext['name_space']["meta"]+"name") == "Info 3":
586                     pe[0].text=data['id']
587                 if pe.get(rml_parser.localcontext['name_space']["meta"]+"name") == "Info 4":
588                     pe[0].text=data['model']
589         meta = etree.tostring(rml_dom_meta, encoding='utf-8',
590                               xml_declaration=True)
591
592         rml_dom =  etree.XML(rml)
593         elements = []
594         key1 = rml_parser.localcontext['name_space']["text"]+"p"
595         key2 = rml_parser.localcontext['name_space']["text"]+"drop-down"
596         for n in rml_dom.iterdescendants():
597             if n.tag == key1:
598                 elements.append(n)
599         if mime_type == 'odt':
600             for pe in elements:
601                 e = pe.findall(key2)
602                 for de in e:
603                     pp=de.getparent()
604                     if de.text or de.tail:
605                         pe.text = de.text or de.tail
606                     for cnd in de:
607                         if cnd.text or cnd.tail:
608                             if pe.text:
609                                 pe.text +=  cnd.text or cnd.tail
610                             else:
611                                 pe.text =  cnd.text or cnd.tail
612                             pp.remove(de)
613         else:
614             for pe in elements:
615                 e = pe.findall(key2)
616                 for de in e:
617                     pp = de.getparent()
618                     if de.text or de.tail:
619                         pe.text = de.text or de.tail
620                     for cnd in de:
621                         text = cnd.get("{http://openoffice.org/2000/text}value",False)
622                         if text:
623                             if pe.text and text.startswith('[['):
624                                 pe.text +=  text
625                             elif text.startswith('[['):
626                                 pe.text =  text
627                             if de.getparent():
628                                 pp.remove(de)
629
630         rml_dom = self.preprocess_rml(rml_dom, mime_type)
631         create_doc = self.generators[mime_type]
632         odt = etree.tostring(create_doc(rml_dom, rml_parser.localcontext),
633                              encoding='utf-8', xml_declaration=True)
634         sxw_contents = {'content.xml':odt, 'meta.xml':meta}
635
636         if report_xml.header:
637             #Add corporate header/footer
638             rml_file = tools.file_open(os.path.join('base', 'report', 'corporate_%s_header.xml' % report_type))
639             try:
640                 rml = rml_file.read()
641                 rml_parser = self.parser(cr, uid, self.name2, context=context)
642                 rml_parser.parents = sxw_parents
643                 rml_parser.tag = sxw_tag
644                 objs = self.getObjects(cr, uid, ids, context)
645                 rml_parser.set_context(objs, data, ids, report_xml.report_type)
646                 rml_dom = self.preprocess_rml(etree.XML(rml),report_type)
647                 create_doc = self.generators[report_type]
648                 odt = create_doc(rml_dom,rml_parser.localcontext)
649                 if report_xml.header:
650                     rml_parser._add_header(odt)
651                 odt = etree.tostring(odt, encoding='utf-8',
652                                      xml_declaration=True)
653                 sxw_contents['styles.xml'] = odt
654             finally:
655                 rml_file.close()
656
657         #created empty zip writing sxw contents to avoid duplication
658         sxw_out = StringIO.StringIO()
659         sxw_out_zip = zipfile.ZipFile(sxw_out, mode='w')
660         sxw_template_zip = zipfile.ZipFile (sxw_io, 'r')
661         for item in sxw_template_zip.infolist():
662             if item.filename not in sxw_contents:
663                 buffer = sxw_template_zip.read(item.filename)
664                 sxw_out_zip.writestr(item.filename, buffer)
665         for item_filename, buffer in sxw_contents.iteritems():
666             sxw_out_zip.writestr(item_filename, buffer)
667         sxw_template_zip.close()
668         sxw_out_zip.close()
669         final_op = sxw_out.getvalue()
670         sxw_io.close()
671         sxw_out.close()
672         return final_op, mime_type
673
674     def create_single_html2html(self, cr, uid, ids, data, report_xml, context=None):
675         if not context:
676             context = {}
677         context = context.copy()
678         report_type = 'html'
679         context['parents'] = html_parents
680
681         html = report_xml.report_rml_content
682         html_parser = self.parser(cr, uid, self.name2, context=context)
683         html_parser.parents = html_parents
684         html_parser.tag = sxw_tag
685         objs = self.getObjects(cr, uid, ids, context)
686         html_parser.set_context(objs, data, ids, report_type)
687
688         html_dom =  etree.HTML(html)
689         html_dom = self.preprocess_rml(html_dom,'html2html')
690
691         create_doc = self.generators['html2html']
692         html = etree.tostring(create_doc(html_dom, html_parser.localcontext))
693
694         return html.replace('&amp;','&').replace('&lt;', '<').replace('&gt;', '>').replace('</br>',''), report_type
695
696     def create_single_mako2html(self, cr, uid, ids, data, report_xml, context=None):
697         mako_html = report_xml.report_rml_content
698         html_parser = self.parser(cr, uid, self.name2, context)
699         objs = self.getObjects(cr, uid, ids, context)
700         html_parser.set_context(objs, data, ids, 'html')
701         create_doc = self.generators['makohtml2html']
702         html = create_doc(mako_html,html_parser.localcontext)
703         return html,'html'
704
705
706 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: