Launchpad automatic translations update.
[odoo/odoo.git] / openerp / report / interface.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
22 import os
23 import re
24
25 from lxml import etree
26 import openerp.netsvc as netsvc
27 import openerp.pooler as pooler
28
29 import openerp.tools as tools
30 import openerp.modules
31 import print_xml
32 import render
33 import urllib
34
35 #
36 # coerce any type to a unicode string (to preserve non-ascii characters)
37 # and escape XML entities
38 #
39 def toxml(value):
40     unicode_value = tools.ustr(value)
41     return unicode_value.replace('&', '&amp;').replace('<','&lt;').replace('>','&gt;')
42
43 class report_int(netsvc.Service):
44     def __init__(self, name, audience='*'):
45         assert not self.exists(name), 'The report "%s" already exists!' % name
46         super(report_int, self).__init__(name, audience)
47         if name[0:7]<>'report.':
48             raise Exception, 'ConceptionError, bad report name, should start with "report."'
49         self.name = name
50         self.id = 0
51         self.name2 = '.'.join(name.split('.')[1:])
52         # TODO the reports have methods with a 'title' kwarg that is redundant with this attribute
53         self.title = None
54         #self.joinGroup('report')
55         self.exportMethod(self.create)
56
57     def create(self, cr, uid, ids, datas, context=None):
58         return False
59
60 """
61     Class to automatically build a document using the transformation process:
62         XML -> DATAS -> RML -> PDF
63                             -> HTML
64     using a XSL:RML transformation
65 """
66 class report_rml(report_int):
67     def __init__(self, name, table, tmpl, xsl):
68         super(report_rml, self).__init__(name)
69         self.table = table
70         self.internal_header=False
71         self.tmpl = tmpl
72         self.xsl = xsl
73         self.bin_datas = {}
74         self.generators = {
75             'pdf': self.create_pdf,
76             'html': self.create_html,
77             'raw': self.create_raw,
78             'sxw': self.create_sxw,
79             'txt': self.create_txt,
80             'odt': self.create_odt,
81             'html2html' : self.create_html2html,
82             'makohtml2html' :self.create_makohtml2html,
83         }
84
85     def create(self, cr, uid, ids, datas, context):
86         xml = self.create_xml(cr, uid, ids, datas, context)
87         xml = tools.ustr(xml).encode('utf8')
88         report_type = datas.get('report_type', 'pdf')
89         if report_type == 'raw':
90             return (xml,report_type)
91         rml = self.create_rml(cr, xml, uid, context)
92         pool = pooler.get_pool(cr.dbname)
93         ir_actions_report_xml_obj = pool.get('ir.actions.report.xml')
94         report_xml_ids = ir_actions_report_xml_obj.search(cr, uid, [('report_name', '=', self.name[7:])], context=context)
95         self.title = report_xml_ids and ir_actions_report_xml_obj.browse(cr,uid,report_xml_ids)[0].name or 'OpenERP Report'
96         create_doc = self.generators[report_type]
97         pdf = create_doc(rml, title=self.title)
98         return (pdf, report_type)
99
100     def create_xml(self, cr, uid, ids, datas, context=None):
101         if not context:
102             context={}
103         doc = print_xml.document(cr, uid, datas, {})
104         self.bin_datas.update( doc.bin_datas  or {})
105         doc.parse(self.tmpl, ids, self.table, context)
106         xml = doc.xml_get()
107         doc.close()
108         return self.post_process_xml_data(cr, uid, xml, context)
109
110     def post_process_xml_data(self, cr, uid, xml, context=None):
111
112         if not context:
113             context={}
114         # find the position of the 3rd tag
115         # (skip the <?xml ...?> and the "root" tag)
116         iter = re.finditer('<[^>]*>', xml)
117         i = iter.next()
118         i = iter.next()
119         pos_xml = i.end()
120
121         doc = print_xml.document(cr, uid, {}, {})
122         tmpl_path = openerp.modules.get_module_resource('base', 'report', 'corporate_defaults.xml')
123         doc.parse(tmpl_path, [uid], 'res.users', context)
124         corporate_header = doc.xml_get()
125         doc.close()
126
127         # find the position of the tag after the <?xml ...?> tag
128         iter = re.finditer('<[^>]*>', corporate_header)
129         i = iter.next()
130         pos_header = i.end()
131
132         return xml[:pos_xml] + corporate_header[pos_header:] + xml[pos_xml:]
133
134     #
135     # TODO: The translation doesn't work for "<tag t="1">textext<tag> tex</tag>text</tag>"
136     #
137     def create_rml(self, cr, xml, uid, context=None):
138         if self.tmpl=='' and not self.internal_header:
139             self.internal_header=True
140         if not context:
141             context={}
142         pool = pooler.get_pool(cr.dbname)
143         ir_translation_obj = pool.get('ir.translation')
144
145         # In some case we might not use xsl ...
146         if not self.xsl:
147             return xml
148
149         stylesheet_file = tools.file_open(self.xsl)
150         try:
151             stylesheet = etree.parse(stylesheet_file)
152             xsl_path, _ = os.path.split(self.xsl)
153             for import_child in stylesheet.findall('./import'):
154                 if 'href' in import_child.attrib:
155                     imp_file = import_child.get('href')
156                     _, imp_file = tools.file_open(imp_file, subdir=xsl_path, pathinfo=True)
157                     import_child.set('href', urllib.quote(str(imp_file)))
158                     imp_file.close()
159         finally:
160             stylesheet_file.close()
161
162         #TODO: get all the translation in one query. That means we have to:
163         # * build a list of items to translate,
164         # * issue the query to translate them,
165         # * (re)build/update the stylesheet with the translated items
166
167         def translate(doc, lang):
168             for node in doc.xpath('//*[@t]'):
169                 if not node.text:
170                     continue
171                 translation = ir_translation_obj._get_source(cr, uid, self.name2, 'xsl', lang, node.text)
172                 if translation:
173                     node.text = translation
174
175         if context.get('lang', False):
176             translate(stylesheet, context['lang'])
177
178         transform = etree.XSLT(stylesheet)
179         xml = etree.tostring(
180             transform(etree.fromstring(xml)))
181
182         return xml
183
184     def create_pdf(self, rml, localcontext = None, logo=None, title=None):
185         if not localcontext:
186            localcontext={}
187         localcontext.update({'internal_header':self.internal_header})
188         if logo:
189             self.bin_datas['logo'] = logo
190         else:
191             if 'logo' in self.bin_datas:
192                 del self.bin_datas['logo']
193         obj = render.rml(rml, localcontext, self.bin_datas, self._get_path(), title)
194         obj.render()
195         return obj.get()
196
197     def create_html(self, rml, localcontext = None, logo=None, title=None):
198         obj = render.rml2html(rml, localcontext, self.bin_datas)
199         obj.render()
200         return obj.get()
201
202     def create_txt(self, rml,localcontext, logo=None, title=None):
203         obj = render.rml2txt(rml, localcontext, self.bin_datas)
204         obj.render()
205         return obj.get().encode('utf-8')
206
207     def create_html2html(self, rml, localcontext = None, logo=None, title=None):
208         obj = render.html2html(rml, localcontext, self.bin_datas)
209         obj.render()
210         return obj.get()
211
212
213     def create_raw(self,rml, localcontext = None, logo=None, title=None):
214         obj = render.odt2odt(etree.XML(rml),localcontext)
215         obj.render()
216         return etree.tostring(obj.get())
217
218     def create_sxw(self,rml,localcontext = None):
219         obj = render.odt2odt(rml,localcontext)
220         obj.render()
221         return obj.get()
222
223     def create_odt(self,rml,localcontext = None):
224         obj = render.odt2odt(rml,localcontext)
225         obj.render()
226         return obj.get()
227
228     def create_makohtml2html(self,html,localcontext = None):
229         obj = render.makohtml2html(html,localcontext)
230         obj.render()
231         return obj.get()
232
233     def _get_path(self):
234         ret = []
235         ret.append(self.tmpl.replace(os.path.sep, '/').rsplit('/',1)[0]) # Same dir as the report rml
236         ret.append(tools.config['root_path'])
237         return ret
238
239 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: