5e7bb5cf4e944f8068ea3d420ce430257cc59e48
[odoo/odoo.git] / bin / report / render / rml2html / rml2html.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 # Copyright (C) 2005, Fabien Pinckaers, UCL, FSA
24 #
25 # This library is free software; you can redistribute it and/or
26 # modify it under the terms of the GNU Lesser General Public
27 # License as published by the Free Software Foundation; either
28 # version 2.1 of the License, or (at your option) any later version.
29 #
30 # This library is distributed in the hope that it will be useful,
31 # but WITHOUT ANY WARRANTY; without even the implied warranty of
32 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
33 # Lesser General Public License for more details.
34 #
35 # You should have received a copy of the GNU Lesser General Public
36 # License along with this library; if not, write to the Free Software
37 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
38
39 import sys
40 import cStringIO
41 from lxml import etree
42 import copy
43 import utils
44 from report.render.rml2pdf import utils
45
46 class _flowable(object):
47     def __init__(self, template, doc, localcontext = None):
48         self._tags = {
49             'title': self._tag_title,
50             'spacer': self._tag_spacer,
51             'para': self._tag_para,
52             'section':self._section,
53             'nextFrame': self._tag_next_frame,
54             'blockTable': self._tag_table,
55             'pageBreak': self._tag_page_break,
56             'setNextTemplate': self._tag_next_template,
57         }
58         self.template = template
59         self.doc = doc
60         self.localcontext = localcontext
61         self._cache = {}
62
63     def _tag_page_break(self, node):
64         return '<br/>'*3
65
66     def _tag_next_template(self, node):
67         return ''
68
69     def _tag_next_frame(self, node):
70         result=self.template.frame_stop()
71         result+='<br/>'
72         result+=self.template.frame_start()
73         return result
74
75     def _tag_title(self, node):
76         node.tag='h1'
77         return etree.tostring(node)
78
79     def _tag_spacer(self, node):
80         length = 1+int(utils.unit_get(node.get('length')))/35
81         return "<br/>"*length
82
83     def _tag_table(self, node):
84         new_node = copy.deepcopy(node)
85         for child in new_node:
86             new_node.remove(child)
87         new_node.tag = 'table'
88         def process(node,new_node):
89             for child in utils._child_get(node,self):
90                 new_child = copy.deepcopy(child)
91                 new_node.append(new_child)
92                 if len(child):
93                     for n in new_child:
94                         new_child.remove(n)
95                     process(child, new_child)
96                 else:
97                     new_child.text  = utils._process_text(self, child.text)
98                     new_child.tag = 'p'
99                     try:
100                         if new_child.get('style').find('terp_tblheader')!= -1:
101                             new_node.tag = 'th'
102                     except:
103                         pass
104         process(node,new_node)
105         if new_node.get('colWidths',False):
106             sizes = map(lambda x: utils.unit_get(x), new_node.get('colWidths').split(','))
107             tr = etree.Element('tr')
108             for s in sizes:
109                 td = etree.Element('td')
110                 td.set("width", str(s))
111                 tr.append(td)
112             new_node.append(tr)
113         return etree.tostring(new_node)
114
115     def _tag_para(self, node):
116         new_node = copy.deepcopy(node)
117         new_node.tag = 'p'
118         if new_node.attrib.get('style',False):
119             new_node.set('class', new_node.get('style'))
120         new_node.text = utils._process_text(self, node.text)
121         return etree.tostring(new_node)
122
123     def _section(self, node):
124         result = ''
125         for child in utils._child_get(node, self):
126             if child.tag in self._tags:
127                 result += self._tags[child.tag](child)
128         return result
129
130     def render(self, node):
131         result = self.template.start()
132         result += self.template.frame_start()
133         for n in utils._child_get(node, self):
134             if n.tag in self._tags:
135                 result += self._tags[n.tag](n)
136             else:
137                 pass
138         result += self.template.frame_stop()
139         result += self.template.end()
140         return result.encode('utf-8').replace('"',"\'").replace('°','&deg;')
141
142 class _rml_tmpl_tag(object):
143     def __init__(self, *args):
144         pass
145     def tag_start(self):
146         return ''
147     def tag_end(self):
148         return False
149     def tag_stop(self):
150         return ''
151     def tag_mergeable(self):
152         return True
153
154 class _rml_tmpl_frame(_rml_tmpl_tag):
155     def __init__(self, posx, width):
156         self.width = width
157         self.posx = posx
158     def tag_start(self):
159         return "<table border=\'0\' width=\'%d\'><tr><td width=\'%d\'>&nbsp;</td><td>" % (self.width+self.posx,self.posx)
160     def tag_end(self):
161         return True
162     def tag_stop(self):
163         return '</td></tr></table><br/>'
164     def tag_mergeable(self):
165         return False
166
167     def merge(self, frame):
168         pass
169
170 class _rml_tmpl_draw_string(_rml_tmpl_tag):
171     def __init__(self, node, style,localcontext = {}):
172         self.localcontext = localcontext
173         self.posx = utils.unit_get(node.get('x'))
174         self.posy =  utils.unit_get(node.get('y'))
175
176         aligns = {
177             'drawString': 'left',
178             'drawRightString': 'right',
179             'drawCentredString': 'center'
180         }
181         align = aligns[node.tag]
182         self.pos = [(self.posx, self.posy, align, utils._process_text(self, node.text), style.get('td'), style.font_size_get('td'))]
183
184     def tag_start(self):
185         self.pos.sort()
186         res = "<table border='0' cellpadding='0' cellspacing='0'><tr>"
187         posx = 0
188         i = 0
189         for (x,y,align,txt, style, fs) in self.pos:
190             if align=="left":
191                 pos2 = len(txt)*fs
192                 res+="<td width=\'%d\'></td><td style=\'%s\' width=\'%d\'>%s</td>" % (x - posx, style, pos2, txt)
193                 posx = x+pos2
194             if align=="right":
195                 res+="<td width=\'%d\' align=\'right\' style=\'%s\'>%s</td>" % (x - posx, style, txt)
196                 posx = x
197             if align=="center":
198                 res+="<td width=\'%d\' align=\'center\' style=\'%s\'>%s</td>" % ((x - posx)*2, style, txt)
199                 posx = 2*x-posx
200             i+=1
201         res+='</tr></table>'
202         return res
203     def merge(self, ds):
204         self.pos+=ds.pos
205
206 class _rml_tmpl_draw_lines(_rml_tmpl_tag):
207     def __init__(self, node, style, localcontext = {}):
208         self.localcontext = localcontext
209         coord = [utils.unit_get(x) for x in utils._process_text(self, node.text).split(' ')]
210         self.ok = False
211         self.posx = coord[0]
212         self.posy = coord[1]
213         self.width = coord[2]-coord[0]
214         self.ok = coord[1]==coord[3]
215         self.style = style
216         self.style = style.get('hr')
217
218     def tag_start(self):
219         if self.ok:
220             return "<table border=\'0\' cellpadding=\'0\' cellspacing=\'0\' width=\'%d\'><tr><td width=\'%d\'></td><td><hr width=\'100%%\' style=\'margin:0px; %s\'></td></tr></table>" % (self.posx+self.width,self.posx,self.style)
221         else:
222             return ''
223
224 class _rml_stylesheet(object):
225     def __init__(self, localcontext, stylesheet, doc):
226         self.doc = doc
227         self.localcontext = localcontext
228         self.attrs = {}
229         self._tags = {
230             'fontSize': lambda x: ('font-size',str(utils.unit_get(x)+5.0)+'px'),
231             'alignment': lambda x: ('text-align',str(x))
232         }
233         result = ''
234         for ps in stylesheet.findall('paraStyle'):
235             attr = {}
236             attrs = ps.attrib
237             for key, val in attrs.items():
238                  attr[key] = val
239             attrs = []
240             for a in attr:
241                 if a in self._tags:
242                     attrs.append('%s:%s' % self._tags[a](attr[a]))
243             if len(attrs):
244                 result += 'p.'+attr['name']+' {'+'; '.join(attrs)+'}\n'
245         self.result = result
246
247     def render(self):
248         return self.result
249
250 class _rml_draw_style(object):
251     def __init__(self):
252         self.style = {}
253         self._styles = {
254             'fill': lambda x: {'td': {'color':x.get('color')}},
255             'setFont': lambda x: {'td': {'font-size':x.get('size')+'px'}},
256             'stroke': lambda x: {'hr': {'color':x.get('color')}},
257         }
258     def update(self, node):
259         if node.tag in self._styles:
260             result = self._styles[node.tag](node)
261             for key in result:
262                 if key in self.style:
263                     self.style[key].update(result[key])
264                 else:
265                     self.style[key] = result[key]
266     def font_size_get(self,tag):
267         size  = utils.unit_get(self.style.get('td', {}).get('font-size','16'))
268         return size
269
270     def get(self,tag):
271         if not tag in self.style:
272             return ""
273         return ';'.join(['%s:%s' % (x[0],x[1]) for x in self.style[tag].items()])
274
275 class _rml_template(object):
276     def __init__(self, template, localcontext=None):
277         self.frame_pos = -1
278         self.localcontext = localcontext
279         self.frames = []
280         self.template_order = []
281         self.page_template = {}
282         self.loop = 0
283         self._tags = {
284             'drawString': _rml_tmpl_draw_string,
285             'drawRightString': _rml_tmpl_draw_string,
286             'drawCentredString': _rml_tmpl_draw_string,
287             'lines': _rml_tmpl_draw_lines
288         }
289         self.style = _rml_draw_style()
290         rc = 'data:image/png;base64,'
291         self.data = ''
292         for pt in template.findall('pageTemplate'):
293             frames = {}
294             id = pt.get('id')
295             self.template_order.append(id)
296             for tmpl in pt.findall('frame'):
297                 posy = int(utils.unit_get(tmpl.get('y1')))
298                 posx = int(utils.unit_get(tmpl.get('x1')))
299                 frames[(posy,posx,tmpl.get('id'))] = _rml_tmpl_frame(posx, utils.unit_get(tmpl.get('width')))
300             for tmpl in pt.findall('pageGraphics'):
301                 for n in tmpl.getchildren():
302                         if n.tag == 'image':
303                            self.data = rc + utils._process_text(self, n.text)
304                         if n.tag in self._tags:
305                             t = self._tags[n.tag](n, self.style,self.localcontext)
306                             frames[(t.posy,t.posx,n.tag)] = t
307                         else:
308                             self.style.update(n)
309             keys = frames.keys()
310             keys.sort()
311             keys.reverse()
312             self.page_template[id] = []
313             for key in range(len(keys)):
314                 if key>0 and keys[key-1][0] == keys[key][0]:
315                     if type(self.page_template[id][-1]) == type(frames[keys[key]]):
316                         if self.page_template[id][-1].tag_mergeable():
317                             self.page_template[id][-1].merge(frames[keys[key]])
318                         continue
319                 self.page_template[id].append(frames[keys[key]])
320         self.template = self.template_order[0]
321
322     def _get_style(self):
323         return self.style
324
325     def set_next_template(self):
326         self.template = self.template_order[(self.template_order.index(name)+1) % self.template_order]
327         self.frame_pos = -1
328
329     def set_template(self, name):
330         self.template = name
331         self.frame_pos = -1
332
333     def frame_start(self):
334         result = ''
335         frames = self.page_template[self.template]
336         ok = True
337         while ok:
338             self.frame_pos += 1
339             if self.frame_pos>=len(frames):
340                 self.frame_pos=0
341                 self.loop=1
342                 ok = False
343                 continue
344             f = frames[self.frame_pos]
345             result+=f.tag_start()
346             ok = not f.tag_end()
347             if ok:
348                 result+=f.tag_stop()
349         return result
350
351     def frame_stop(self):
352         frames = self.page_template[self.template]
353         f = frames[self.frame_pos]
354         result=f.tag_stop()
355         return result
356
357     def start(self):
358         return ''
359
360     def end(self):
361         result = ''
362         while not self.loop:
363             result += self.frame_start()
364             result += self.frame_stop()
365         return result
366
367 class _rml_doc(object):
368     def __init__(self, data, localcontext):
369         self.dom = etree.XML(data)
370         self.localcontext = localcontext
371         self.filename = self.dom.get('filename')
372         self.result = ''
373
374     def render(self, out):
375         self.result += '''<!DOCTYPE HTML PUBLIC "-//w3c//DTD HTML 4.0 Frameset//EN">
376 <html>
377 <head>
378 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
379     <style type="text/css">
380         p {margin:0px; font-size:12px;}
381         td {font-size:14px;}
382 '''
383         style = self.dom.findall('stylesheet')[0]
384         s = _rml_stylesheet(self.localcontext, style, self.dom)
385         self.result += s.render()
386         self.result+='''
387     </style>
388 '''
389         list_story =[]
390         for story in utils._child_get(self.dom, self, 'story'):
391             template = _rml_template(self.dom.findall('template')[0], self.localcontext)
392             f = _flowable(template, self.dom, localcontext = self.localcontext)
393             story_text = f.render(story)
394             list_story.append(story_text)
395         del f
396         if template.data:
397             tag = '''<img src = '%s' width=80 height=72/>'''%(template.data)
398         else:
399             tag = ''
400         self.result +='''
401             <script type="text/javascript">
402
403             var indexer = 0;
404             var aryTest = %s ;
405             function nextData()
406                 {
407                 if(indexer < aryTest.length -1)
408                     {
409                     indexer += 1;
410                     document.getElementById("tiny_data").innerHTML=aryTest[indexer];
411                     }
412                 }
413             function prevData()
414                 {
415                 if (indexer > 0)
416                     {
417                     indexer -= 1;
418                     document.getElementById("tiny_data").innerHTML=aryTest[indexer];
419                     }
420                 }
421         </script>
422         </head>
423         <body>
424             %s
425             <div id="tiny_data">
426                 %s
427             </div>
428             <br>
429             <input type="button" value="next" onclick="nextData();">
430             <input type="button" value="prev" onclick="prevData();">
431
432         </body></html>'''%(list_story,tag,list_story[0])
433         out.write( self.result)
434
435 def parseString(data,localcontext = {}, fout=None):
436     r = _rml_doc(data, localcontext)
437     if fout:
438         fp = file(fout,'wb')
439         r.render(fp)
440         fp.close()
441         return fout
442     else:
443         fp = cStringIO.StringIO()
444         r.render(fp)
445         return fp.getvalue()
446
447 def rml2html_help():
448     print 'Usage: rml2html input.rml >output.html'
449     print 'Render the standard input (RML) and output an HTML file'
450     sys.exit(0)
451
452 if __name__=="__main__":
453     if len(sys.argv)>1:
454         if sys.argv[1]=='--help':
455             rml2html_help()
456         print parseString(file(sys.argv[1], 'r').read()),
457     else:
458         print 'Usage: trml2pdf input.rml >output.pdf'
459         print 'Try \'trml2pdf --help\' for more information.'
460
461 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: