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