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