diff --git a/trytond/report/report.py b/trytond/report/report.py index a1ac9bf49b..2340462e49 100644 --- a/trytond/report/report.py +++ b/trytond/report/report.py @@ -21,6 +21,11 @@ import datetime import md5 from _strptime import LocaleTime from base64 import decodestring +import relatorio +import tempfile +from genshi.filters import Translator +import traceback +from trytond.config import CONFIG MODULE_LIST = [] MODULE_CLASS_LIST = {} @@ -241,6 +246,34 @@ PARENTS = { } +class ReportFactory: + + def __call__(self, objects, **kwargs): + data = {} + data['objects'] = objects + data.update(kwargs) + return data + + +class TranslateFactory: + + def __init__(self, cursor, report_name, language, translation): + self.cursor = cursor + self.report_name = report_name + self.language = language + self.translation = translation + + def __call__(self, text): + res = self.translation._get_source(self.cursor, + self.report_name, 'odt', self.language, text) + if res: + return res + return text + + def set_language(self, language): + self.language = language + + class Report(object): _name = "" @@ -304,34 +337,33 @@ class Report(object): def parse(self, cursor, user, report, objects, datas, context): localcontext = {} localcontext['datas'] = datas - localcontext['objects'] = objects localcontext['user'] = self.pool.get('res.user').\ browse(cursor, user, user) - localcontext['_language_cache'] = {} + localcontext['formatLang'] = self.format_lang + localcontext['decodestring'] = decodestring + localcontext['time'] = time + localcontext['datetime'] = datetime localcontext.update(context) + + translate = TranslateFactory(cursor, self._name, + context.get('language', 'en_US'), + self.pool.get('ir.translation')) + localcontext['setLang'] = lambda language: translate.set_language(language) + if not report.report_content: - raise ExceptOSV('Error', 'Missing report file!') - #cStringIO difference: - #calling StringIO() with a string parameter creates a read-only object - content_io = StringIO.StringIO() - content_io.write(report.report_content) - content_z = zipfile.ZipFile(content_io, mode='r') - localcontext['content_z'] = content_z - content_xml = content_z.read('content.xml') - dom = xml.dom.minidom.parseString(content_xml) - node = dom.documentElement - self._parse_node(cursor, user, node, localcontext, context) + raise Exception('Error', 'Missing report file!') - style_z = zipfile.ZipFile(content_io, mode='r') - style_xml = content_z.read('styles.xml') - style_z.close() - dom_style = xml.dom.minidom.parseString(style_xml) - node_style = dom_style.documentElement - self._parse_node(cursor, user, node_style, localcontext, context) - content_z.close() + fd, path = tempfile.mkstemp(suffix='.odt', prefix='trytond') + os.write(fd, report.report_content) + os.close(fd) - pictures = [] if report.style_content: + pictures = [] + content_z = zipfile.ZipFile(path, mode='r') + style_xml = content_z.read('styles.xml') + dom_style = xml.dom.minidom.parseString(style_xml) + node_style = dom_style.documentElement + #cStringIO difference: #calling StringIO() with a string parameter creates a read-only object style2_io = StringIO.StringIO() @@ -346,7 +378,6 @@ class Report(object): style2_io.close() dom_style2 = xml.dom.minidom.parseString(style2_xml) node_style2 = dom_style2.documentElement - self._parse_node(cursor, user, node_style2, localcontext, context) style_header_node2 = self.find(node_style2, 'master-styles') style_header_node = self.find(node_style, 'master-styles') style_header_node.parentNode.replaceChild(style_header_node2, @@ -356,27 +387,28 @@ class Report(object): style_header_node.parentNode.replaceChild(style_header_node2, style_header_node) - content_z = zipfile.ZipFile(content_io, mode='a') - content_z.writestr('content.xml', - '' + \ - dom.documentElement.toxml('utf-8')) - content_z.writestr('styles.xml', - '' + \ - dom_style.documentElement.toxml('utf-8')) + content_z = zipfile.ZipFile(path, mode='a') - if localcontext.get('pictures'): - pictures.extend(localcontext['pictures']) - for file, picture in pictures: - content_z.writestr(file, picture) - content_z.close() - data = content_io.getvalue() - content_io.close() + content_z.writestr('styles.xml', + '' + \ + dom_style.documentElement.toxml('utf-8')) + + for file, picture in pictures: + content_z.writestr(file, picture) + content_z.close() + + translator = Translator(translate) + + rel_report = relatorio.Report(path, 'application/vnd.oasis.opendocument.text', + ReportFactory(), relatorio.MIMETemplateLoader()) + rel_report.filters.insert(0, translator) + data = rel_report(objects, **localcontext).render().getvalue() + os.remove(path) output_format = report.output_format.format if output_format == 'pdf': data = self.convert_pdf(data) return (output_format, data) - def convert_pdf(self, data): """ Convert report to PDF using OpenOffice.org. @@ -428,174 +460,6 @@ class Report(object): ExceptOSV('Error', 'Error converting to PDF') return res_data - def _parse_node(self, cursor, user, node, localcontext, context, - node_context = None): - if node_context is None: - node_context = {} - while True: - if node.hasChildNodes(): - node = node.firstChild - elif node.nextSibling: - node = node.nextSibling - else: - while node and not node.nextSibling: - node = node.parentNode - if not node: - break - node = node.nextSibling - if node in node_context: - localcontext.update(node_context[node]) - if node.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE): - res = self._parse_text(cursor, user, node, localcontext, context, - node_context) - if isinstance(res, dom.Node): - node = res - if node.nodeType == node.ELEMENT_NODE: - if node.nodeName == "draw:frame": - self._parse_draw_frame(cursor, user, node, localcontext, context,) - - def _parse_draw_frame(self, cursor, user, node, localcontext, context): - """When a picture name in the template document contain - "replaceWith(content, extension)", this piture is replaced - with the content, a file name is generated automatically with - the given extension - """ - if "replaceWith" not in node.attributes["draw:name"].nodeValue: - return - localcontext.setdefault('pictures', []) - ctx = localcontext.copy() - ctx.update(context) - ctx["replaceWith"] = lambda x,y: (x,y) - try: - res = eval(node.attributes["draw:name"].nodeValue, ctx) - except: - Logger().notify_channel('report', LOG_ERROR, - 'Error on eval "%s"' % node.nodeValue) - raise - - if isinstance(res, tuple): - data = decodestring(res[0]) - filename = 'Pictures/%s.%s'% (md5.new(data).hexdigest(), res[1]) - localcontext['pictures'].append((filename,data)) - for child_node in node.childNodes: - if child_node.nodeName == "draw:image": - child_node.attributes["xlink:href"].nodeValue = filename - continue - - def _parse_text(self, cursor, user, node, localcontext, context, - node_context): - if node.parentNode.tagName == 'text:text-input': - ctx = localcontext.copy() - ctx.update(context) - ctx['RepeatIn'] = lambda lst, name, parents=False: \ - self.repeat_in(lst, name, parents=parents, - tnode=node, node_context=node_context) - ctx['setTag'] = lambda oldtag, newtag, attrs=None: \ - self.set_tag(oldtag, newtag, attrs=attrs, tnode=node) - ctx['removeParentNode'] = lambda tag='p': \ - self.remove_parent_node(tag, tnode=node) - ctx['setLang'] = lambda lang: \ - self.set_lang(lang, localcontext) - ctx['formatLang'] = lambda value, digits=2, date=False: \ - self.format_lang(value, digits=digits, date=date, - localcontext=localcontext) - ctx['time'] = time - ctx['datetime'] = datetime - try: - res = eval(node.nodeValue, ctx) - except: - Logger().notify_channel('report', LOG_ERROR, - 'Error on eval "%s"' % node.nodeValue) - raise - if isinstance(res, bool): - res = '' - if hasattr(res, '__str__'): - res = res.__str__() - if isinstance(res, basestring): - if '\n' in res: - parent2 = node.parentNode.parentNode - parent = node.parentNode - first = True - newnode = None - for val in res.decode('utf-8').split('\n'): - if first: - newnode = node - first = False - else: - newnode = node.cloneNode(1) - newnode.nodeValue = val - parent2.insertBefore(newnode, parent) - newnode = node.parentNode.cloneNode(1) - newnode.nodeType = newnode.ELEMENT_NODE - newnode.tagName = 'text:line-break' - newnode.firstChild.nodeValue = '' - if newnode.getAttribute('text:style-name'): - newnode.removeAttribute('text:style-name') - parent2.insertBefore(newnode, parent) - parent2.removeChild(parent) - if newnode: - parent2.removeChild(newnode) - else: - node.nodeValue = res.decode('utf-8') - node.parentNode.parentNode.replaceChild(node, node.parentNode) - return res - if 'language' in localcontext: - lang = localcontext['language'] - text = node.nodeValue - if lang and text and not text.isspace(): - translation_obj = self.pool.get('ir.translation') - new_text = translation_obj._get_source(cursor, - self._name, 'odt', lang, text) - if new_text: - node.nodeValue = new_text.decode('utf-8') - return None - - def repeat_in(self, lst, name, parents=False, tnode=None, node_context=None): - node = self.find_parent(tnode, parents or PARENTS) - - pnode = node.parentNode - nextnode = node.nextSibling - pnode.removeChild(node) - tnode.parentNode.parentNode.removeChild(tnode.parentNode) - - if not lst: - return pnode - for i in range(len(lst)): - newnode = node.cloneNode(1) - if nextnode: - pnode.insertBefore(newnode, nextnode) - else: - pnode.appendChild(newnode) - node_context[newnode] = {name: lst[i]} - return pnode - - def set_tag(self, oldtag, newtag, attrs=None, tnode=None): - if attrs is None: - attrs = {} - node = self.find_parent(tnode, [oldtag]) - if node: - node.tagName = newtag - for key, val in attrs.items(): - node.setAttribute(key, val) - return None - - def remove_parent_node(self, tag='p', tnode=None): - node = self.find_parent(tnode, [tag]) - if node: - pnode = node.parentNode - pnode.removeChild(node) - return pnode - - def find_parent(self, node, parents): - while True: - if not node.parentNode: - return None - node = node.parentNode - if node.nodeType == node.ELEMENT_NODE \ - and node.localName in parents: - break - return node - def find(self, tnode, tag): for node in tnode.childNodes: if node.nodeType == node.ELEMENT_NODE \ @@ -606,43 +470,20 @@ class Report(object): return res return None - def set_lang(self, lang, localcontext): - localcontext['language'] = lang - _language_cache = localcontext['_language_cache'] - for obj in localcontext['objects']: - obj._context['language'] = lang - for table in obj._cache: - for obj_id in obj._cache[table]: - _language_cache.setdefault( - obj._context['language'], {}).setdefault( - table, {}).update( - obj._cache[table][obj_id]) - if lang in _language_cache \ - and table in _language_cache[lang] \ - and obj_id in _language_cache[lang][table]: - obj._cache[table][obj_id] = \ - _language_cache[lang][table][obj_id] - else: - obj._cache[table][obj_id] = {'id': obj_id} - return '' - - def format_lang(self, value, digits=2, date=False, localcontext=None): - if localcontext is None: - localcontext = {} + def format_lang(self, value, digits=2, date=False, language='en_US'): encoding = locale.getdefaultlocale()[1] if encoding == 'utf': encoding = 'UTF-8' if encoding == 'cp1252': encoding = '1252' - lang = localcontext.get('language', False) or 'en_US' try: if os.name == 'nt': - lang = _LOCALE2WIN32.get(lang, lang) - locale.setlocale(locale.LC_ALL, lang + '.' + encoding) + language = _LOCALE2WIN32.get(language, language) + locale.setlocale(locale.LC_ALL, language + '.' + encoding) except Exception: Logger().notify_channel('web-service', LOG_ERROR, 'Report %s: unable to set locale "%s"' % \ - (self._name, lang + '.' + encoding)) + (self._name, language + '.' + encoding)) if date: if isinstance(value, time.struct_time): locale_format = LocaleTime().LC_date.replace('%y', '%Y')