[FIX] improved date format in fiche de paie report
[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(self.lang_obj.date_format.encode('utf-8'))
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"%((self.lang_obj.date_format).encode('utf-8'),
123                                       (self.lang_obj.time_format).encode('utf-8')))
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 not char:
193             return ''
194         if len(char) <= size:
195             return char
196         return char[:size-len(truncation_str)] + truncation_str
197
198     def setCompany(self, company_id):
199         if company_id:
200             self.localcontext['company'] = company_id
201             self.localcontext['logo'] = company_id.logo
202             self.rml_header = company_id.rml_header
203             self.rml_header2 = company_id.rml_header2
204             self.rml_header3 = company_id.rml_header3
205             self.logo = company_id.logo
206
207     def _strip_name(self, name, maxlen=50):
208         return self._ellipsis(name, maxlen)
209
210     def format(self, text, oldtag=None):
211         return text.strip()
212
213     def removeParentNode(self, tag=None):
214         raise GeneratorExit('Skip')
215
216     def set_html_image(self,id,model=None,field=None,context=None):
217         if not id :
218             return ''
219         if not model:
220             model = 'ir.attachment'
221         try :
222             id = int(id)
223             res = self.pool.get(model).read(self.cr,self.uid,id)
224             if field :
225                 return res[field]
226             elif model =='ir.attachment' :
227                 return res['datas']
228             else :
229                 return ''
230         except Exception:
231             return ''
232
233     def setLang(self, lang):
234         self.localcontext['lang'] = lang
235         self.lang_dict_called = False
236         for obj in self.objects:
237             obj._context['lang'] = lang
238
239     def _get_lang_dict(self):
240         pool_lang = self.pool.get('res.lang')
241         lang = self.localcontext.get('lang', 'en_US') or 'en_US'
242         lang_ids = pool_lang.search(self.cr,self.uid,[('code','=',lang)])
243         if not lang_ids:
244             lang_ids = pool_lang.search(self.cr,self.uid,[('code','=','en_US')])
245         lang_obj = pool_lang.browse(self.cr,self.uid,lang_ids[0])
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.get('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.get('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.get('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     def __init__(self, name, table, rml=False, parser=rml_parse, header='external', store=False):
392         report_rml.__init__(self, name, table, rml, '')
393         self.name = name
394         self.parser = parser
395         self.header = header
396         self.store = store
397         self.internal_header=False
398         if header=='internal' or header=='internal landscape':
399             self.internal_header=True
400
401     def getObjects(self, cr, uid, ids, context):
402         table_obj = pooler.get_pool(cr.dbname).get(self.table)
403         return table_obj.browse(cr, uid, ids, list_class=browse_record_list, context=context, fields_process=_fields_process)
404
405     def create(self, cr, uid, ids, data, context=None):
406         if context is None:
407             context = {}
408         if self.internal_header:
409             context.update(internal_header=self.internal_header)
410         # skip osv.fields.sanitize_binary_value() because we want the raw bytes in all cases
411         context.update(bin_raw=True)
412         pool = pooler.get_pool(cr.dbname)
413         ir_obj = pool.get('ir.actions.report.xml')
414         report_xml_ids = ir_obj.search(cr, uid,
415                 [('report_name', '=', self.name[7:])], context=context)
416         if report_xml_ids:
417             report_xml = ir_obj.browse(cr, uid, report_xml_ids[0], context=context)
418         else:
419             title = ''
420             report_file = tools.file_open(self.tmpl, subdir=None)
421             try:
422                 rml = report_file.read()
423                 report_type= data.get('report_type', 'pdf')
424                 class a(object):
425                     def __init__(self, *args, **argv):
426                         for key,arg in argv.items():
427                             setattr(self, key, arg)
428                 report_xml = a(title=title, report_type=report_type, report_rml_content=rml, name=title, attachment=False, header=self.header)
429             finally:
430                 report_file.close()
431         if report_xml.header:
432             report_xml.header = self.header
433         report_type = report_xml.report_type
434         if report_type in ['sxw','odt']:
435             fnct = self.create_source_odt
436         elif report_type in ['pdf','raw','txt','html']:
437             fnct = self.create_source_pdf
438         elif report_type=='html2html':
439             fnct = self.create_source_html2html
440         elif report_type=='mako2html':
441             fnct = self.create_source_mako2html
442         else:
443             raise NotImplementedError(_('Unknown report type: %s') % report_type)
444         fnct_ret = fnct(cr, uid, ids, data, report_xml, context)
445         if not fnct_ret:
446             return False, False
447         return fnct_ret
448
449     def create_source_odt(self, cr, uid, ids, data, report_xml, context=None):
450         return self.create_single_odt(cr, uid, ids, data, report_xml, context or {})
451
452     def create_source_html2html(self, cr, uid, ids, data, report_xml, context=None):
453         return self.create_single_html2html(cr, uid, ids, data, report_xml, context or {})
454
455     def create_source_mako2html(self, cr, uid, ids, data, report_xml, context=None):
456         return self.create_single_mako2html(cr, uid, ids, data, report_xml, context or {})
457
458     def create_source_pdf(self, cr, uid, ids, data, report_xml, context=None):
459         if not context:
460             context={}
461         pool = pooler.get_pool(cr.dbname)
462         attach = report_xml.attachment
463         if attach:
464             objs = self.getObjects(cr, uid, ids, context)
465             results = []
466             for obj in objs:
467                 aname = eval(attach, {'object':obj, 'time':time})
468                 result = False
469                 if report_xml.attachment_use and aname and context.get('attachment_use', True):
470                     aids = pool.get('ir.attachment').search(cr, uid, [('datas_fname','=',aname+'.pdf'),('res_model','=',self.table),('res_id','=',obj.id)])
471                     if aids:
472                         brow_rec = pool.get('ir.attachment').browse(cr, uid, aids[0])
473                         if not brow_rec.datas:
474                             continue
475                         d = base64.decodestring(brow_rec.datas)
476                         results.append((d,'pdf'))
477                         continue
478                 result = self.create_single_pdf(cr, uid, [obj.id], data, report_xml, context)
479                 if not result:
480                     return False
481                 if aname:
482                     try:
483                         name = aname+'.'+result[1]
484                         # Remove the default_type entry from the context: this
485                         # is for instance used on the account.account_invoices
486                         # and is thus not intended for the ir.attachment type
487                         # field.
488                         ctx = dict(context)
489                         ctx.pop('default_type', None)
490                         pool.get('ir.attachment').create(cr, uid, {
491                             'name': aname,
492                             'datas': base64.encodestring(result[0]),
493                             'datas_fname': name,
494                             'res_model': self.table,
495                             'res_id': obj.id,
496                             }, context=ctx
497                         )
498                     except Exception:
499                         #TODO: should probably raise a proper osv_except instead, shouldn't we? see LP bug #325632
500                         _logger.error('Could not create saved report attachment', exc_info=True)
501                 results.append(result)
502             if results:
503                 if results[0][1]=='pdf':
504                     from pyPdf import PdfFileWriter, PdfFileReader
505                     output = PdfFileWriter()
506                     for r in results:
507                         reader = PdfFileReader(cStringIO.StringIO(r[0]))
508                         for page in range(reader.getNumPages()):
509                             output.addPage(reader.getPage(page))
510                     s = cStringIO.StringIO()
511                     output.write(s)
512                     return s.getvalue(), results[0][1]
513         return self.create_single_pdf(cr, uid, ids, data, report_xml, context)
514
515     def create_single_pdf(self, cr, uid, ids, data, report_xml, context=None):
516         if not context:
517             context={}
518         logo = None
519         context = context.copy()
520         title = report_xml.name
521         rml = report_xml.report_rml_content
522         # if no rml file is found
523         if not rml:
524             return False
525         rml_parser = self.parser(cr, uid, self.name2, context=context)
526         objs = self.getObjects(cr, uid, ids, context)
527         rml_parser.set_context(objs, data, ids, report_xml.report_type)
528         processed_rml = etree.XML(rml)
529         if report_xml.header:
530             rml_parser._add_header(processed_rml, self.header)
531         processed_rml = self.preprocess_rml(processed_rml,report_xml.report_type)
532         if rml_parser.logo:
533             logo = base64.decodestring(rml_parser.logo)
534         create_doc = self.generators[report_xml.report_type]
535         pdf = create_doc(etree.tostring(processed_rml),rml_parser.localcontext,logo,title.encode('utf8'))
536         return pdf, report_xml.report_type
537
538     def create_single_odt(self, cr, uid, ids, data, report_xml, context=None):
539         if not context:
540             context={}
541         context = context.copy()
542         report_type = report_xml.report_type
543         context['parents'] = sxw_parents
544         binary_report_content = report_xml.report_sxw_content
545         if isinstance(report_xml.report_sxw_content, unicode):
546             # if binary content was passed as unicode, we must
547             # re-encode it as a 8-bit string using the pass-through
548             # 'latin1' encoding, to restore the original byte values.
549             # See also osv.fields.sanitize_binary_value()
550             binary_report_content = report_xml.report_sxw_content.encode("latin1")
551
552         sxw_io = StringIO.StringIO(binary_report_content)
553         sxw_z = zipfile.ZipFile(sxw_io, mode='r')
554         rml = sxw_z.read('content.xml')
555         meta = sxw_z.read('meta.xml')
556         mime_type = sxw_z.read('mimetype')
557         if mime_type == 'application/vnd.sun.xml.writer':
558             mime_type = 'sxw'
559         else :
560             mime_type = 'odt'
561         sxw_z.close()
562
563         rml_parser = self.parser(cr, uid, self.name2, context=context)
564         rml_parser.parents = sxw_parents
565         rml_parser.tag = sxw_tag
566         objs = self.getObjects(cr, uid, ids, context)
567         rml_parser.set_context(objs, data, ids, mime_type)
568
569         rml_dom_meta = node = etree.XML(meta)
570         elements = node.findall(rml_parser.localcontext['name_space']["meta"]+"user-defined")
571         for pe in elements:
572             if pe.get(rml_parser.localcontext['name_space']["meta"]+"name"):
573                 if pe.get(rml_parser.localcontext['name_space']["meta"]+"name") == "Info 3":
574                     pe[0].text=data['id']
575                 if pe.get(rml_parser.localcontext['name_space']["meta"]+"name") == "Info 4":
576                     pe[0].text=data['model']
577         meta = etree.tostring(rml_dom_meta, encoding='utf-8',
578                               xml_declaration=True)
579
580         rml_dom =  etree.XML(rml)
581         elements = []
582         key1 = rml_parser.localcontext['name_space']["text"]+"p"
583         key2 = rml_parser.localcontext['name_space']["text"]+"drop-down"
584         for n in rml_dom.iterdescendants():
585             if n.tag == key1:
586                 elements.append(n)
587         if mime_type == 'odt':
588             for pe in elements:
589                 e = pe.findall(key2)
590                 for de in e:
591                     pp=de.getparent()
592                     if de.text or de.tail:
593                         pe.text = de.text or de.tail
594                     for cnd in de:
595                         if cnd.text or cnd.tail:
596                             if pe.text:
597                                 pe.text +=  cnd.text or cnd.tail
598                             else:
599                                 pe.text =  cnd.text or cnd.tail
600                             pp.remove(de)
601         else:
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                         text = cnd.get("{http://openoffice.org/2000/text}value",False)
610                         if text:
611                             if pe.text and text.startswith('[['):
612                                 pe.text +=  text
613                             elif text.startswith('[['):
614                                 pe.text =  text
615                             if de.getparent():
616                                 pp.remove(de)
617
618         rml_dom = self.preprocess_rml(rml_dom, mime_type)
619         create_doc = self.generators[mime_type]
620         odt = etree.tostring(create_doc(rml_dom, rml_parser.localcontext),
621                              encoding='utf-8', xml_declaration=True)
622         sxw_contents = {'content.xml':odt, 'meta.xml':meta}
623
624         if report_xml.header:
625             #Add corporate header/footer
626             rml_file = tools.file_open(os.path.join('base', 'report', 'corporate_%s_header.xml' % report_type))
627             try:
628                 rml = rml_file.read()
629                 rml_parser = self.parser(cr, uid, self.name2, context=context)
630                 rml_parser.parents = sxw_parents
631                 rml_parser.tag = sxw_tag
632                 objs = self.getObjects(cr, uid, ids, context)
633                 rml_parser.set_context(objs, data, ids, report_xml.report_type)
634                 rml_dom = self.preprocess_rml(etree.XML(rml),report_type)
635                 create_doc = self.generators[report_type]
636                 odt = create_doc(rml_dom,rml_parser.localcontext)
637                 if report_xml.header:
638                     rml_parser._add_header(odt)
639                 odt = etree.tostring(odt, encoding='utf-8',
640                                      xml_declaration=True)
641                 sxw_contents['styles.xml'] = odt
642             finally:
643                 rml_file.close()
644
645         #created empty zip writing sxw contents to avoid duplication
646         sxw_out = StringIO.StringIO()
647         sxw_out_zip = zipfile.ZipFile(sxw_out, mode='w')
648         sxw_template_zip = zipfile.ZipFile (sxw_io, 'r')
649         for item in sxw_template_zip.infolist():
650             if item.filename not in sxw_contents:
651                 buffer = sxw_template_zip.read(item.filename)
652                 sxw_out_zip.writestr(item.filename, buffer)
653         for item_filename, buffer in sxw_contents.iteritems():
654             sxw_out_zip.writestr(item_filename, buffer)
655         sxw_template_zip.close()
656         sxw_out_zip.close()
657         final_op = sxw_out.getvalue()
658         sxw_io.close()
659         sxw_out.close()
660         return final_op, mime_type
661
662     def create_single_html2html(self, cr, uid, ids, data, report_xml, context=None):
663         if not context:
664             context = {}
665         context = context.copy()
666         report_type = 'html'
667         context['parents'] = html_parents
668
669         html = report_xml.report_rml_content
670         html_parser = self.parser(cr, uid, self.name2, context=context)
671         html_parser.parents = html_parents
672         html_parser.tag = sxw_tag
673         objs = self.getObjects(cr, uid, ids, context)
674         html_parser.set_context(objs, data, ids, report_type)
675
676         html_dom =  etree.HTML(html)
677         html_dom = self.preprocess_rml(html_dom,'html2html')
678
679         create_doc = self.generators['html2html']
680         html = etree.tostring(create_doc(html_dom, html_parser.localcontext))
681
682         return html.replace('&amp;','&').replace('&lt;', '<').replace('&gt;', '>').replace('</br>',''), report_type
683
684     def create_single_mako2html(self, cr, uid, ids, data, report_xml, context=None):
685         mako_html = report_xml.report_rml_content
686         html_parser = self.parser(cr, uid, self.name2, context)
687         objs = self.getObjects(cr, uid, ids, context)
688         html_parser.set_context(objs, data, ids, 'html')
689         create_doc = self.generators['makohtml2html']
690         html = create_doc(mako_html,html_parser.localcontext)
691         return html,'html'
692
693
694 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: