Merge origin/master into mdv-gpl3-py26
[openerp-server.git] / bin / report / report_sxw.py
blob0a0fce2845aa6241a98575b5572226f57a9d7012
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 # $Id$
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
23 from lxml import etree
24 import StringIO
25 import cStringIO
26 import base64
27 import copy
28 import locale
29 import mx.DateTime
30 import os
31 import re
32 import time
33 from interface import report_rml
34 import preprocess
35 import ir
36 import netsvc
37 import osv
38 import pooler
39 import tools
40 import warnings
41 import zipfile
42 import common
44 DT_FORMAT = '%Y-%m-%d'
45 DHM_FORMAT = '%Y-%m-%d %H:%M:%S'
46 HM_FORMAT = '%H:%M:%S'
48 rml_parents = {
49 'tr':1,
50 'li':1,
51 'story': 0,
52 'section': 0
55 rml_tag="para"
57 sxw_parents = {
58 'table-row': 1,
59 'list-item': 1,
60 'body': 0,
61 'section': 0,
64 html_parents = {
65 'tr' : 1,
66 'body' : 0,
67 'div' : 0
69 sxw_tag = "p"
71 rml2sxw = {
72 'para': 'p',
75 class _format(object):
76 def set_value(self, cr, uid, name, object, field, lang_obj):
77 self.object = object
78 self._field = field
79 self.name = name
80 self.lang_obj = lang_obj
82 class _float_format(float, _format):
83 def __init__(self,value):
84 super(_float_format, self).__init__()
85 self.val = value
87 def __str__(self):
88 digits = 2
89 if hasattr(self,'_field') and hasattr(self._field, 'digits') and self._field.digits:
90 digits = self._field.digits[1]
91 if hasattr(self, 'lang_obj'):
92 return self.lang_obj.format('%.' + str(digits) + 'f', self.name, True)
93 return self.val
95 class _int_format(int, _format):
96 def __init__(self,value):
97 super(_int_format, self).__init__()
98 self.val = value and str(value) or str(0)
100 def __str__(self):
101 if hasattr(self,'lang_obj'):
102 return self.lang_obj.format('%.d', self.name, True)
103 return self.val
105 class _date_format(str, _format):
106 def __init__(self,value):
107 super(_date_format, self).__init__()
108 self.val = value and str(value) or ''
110 def __str__(self):
111 if self.val:
112 if hasattr(self,'name') and (self.name):
113 date = mx.DateTime.strptime(self.name,DT_FORMAT)
114 return date.strftime(self.lang_obj.date_format)
115 return self.val
117 class _dttime_format(str, _format):
118 def __init__(self,value):
119 super(_dttime_format, self).__init__()
120 self.val = value and str(value) or ''
122 def __str__(self):
123 if self.val:
124 if hasattr(self,'name') and self.name:
125 datetime = mx.DateTime.strptime(self.name,DHM_FORMAT)
126 return datetime.strftime(self.lang_obj.date_format+ " " + self.lang_obj.time_format)
127 return self.val
130 _fields_process = {
131 'float': _float_format,
132 'date': _date_format,
133 'integer': _int_format,
134 'datetime' : _dttime_format
138 # Context: {'node': node.dom}
140 class browse_record_list(list):
141 def __init__(self, lst, context):
142 super(browse_record_list, self).__init__(lst)
143 self.context = context
145 def __getattr__(self, name):
146 res = browse_record_list([getattr(x,name) for x in self], self.context)
147 return res
149 def __str__(self):
150 return "browse_record_list("+str(len(self))+")"
152 class rml_parse(object):
153 def __init__(self, cr, uid, name, parents=rml_parents, tag=rml_tag, context=None):
154 if not context:
155 context={}
156 self.cr = cr
157 self.uid = uid
158 self.pool = pooler.get_pool(cr.dbname)
159 user = self.pool.get('res.users').browse(cr, uid, uid)
160 self.localcontext = {
161 'user': user,
162 'company': user.company_id,
163 'repeatIn': self.repeatIn,
164 'setLang': self.setLang,
165 'setTag': self.setTag,
166 'removeParentNode': self.removeParentNode,
167 'format': self.format,
168 'formatLang': self.formatLang,
169 'logo' : user.company_id.logo,
170 'lang' : user.company_id.partner_id.lang,
171 'translate' : self._translate,
172 'setHtmlImage' : self.set_html_image
174 self.localcontext.update(context)
175 self.rml_header = user.company_id.rml_header
176 self.rml_header2 = user.company_id.rml_header2
177 self.logo = user.company_id.logo
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('(\[\[.+?\]\])')
188 def setTag(self, oldtag, newtag, attrs=None):
189 return newtag, attrs
191 def format(self, text, oldtag=None):
192 return text
194 def removeParentNode(self, tag=None):
195 raise Exception('Skip')
197 def set_html_image(self,id,model=None,field=None,context=None):
198 if not id :
199 return ''
200 if not model:
201 model = 'ir.attachment'
202 try :
203 id = int(id)
204 res = self.pool.get(model).read(self.cr,self.uid,id)
205 if field :
206 return res[field]
207 elif model =='ir.attachment' :
208 return res['datas']
209 else :
210 return ''
211 except Exception,e:
212 return ''
214 def setLang(self, lang):
215 if not lang or self.default_lang.has_key(lang):
216 if not lang:
217 key = 'en_US'
218 self.localcontext['lang'] = lang
219 self.lang_dict_called = False
220 for obj in self.objects:
221 obj._context['lang'] = lang
222 for table in obj._cache:
223 for id in obj._cache[table]:
224 self._lang_cache.setdefault(obj._context['lang'], {}).setdefault(table,
225 {}).update(obj._cache[table][id])
226 if lang in self._lang_cache \
227 and table in self._lang_cache[lang] \
228 and id in self._lang_cache[lang][table]:
229 obj._cache[table][id] = self._lang_cache[lang][table][id]
230 else:
231 obj._cache[table][id] = {'id': id}
233 def _get_lang_dict(self):
234 pool_lang = self.pool.get('res.lang')
235 lang = self.localcontext.get('lang', 'en_US') or 'en_US'
236 lang_ids = pool_lang.search(self.cr,self.uid,[('code','=',lang)])[0]
237 lang_obj = pool_lang.browse(self.cr,self.uid,lang_ids)
238 self.lang_dict.update({'lang_obj':lang_obj,'date_format':lang_obj.date_format,'time_format':lang_obj.time_format})
239 self.default_lang[lang] = self.lang_dict.copy()
240 return True
242 def formatLang(self, value, digits=2, date=False,date_time=False, grouping=True, monetary=False, currency=None):
243 if isinstance(value, (str, unicode)) and not value:
244 return ''
245 if not self.lang_dict_called:
246 self._get_lang_dict()
247 self.lang_dict_called = True
249 if date or date_time:
250 if not str(value):
251 return ''
252 date_format = self.lang_dict['date_format']
253 parse_format = DT_FORMAT
254 if date_time:
255 date_format = date_format + " " + self.lang_dict['time_format']
256 parse_format = DHM_FORMAT
258 # filtering time.strftime('%Y-%m-%d')
259 if type(value) == type(''):
260 parse_format = DHM_FORMAT
261 if (not date_time):
262 return str(value)
264 if not isinstance(value, time.struct_time):
265 try:
266 date = mx.DateTime.strptime(str(value),parse_format)
267 except:# sometimes it takes converted values into value, so we dont need conversion.
268 return str(value)
269 else:
270 date = mx.DateTime.DateTime(*(value.timetuple()[:6]))
271 return date.strftime(date_format)
272 return self.lang_dict['lang_obj'].format('%.' + str(digits) + 'f', value, grouping=grouping, monetary=monetary)
274 def repeatIn(self, lst, name,nodes_parent=False):
275 ret_lst = []
276 for id in lst:
277 ret_lst.append({name:id})
278 return ret_lst
280 def _translate(self,text):
281 lang = self.localcontext['lang']
282 if lang and text and not text.isspace():
283 transl_obj = self.pool.get('ir.translation')
284 piece_list = self._transl_regex.split(text)
285 for pn in range(len(piece_list)):
286 if not self._transl_regex.match(piece_list[pn]):
287 source_string = piece_list[pn].replace('\n', ' ').strip()
288 if len(source_string):
289 translated_string = transl_obj._get_source(self.cr, self.uid, self.name, 'rml', lang, source_string)
290 if translated_string:
291 piece_list[pn] = piece_list[pn].replace(source_string, translated_string)
292 text = ''.join(piece_list)
293 return text
295 def _add_header(self, rml_dom, header=1):
296 if header==2:
297 rml_head = self.rml_header2
298 else:
299 rml_head = self.rml_header
300 if self.logo and (rml_head.find('company.logo')<0 or rml_head.find('<image')<0) and rml_head.find('<!--image')<0:
301 rml_head = rml_head.replace('<pageGraphics>','''<pageGraphics> <image x="10" y="26cm" height="70" width="90" >[[company.logo]] </image> ''')
302 if not self.logo and rml_head.find('company.logo')>=0:
303 rml_head = rml_head.replace('<image','<!--image')
304 rml_head = rml_head.replace('</image>','</image-->')
305 head_dom = etree.XML(rml_head)
306 for tag in head_dom.getchildren():
307 found = rml_dom.find('.//'+tag.tag)
308 if found is not None:
309 if tag.get('position'):
310 found.append(tag)
311 else :
312 found.getparent().replace(found,tag)
313 return True
315 def set_context(self, objects, data, ids, report_type = None):
316 self.localcontext['data'] = data
317 self.localcontext['objects'] = objects
318 self.datas = data
319 self.ids = ids
320 self.objects = objects
321 if report_type:
322 if report_type=='odt' :
323 self.localcontext.update({'name_space' :common.odt_namespace})
324 else:
325 self.localcontext.update({'name_space' :common.sxw_namespace})
327 class report_sxw(report_rml, preprocess.report):
328 def __init__(self, name, table, rml=False, parser=rml_parse, header=True, store=False):
329 report_rml.__init__(self, name, table, rml, '')
330 self.name = name
331 self.parser = parser
332 self.header = header
333 self.store = store
335 def getObjects(self, cr, uid, ids, context):
336 table_obj = pooler.get_pool(cr.dbname).get(self.table)
337 return table_obj.browse(cr, uid, ids, list_class=browse_record_list, context=context, fields_process=_fields_process)
339 def create(self, cr, uid, ids, data, context=None):
340 pool = pooler.get_pool(cr.dbname)
341 ir_obj = pool.get('ir.actions.report.xml')
342 report_xml_ids = ir_obj.search(cr, uid,
343 [('report_name', '=', self.name[7:])], context=context)
344 if report_xml_ids:
345 report_xml = ir_obj.browse(cr, uid, report_xml_ids[0], context=context)
346 else:
347 title = ''
348 rml = tools.file_open(self.tmpl, subdir=None).read()
349 report_type= data.get('report_type', 'pdf')
350 class a(object):
351 def __init__(self, *args, **argv):
352 for key,arg in argv.items():
353 setattr(self, key, arg)
354 report_xml = a(title=title, report_type=report_type, report_rml_content=rml, name=title, attachment=False, header=self.header)
355 report_type = report_xml.report_type
356 if report_type in ['sxw','odt']:
357 fnct = self.create_source_odt
358 elif report_type in ['pdf','raw','txt','html']:
359 fnct = self.create_source_pdf
360 elif report_type=='html2html':
361 fnct = self.create_source_html2html
362 else:
363 raise Exception('Unknown Report Type: '+report_type)
364 return fnct(cr, uid, ids, data, report_xml, context)
366 def create_source_odt(self, cr, uid, ids, data, report_xml, context=None):
367 return self.create_single_odt(cr, uid, ids, data, report_xml, context or {})
369 def create_source_html2html(self, cr, uid, ids, data, report_xml, context=None):
370 return self.create_single_html2html(cr, uid, ids, data, report_xml, context or {})
372 def create_source_pdf(self, cr, uid, ids, data, report_xml, context=None):
373 if not context:
374 context={}
375 pool = pooler.get_pool(cr.dbname)
376 attach = report_xml.attachment
377 if attach:
378 objs = self.getObjects(cr, uid, ids, context)
379 results = []
380 for obj in objs:
381 aname = eval(attach, {'object':obj, 'time':time})
382 result = False
383 if report_xml.attachment_use and aname and context.get('attachment_use', True):
384 aids = pool.get('ir.attachment').search(cr, uid, [('datas_fname','=',aname+'.pdf'),('res_model','=',self.table),('res_id','=',obj.id)])
385 if aids:
386 brow_rec = pool.get('ir.attachment').browse(cr, uid, aids[0])
387 if not brow_rec.datas:
388 continue
389 d = base64.decodestring(brow_rec.datas)
390 results.append((d,'pdf'))
391 continue
392 result = self.create_single_pdf(cr, uid, [obj.id], data, report_xml, context)
393 try:
394 if aname:
395 name = aname+'.'+result[1]
396 pool.get('ir.attachment').create(cr, uid, {
397 'name': aname,
398 'datas': base64.encodestring(result[0]),
399 'datas_fname': name,
400 'res_model': self.table,
401 'res_id': obj.id,
402 }, context=context
404 cr.commit()
405 except Exception,e:
406 import traceback, sys
407 tb_s = reduce(lambda x, y: x+y, traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback))
408 netsvc.Logger().notifyChannel('report', netsvc.LOG_ERROR,str(e))
409 results.append(result)
410 if results:
411 if results[0][1]=='pdf':
412 from pyPdf import PdfFileWriter, PdfFileReader
413 output = PdfFileWriter()
414 for r in results:
415 reader = PdfFileReader(cStringIO.StringIO(r[0]))
416 for page in range(reader.getNumPages()):
417 output.addPage(reader.getPage(page))
418 s = cStringIO.StringIO()
419 output.write(s)
420 return s.getvalue(), results[0][1]
421 return self.create_single_pdf(cr, uid, ids, data, report_xml, context)
423 def create_single_pdf(self, cr, uid, ids, data, report_xml, context={}):
424 logo = None
425 context = context.copy()
426 title = report_xml.name
427 rml = report_xml.report_rml_content
428 rml_parser = self.parser(cr, uid, self.name2, context)
429 objs = self.getObjects(cr, uid, ids, context)
430 rml_parser.set_context(objs, data, ids, report_xml.report_type)
431 processed_rml = self.preprocess_rml(etree.XML(rml),report_xml.report_type)
432 if report_xml.header:
433 rml_parser._add_header(processed_rml)
434 if rml_parser.logo:
435 logo = base64.decodestring(rml_parser.logo)
436 create_doc = self.generators[report_xml.report_type]
437 pdf = create_doc(etree.tostring(processed_rml),rml_parser.localcontext,logo,title.encode('utf8'))
438 return (pdf, report_xml.report_type)
440 def create_single_odt(self, cr, uid, ids, data, report_xml, context={}):
441 context = context.copy()
442 report_type = report_xml.report_type
443 context['parents'] = sxw_parents
444 sxw_io = StringIO.StringIO(report_xml.report_sxw_content)
445 sxw_z = zipfile.ZipFile(sxw_io, mode='r')
446 rml = sxw_z.read('content.xml')
447 meta = sxw_z.read('meta.xml')
448 sxw_z.close()
450 rml_parser = self.parser(cr, uid, self.name2, context)
451 rml_parser.parents = sxw_parents
452 rml_parser.tag = sxw_tag
453 objs = self.getObjects(cr, uid, ids, context)
454 rml_parser.set_context(objs, data, ids,report_xml.report_type)
456 rml_dom_meta = node = etree.XML(meta)
457 elements = node.findall(rml_parser.localcontext['name_space']["meta"]+"user-defined")
458 for pe in elements:
459 if pe.get(rml_parser.localcontext['name_space']["meta"]+"name"):
460 if pe.get(rml_parser.localcontext['name_space']["meta"]+"name") == "Info 3":
461 pe.getchildren()[0].text=data['id']
462 if pe.get(rml_parser.localcontext['name_space']["meta"]+"name") == "Info 4":
463 pe.getchildren()[0].text=data['model']
464 meta = etree.tostring(rml_dom_meta)
466 rml_dom = etree.XML(rml)
467 body = rml_dom.getchildren()[-1]
468 elements = []
469 key1 = rml_parser.localcontext['name_space']["text"]+"p"
470 key2 = rml_parser.localcontext['name_space']["text"]+"drop-down"
471 for n in rml_dom.iterdescendants():
472 if n.tag == key1:
473 elements.append(n)
474 if report_type == 'odt':
475 for pe in elements:
476 e = pe.findall(key2)
477 for de in e:
478 pp=de.getparent()
479 if de.text or de.tail:
480 pe.text = de.text or de.tail
481 for cnd in de.getchildren():
482 if cnd.text or cnd.tail:
483 if pe.text:
484 pe.text += cnd.text or cnd.tail
485 else:
486 pe.text = cnd.text or cnd.tail
487 pp.remove(de)
488 else:
489 for pe in elements:
490 e = pe.findall(key2)
491 for de in e:
492 pp = de.getparent()
493 if de.text or de.tail:
494 pe.text = de.text or de.tail
495 for cnd in de:
496 text = cnd.get("{http://openoffice.org/2000/text}value",False)
497 if text:
498 if pe.text and text.startswith('[['):
499 pe.text += text
500 elif text.startswith('[['):
501 pe.text = text
502 if de.getparent():
503 pp.remove(de)
505 rml_dom = self.preprocess_rml(rml_dom,report_type)
506 create_doc = self.generators[report_type]
507 odt = etree.tostring(create_doc(rml_dom, rml_parser.localcontext))
508 sxw_z = zipfile.ZipFile(sxw_io, mode='a')
509 sxw_z.writestr('content.xml', "<?xml version='1.0' encoding='UTF-8'?>" + \
510 odt)
511 sxw_z.writestr('meta.xml', "<?xml version='1.0' encoding='UTF-8'?>" + \
512 meta)
514 if report_xml.header:
515 #Add corporate header/footer
516 rml = tools.file_open(os.path.join('base', 'report', 'corporate_%s_header.xml' % report_type)).read()
517 rml_parser = self.parser(cr, uid, self.name2, context)
518 rml_parser.parents = sxw_parents
519 rml_parser.tag = sxw_tag
520 objs = self.getObjects(cr, uid, ids, context)
521 rml_parser.set_context(objs, data, ids, report_xml.report_type)
522 rml_dom = self.preprocess_rml(etree.XML(rml),report_type)
523 create_doc = self.generators[report_type]
524 odt = create_doc(rml_dom,rml_parser.localcontext)
525 if report_xml.header:
526 rml_parser._add_header(odt)
527 odt = etree.tostring(odt)
528 sxw_z.writestr('styles.xml',"<?xml version='1.0' encoding='UTF-8'?>" + \
529 odt)
530 sxw_z.close()
531 final_op = sxw_io.getvalue()
532 sxw_io.close()
533 return (final_op, report_type)
535 def create_single_html2html(self, cr, uid, ids, data, report_xml, context={}):
536 context = context.copy()
537 report_type = 'html'
538 context['parents'] = html_parents
540 html = report_xml.report_rml_content
541 html_parser = self.parser(cr, uid, self.name2, context)
542 html_parser.parents = html_parents
543 html_parser.tag = sxw_tag
544 objs = self.getObjects(cr, uid, ids, context)
545 html_parser.set_context(objs, data, ids, report_type)
547 html_dom = etree.HTML(html)
548 html_dom = self.preprocess_rml(html_dom,'html2html')
550 create_doc = self.generators['html2html']
551 html = etree.tostring(create_doc(html_dom, html_parser.localcontext))
553 return (html.replace('&amp;','&').replace('&lt;', '<').replace('&gt;', '>').replace('</br>',''), report_type)