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