[FIX] stock: error on stock.partial.move creation
[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]
269                 if not d and d is not 0:
270                     d = DEFAULT_DIGITS
271         return d
272
273     def formatLang(self, value, digits=None, date=False, date_time=False, grouping=True, monetary=False, dp=False, currency_obj=False):
274         """
275             Assuming 'Account' decimal.precision=3:
276                 formatLang(value) -> digits=2 (default)
277                 formatLang(value, digits=4) -> digits=4
278                 formatLang(value, dp='Account') -> digits=3
279                 formatLang(value, digits=5, dp='Account') -> digits=5
280         """
281         if digits is None:
282             if dp:
283                 digits = self.get_digits(dp=dp)
284             else:
285                 digits = self.get_digits(value)
286
287         if isinstance(value, (str, unicode)) and not value:
288             return ''
289
290         if not self.lang_dict_called:
291             self._get_lang_dict()
292             self.lang_dict_called = True
293
294         if date or date_time:
295             if not str(value):
296                 return ''
297
298             date_format = self.lang_dict['date_format']
299             parse_format = DEFAULT_SERVER_DATE_FORMAT
300             if date_time:
301                 value = value.split('.')[0]
302                 date_format = date_format + " " + self.lang_dict['time_format']
303                 parse_format = DEFAULT_SERVER_DATETIME_FORMAT
304             if isinstance(value, basestring):
305                 # FIXME: the trimming is probably unreliable if format includes day/month names
306                 #        and those would need to be translated anyway.
307                 date = datetime.strptime(value[:get_date_length(parse_format)], parse_format)
308             elif isinstance(value, time.struct_time):
309                 date = datetime(*value[:6])
310             else:
311                 date = datetime(*value.timetuple()[:6])
312             if date_time:
313                 # Convert datetime values to the expected client/context timezone
314                 date = datetime_field.context_timestamp(self.cr, self.uid,
315                                                         timestamp=date,
316                                                         context=self.localcontext)
317             return date.strftime(date_format.encode('utf-8'))
318
319         res = self.lang_dict['lang_obj'].format('%.' + str(digits) + 'f', value, grouping=grouping, monetary=monetary)
320         if currency_obj:
321             if currency_obj.position == 'after':
322                 res='%s %s'%(res,currency_obj.symbol)
323             elif currency_obj and currency_obj.position == 'before':
324                 res='%s %s'%(currency_obj.symbol, res)
325         return res
326
327     def display_address(self, address_browse_record, without_company=False):
328         return self.pool.get('res.partner')._display_address(
329             self.cr, self.uid, address_browse_record,
330             without_company=without_company
331         )
332
333     def repeatIn(self, lst, name,nodes_parent=False):
334         ret_lst = []
335         for id in lst:
336             ret_lst.append({name:id})
337         return ret_lst
338
339     def _translate(self,text):
340         lang = self.localcontext['lang']
341         if lang and text and not text.isspace():
342             transl_obj = self.pool.get('ir.translation')
343             piece_list = self._transl_regex.split(text)
344             for pn in range(len(piece_list)):
345                 if not self._transl_regex.match(piece_list[pn]):
346                     source_string = piece_list[pn].replace('\n', ' ').strip()
347                     if len(source_string):
348                         translated_string = transl_obj._get_source(self.cr, self.uid, self.name, ('report', 'rml'), lang, source_string)
349                         if translated_string:
350                             piece_list[pn] = piece_list[pn].replace(source_string, translated_string)
351             text = ''.join(piece_list)
352         return text
353
354     def _add_header(self, rml_dom, header='external'):
355         if header=='internal':
356             rml_head =  self.rml_header2
357         elif header=='internal landscape':
358             rml_head =  self.rml_header3
359         else:
360             rml_head =  self.rml_header
361
362         head_dom = etree.XML(rml_head)
363         for tag in head_dom:
364             found = rml_dom.find('.//'+tag.tag)
365             if found is not None and len(found):
366                 if tag.get('position'):
367                     found.append(tag)
368                 else :
369                     found.getparent().replace(found,tag)
370         return True
371
372     def set_context(self, objects, data, ids, report_type = None):
373         self.localcontext['data'] = data
374         self.localcontext['objects'] = objects
375         self.localcontext['digits_fmt'] = self.digits_fmt
376         self.localcontext['get_digits'] = self.get_digits
377         self.datas = data
378         self.ids = ids
379         self.objects = objects
380         if report_type:
381             if report_type=='odt' :
382                 self.localcontext.update({'name_space' :common.odt_namespace})
383             else:
384                 self.localcontext.update({'name_space' :common.sxw_namespace})
385
386         # WARNING: the object[0].exists() call below is slow but necessary because
387         # some broken reporting wizards pass incorrect IDs (e.g. ir.ui.menu ids)
388         if objects and len(objects) == 1 and \
389             objects[0].exists() and 'company_id' in objects[0] and objects[0].company_id:
390             # When we print only one record, we can auto-set the correct
391             # company in the localcontext. For other cases the report
392             # will have to call setCompany() inside the main repeatIn loop.
393             self.setCompany(objects[0].company_id)
394
395 class report_sxw(report_rml, preprocess.report):
396     def __init__(self, name, table, rml=False, parser=rml_parse, header='external', store=False):
397         report_rml.__init__(self, name, table, rml, '')
398         self.name = name
399         self.parser = parser
400         self.header = header
401         self.store = store
402         self.internal_header=False
403         if header=='internal' or header=='internal landscape':
404             self.internal_header=True
405
406     def getObjects(self, cr, uid, ids, context):
407         table_obj = pooler.get_pool(cr.dbname).get(self.table)
408         return table_obj.browse(cr, uid, ids, list_class=browse_record_list, context=context, fields_process=_fields_process)
409
410     def create(self, cr, uid, ids, data, context=None):
411         if context is None:
412             context = {}
413         if self.internal_header:
414             context.update(internal_header=self.internal_header)
415         # skip osv.fields.sanitize_binary_value() because we want the raw bytes in all cases
416         context.update(bin_raw=True)
417         pool = pooler.get_pool(cr.dbname)
418         ir_obj = pool.get('ir.actions.report.xml')
419         report_xml_ids = ir_obj.search(cr, uid,
420                 [('report_name', '=', self.name[7:])], context=context)
421         if report_xml_ids:
422             report_xml = ir_obj.browse(cr, uid, report_xml_ids[0], context=context)
423         else:
424             title = ''
425             report_file = tools.file_open(self.tmpl, subdir=None)
426             try:
427                 rml = report_file.read()
428                 report_type= data.get('report_type', 'pdf')
429                 class a(object):
430                     def __init__(self, *args, **argv):
431                         for key,arg in argv.items():
432                             setattr(self, key, arg)
433                 report_xml = a(title=title, report_type=report_type, report_rml_content=rml, name=title, attachment=False, header=self.header)
434             finally:
435                 report_file.close()
436         if report_xml.header:
437             report_xml.header = self.header
438         report_type = report_xml.report_type
439         if report_type in ['sxw','odt']:
440             fnct = self.create_source_odt
441         elif report_type in ['pdf','raw','txt','html']:
442             fnct = self.create_source_pdf
443         elif report_type=='html2html':
444             fnct = self.create_source_html2html
445         elif report_type=='mako2html':
446             fnct = self.create_source_mako2html
447         else:
448             raise NotImplementedError(_('Unknown report type: %s') % report_type)
449         fnct_ret = fnct(cr, uid, ids, data, report_xml, context)
450         if not fnct_ret:
451             return False, False
452         return fnct_ret
453
454     def create_source_odt(self, cr, uid, ids, data, report_xml, context=None):
455         return self.create_single_odt(cr, uid, ids, data, report_xml, context or {})
456
457     def create_source_html2html(self, cr, uid, ids, data, report_xml, context=None):
458         return self.create_single_html2html(cr, uid, ids, data, report_xml, context or {})
459
460     def create_source_mako2html(self, cr, uid, ids, data, report_xml, context=None):
461         return self.create_single_mako2html(cr, uid, ids, data, report_xml, context or {})
462
463     def create_source_pdf(self, cr, uid, ids, data, report_xml, context=None):
464         if not context:
465             context={}
466         pool = pooler.get_pool(cr.dbname)
467         attach = report_xml.attachment
468         if attach:
469             objs = self.getObjects(cr, uid, ids, context)
470             results = []
471             for obj in objs:
472                 aname = eval(attach, {'object':obj, 'time':time})
473                 result = False
474                 if report_xml.attachment_use and aname and context.get('attachment_use', True):
475                     aids = pool.get('ir.attachment').search(cr, uid, [('datas_fname','=',aname+'.pdf'),('res_model','=',self.table),('res_id','=',obj.id)])
476                     if aids:
477                         brow_rec = pool.get('ir.attachment').browse(cr, uid, aids[0])
478                         if not brow_rec.datas:
479                             continue
480                         d = base64.decodestring(brow_rec.datas)
481                         results.append((d,'pdf'))
482                         continue
483                 result = self.create_single_pdf(cr, uid, [obj.id], data, report_xml, context)
484                 if not result:
485                     return False
486                 if aname:
487                     try:
488                         name = aname+'.'+result[1]
489                         # Remove the default_type entry from the context: this
490                         # is for instance used on the account.account_invoices
491                         # and is thus not intended for the ir.attachment type
492                         # field.
493                         ctx = dict(context)
494                         ctx.pop('default_type', None)
495                         pool.get('ir.attachment').create(cr, uid, {
496                             'name': aname,
497                             'datas': base64.encodestring(result[0]),
498                             'datas_fname': name,
499                             'res_model': self.table,
500                             'res_id': obj.id,
501                             }, context=ctx
502                         )
503                     except Exception:
504                         #TODO: should probably raise a proper osv_except instead, shouldn't we? see LP bug #325632
505                         _logger.error('Could not create saved report attachment', exc_info=True)
506                 results.append(result)
507             if results:
508                 if results[0][1]=='pdf':
509                     from pyPdf import PdfFileWriter, PdfFileReader
510                     output = PdfFileWriter()
511                     for r in results:
512                         reader = PdfFileReader(cStringIO.StringIO(r[0]))
513                         for page in range(reader.getNumPages()):
514                             output.addPage(reader.getPage(page))
515                     s = cStringIO.StringIO()
516                     output.write(s)
517                     return s.getvalue(), results[0][1]
518         return self.create_single_pdf(cr, uid, ids, data, report_xml, context)
519
520     def create_single_pdf(self, cr, uid, ids, data, report_xml, context=None):
521         if not context:
522             context={}
523         logo = None
524         context = context.copy()
525         title = report_xml.name
526         rml = report_xml.report_rml_content
527         # if no rml file is found
528         if not rml:
529             return False
530         rml_parser = self.parser(cr, uid, self.name2, context=context)
531         objs = self.getObjects(cr, uid, ids, context)
532         rml_parser.set_context(objs, data, ids, report_xml.report_type)
533         processed_rml = etree.XML(rml)
534         if report_xml.header:
535             rml_parser._add_header(processed_rml, self.header)
536         processed_rml = self.preprocess_rml(processed_rml,report_xml.report_type)
537         if rml_parser.logo:
538             logo = base64.decodestring(rml_parser.logo)
539         create_doc = self.generators[report_xml.report_type]
540         pdf = create_doc(etree.tostring(processed_rml),rml_parser.localcontext,logo,title.encode('utf8'))
541         return pdf, report_xml.report_type
542
543     def create_single_odt(self, cr, uid, ids, data, report_xml, context=None):
544         if not context:
545             context={}
546         context = context.copy()
547         report_type = report_xml.report_type
548         context['parents'] = sxw_parents
549         binary_report_content = report_xml.report_sxw_content
550         if isinstance(report_xml.report_sxw_content, unicode):
551             # if binary content was passed as unicode, we must
552             # re-encode it as a 8-bit string using the pass-through
553             # 'latin1' encoding, to restore the original byte values.
554             # See also osv.fields.sanitize_binary_value()
555             binary_report_content = report_xml.report_sxw_content.encode("latin1")
556
557         sxw_io = StringIO.StringIO(binary_report_content)
558         sxw_z = zipfile.ZipFile(sxw_io, mode='r')
559         rml = sxw_z.read('content.xml')
560         meta = sxw_z.read('meta.xml')
561         mime_type = sxw_z.read('mimetype')
562         if mime_type == 'application/vnd.sun.xml.writer':
563             mime_type = 'sxw'
564         else :
565             mime_type = 'odt'
566         sxw_z.close()
567
568         rml_parser = self.parser(cr, uid, self.name2, context=context)
569         rml_parser.parents = sxw_parents
570         rml_parser.tag = sxw_tag
571         objs = self.getObjects(cr, uid, ids, context)
572         rml_parser.set_context(objs, data, ids, mime_type)
573
574         rml_dom_meta = node = etree.XML(meta)
575         elements = node.findall(rml_parser.localcontext['name_space']["meta"]+"user-defined")
576         for pe in elements:
577             if pe.get(rml_parser.localcontext['name_space']["meta"]+"name"):
578                 if pe.get(rml_parser.localcontext['name_space']["meta"]+"name") == "Info 3":
579                     pe[0].text=data['id']
580                 if pe.get(rml_parser.localcontext['name_space']["meta"]+"name") == "Info 4":
581                     pe[0].text=data['model']
582         meta = etree.tostring(rml_dom_meta, encoding='utf-8',
583                               xml_declaration=True)
584
585         rml_dom =  etree.XML(rml)
586         elements = []
587         key1 = rml_parser.localcontext['name_space']["text"]+"p"
588         key2 = rml_parser.localcontext['name_space']["text"]+"drop-down"
589         for n in rml_dom.iterdescendants():
590             if n.tag == key1:
591                 elements.append(n)
592         if mime_type == 'odt':
593             for pe in elements:
594                 e = pe.findall(key2)
595                 for de in e:
596                     pp=de.getparent()
597                     if de.text or de.tail:
598                         pe.text = de.text or de.tail
599                     for cnd in de:
600                         if cnd.text or cnd.tail:
601                             if pe.text:
602                                 pe.text +=  cnd.text or cnd.tail
603                             else:
604                                 pe.text =  cnd.text or cnd.tail
605                             pp.remove(de)
606         else:
607             for pe in elements:
608                 e = pe.findall(key2)
609                 for de in e:
610                     pp = de.getparent()
611                     if de.text or de.tail:
612                         pe.text = de.text or de.tail
613                     for cnd in de:
614                         text = cnd.get("{http://openoffice.org/2000/text}value",False)
615                         if text:
616                             if pe.text and text.startswith('[['):
617                                 pe.text +=  text
618                             elif text.startswith('[['):
619                                 pe.text =  text
620                             if de.getparent():
621                                 pp.remove(de)
622
623         rml_dom = self.preprocess_rml(rml_dom, mime_type)
624         create_doc = self.generators[mime_type]
625         odt = etree.tostring(create_doc(rml_dom, rml_parser.localcontext),
626                              encoding='utf-8', xml_declaration=True)
627         sxw_contents = {'content.xml':odt, 'meta.xml':meta}
628
629         if report_xml.header:
630             #Add corporate header/footer
631             rml_file = tools.file_open(os.path.join('base', 'report', 'corporate_%s_header.xml' % report_type))
632             try:
633                 rml = rml_file.read()
634                 rml_parser = self.parser(cr, uid, self.name2, context=context)
635                 rml_parser.parents = sxw_parents
636                 rml_parser.tag = sxw_tag
637                 objs = self.getObjects(cr, uid, ids, context)
638                 rml_parser.set_context(objs, data, ids, report_xml.report_type)
639                 rml_dom = self.preprocess_rml(etree.XML(rml),report_type)
640                 create_doc = self.generators[report_type]
641                 odt = create_doc(rml_dom,rml_parser.localcontext)
642                 if report_xml.header:
643                     rml_parser._add_header(odt)
644                 odt = etree.tostring(odt, encoding='utf-8',
645                                      xml_declaration=True)
646                 sxw_contents['styles.xml'] = odt
647             finally:
648                 rml_file.close()
649
650         #created empty zip writing sxw contents to avoid duplication
651         sxw_out = StringIO.StringIO()
652         sxw_out_zip = zipfile.ZipFile(sxw_out, mode='w')
653         sxw_template_zip = zipfile.ZipFile (sxw_io, 'r')
654         for item in sxw_template_zip.infolist():
655             if item.filename not in sxw_contents:
656                 buffer = sxw_template_zip.read(item.filename)
657                 sxw_out_zip.writestr(item.filename, buffer)
658         for item_filename, buffer in sxw_contents.iteritems():
659             sxw_out_zip.writestr(item_filename, buffer)
660         sxw_template_zip.close()
661         sxw_out_zip.close()
662         final_op = sxw_out.getvalue()
663         sxw_io.close()
664         sxw_out.close()
665         return final_op, mime_type
666
667     def create_single_html2html(self, cr, uid, ids, data, report_xml, context=None):
668         if not context:
669             context = {}
670         context = context.copy()
671         report_type = 'html'
672         context['parents'] = html_parents
673
674         html = report_xml.report_rml_content
675         html_parser = self.parser(cr, uid, self.name2, context=context)
676         html_parser.parents = html_parents
677         html_parser.tag = sxw_tag
678         objs = self.getObjects(cr, uid, ids, context)
679         html_parser.set_context(objs, data, ids, report_type)
680
681         html_dom =  etree.HTML(html)
682         html_dom = self.preprocess_rml(html_dom,'html2html')
683
684         create_doc = self.generators['html2html']
685         html = etree.tostring(create_doc(html_dom, html_parser.localcontext))
686
687         return html.replace('&amp;','&').replace('&lt;', '<').replace('&gt;', '>').replace('</br>',''), report_type
688
689     def create_single_mako2html(self, cr, uid, ids, data, report_xml, context=None):
690         mako_html = report_xml.report_rml_content
691         html_parser = self.parser(cr, uid, self.name2, context)
692         objs = self.getObjects(cr, uid, ids, context)
693         html_parser.set_context(objs, data, ids, 'html')
694         create_doc = self.generators['makohtml2html']
695         html = create_doc(mako_html,html_parser.localcontext)
696         return html,'html'
697
698
699 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: