1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
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.
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.
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/>.
20 ##############################################################################
25 from lxml import etree
26 import openerp.netsvc as netsvc
27 import openerp.pooler as pooler
29 import openerp.tools as tools
30 import openerp.modules
36 # coerce any type to a unicode string (to preserve non-ascii characters)
37 # and escape XML entities
40 unicode_value = tools.ustr(value)
41 return unicode_value.replace('&', '&').replace('<','<').replace('>','>')
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 not name.startswith('report.'):
48 raise Exception('ConceptionError, bad report name, should start with "report."')
51 self.name2 = '.'.join(name.split('.')[1:])
52 # TODO the reports have methods with a 'title' kwarg that is redundant with this attribute
55 def create(self, cr, uid, ids, datas, context=None):
58 class report_rml(report_int):
60 Automatically builds a document using the transformation process:
61 XML -> DATAS -> RML -> PDF -> HTML
62 using a XSL:RML transformation
64 def __init__(self, name, table, tmpl, xsl):
65 super(report_rml, self).__init__(name)
67 self.internal_header=False
72 'pdf': self.create_pdf,
73 'html': self.create_html,
74 'raw': self.create_raw,
75 'sxw': self.create_sxw,
76 'txt': self.create_txt,
77 'odt': self.create_odt,
78 'html2html' : self.create_html2html,
79 'makohtml2html' :self.create_makohtml2html,
82 def create(self, cr, uid, ids, datas, context):
83 xml = self.create_xml(cr, uid, ids, datas, context)
84 xml = tools.ustr(xml).encode('utf8')
85 report_type = datas.get('report_type', 'pdf')
86 if report_type == 'raw':
87 return xml, report_type
88 rml = self.create_rml(cr, xml, uid, context)
89 pool = pooler.get_pool(cr.dbname)
90 ir_actions_report_xml_obj = pool.get('ir.actions.report.xml')
91 report_xml_ids = ir_actions_report_xml_obj.search(cr, uid, [('report_name', '=', self.name[7:])], context=context)
92 self.title = report_xml_ids and ir_actions_report_xml_obj.browse(cr,uid,report_xml_ids)[0].name or 'OpenERP Report'
93 create_doc = self.generators[report_type]
94 pdf = create_doc(rml, title=self.title)
95 return pdf, report_type
97 def create_xml(self, cr, uid, ids, datas, context=None):
100 doc = print_xml.document(cr, uid, datas, {})
101 self.bin_datas.update( doc.bin_datas or {})
102 doc.parse(self.tmpl, ids, self.table, context)
105 return self.post_process_xml_data(cr, uid, xml, context)
107 def post_process_xml_data(self, cr, uid, xml, context=None):
111 # find the position of the 3rd tag
112 # (skip the <?xml ...?> and the "root" tag)
113 iter = re.finditer('<[^>]*>', xml)
118 doc = print_xml.document(cr, uid, {}, {})
119 tmpl_path = openerp.modules.get_module_resource('base', 'report', 'corporate_defaults.xml')
120 doc.parse(tmpl_path, [uid], 'res.users', context)
121 corporate_header = doc.xml_get()
124 # find the position of the tag after the <?xml ...?> tag
125 iter = re.finditer('<[^>]*>', corporate_header)
129 return xml[:pos_xml] + corporate_header[pos_header:] + xml[pos_xml:]
132 # TODO: The translation doesn't work for "<tag t="1">textext<tag> tex</tag>text</tag>"
134 def create_rml(self, cr, xml, uid, context=None):
135 if self.tmpl=='' and not self.internal_header:
136 self.internal_header=True
139 pool = pooler.get_pool(cr.dbname)
140 ir_translation_obj = pool.get('ir.translation')
142 # In some case we might not use xsl ...
146 stylesheet_file = tools.file_open(self.xsl)
148 stylesheet = etree.parse(stylesheet_file)
149 xsl_path, _ = os.path.split(self.xsl)
150 for import_child in stylesheet.findall('./import'):
151 if 'href' in import_child.attrib:
152 imp_file = import_child.get('href')
153 _, imp_file = tools.file_open(imp_file, subdir=xsl_path, pathinfo=True)
154 import_child.set('href', urllib.quote(str(imp_file)))
157 stylesheet_file.close()
159 #TODO: get all the translation in one query. That means we have to:
160 # * build a list of items to translate,
161 # * issue the query to translate them,
162 # * (re)build/update the stylesheet with the translated items
164 def translate(doc, lang):
165 translate_aux(doc, lang, False)
167 def translate_aux(doc, lang, t):
169 t = t or node.get("t")
174 text = node.text.strip().replace('\n',' ')
176 tail = node.tail.strip().replace('\n',' ')
178 translation1 = ir_translation_obj._get_source(cr, uid, self.name2, 'xsl', lang, text)
180 node.text = node.text.replace(text, translation1)
182 translation2 = ir_translation_obj._get_source(cr, uid, self.name2, 'xsl', lang, tail)
184 node.tail = node.tail.replace(tail, translation2)
185 translate_aux(node, lang, t)
187 if context.get('lang', False):
188 translate(stylesheet.iter(), context['lang'])
190 transform = etree.XSLT(stylesheet)
191 xml = etree.tostring(
192 transform(etree.fromstring(xml)))
196 def create_pdf(self, rml, localcontext = None, logo=None, title=None):
199 localcontext.update({'internal_header':self.internal_header})
201 self.bin_datas['logo'] = logo
203 if 'logo' in self.bin_datas:
204 del self.bin_datas['logo']
205 obj = render.rml(rml, localcontext, self.bin_datas, self._get_path(), title)
209 def create_html(self, rml, localcontext = None, logo=None, title=None):
210 obj = render.rml2html(rml, localcontext, self.bin_datas)
214 def create_txt(self, rml,localcontext, logo=None, title=None):
215 obj = render.rml2txt(rml, localcontext, self.bin_datas)
217 return obj.get().encode('utf-8')
219 def create_html2html(self, rml, localcontext = None, logo=None, title=None):
220 obj = render.html2html(rml, localcontext, self.bin_datas)
225 def create_raw(self,rml, localcontext = None, logo=None, title=None):
226 obj = render.odt2odt(etree.XML(rml),localcontext)
228 return etree.tostring(obj.get())
230 def create_sxw(self,rml,localcontext = None):
231 obj = render.odt2odt(rml,localcontext)
235 def create_odt(self,rml,localcontext = None):
236 obj = render.odt2odt(rml,localcontext)
240 def create_makohtml2html(self,html,localcontext = None):
241 obj = render.makohtml2html(html,localcontext)
247 self.tmpl.replace(os.path.sep, '/').rsplit('/', 1)[0],
249 tools.config['root_path']
252 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: