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