fix with the getName tag
[odoo/odoo.git] / bin / report / render / rml2pdf / trml2pdf.py
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # trml2pdf - An RML to PDF converter
5 # Copyright (C) 2003, Fabien Pinckaers, UCL, FSA
6 # Contributors
7 #     Richard Waid <richard@iopen.net>
8 #
9 # This library is free software; you can redistribute it and/or
10 # modify it under the terms of the GNU Lesser General Public
11 # License as published by the Free Software Foundation; either
12 # version 2.1 of the License, or (at your option) any later version.
13 #
14 # This library is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 # Lesser General Public License for more details.
18 #
19 # You should have received a copy of the GNU Lesser General Public
20 # License along with this library; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22
23 import sys
24 import StringIO
25 import xml.dom.minidom
26 import copy
27
28 import reportlab
29 from reportlab.pdfgen import canvas
30 from reportlab import platypus
31
32 import utils
33 import color
34 import os
35
36 #
37 # Change this to UTF-8 if you plan tu use Reportlab's UTF-8 support
38 #
39 # reportlab use "code page 1252" encoding by default. cfr reportlab user guide p.46
40 encoding = 'utf-8'
41
42 def str2xml(s):
43         return s.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
44
45 def _child_get(node, childs):
46         clds = []
47         for n in node.childNodes:
48                 if (n.nodeType==n.ELEMENT_NODE) and (n.localName==childs):
49                         clds.append(n)
50         return clds
51
52 class _rml_styles(object):
53         def __init__(self, nodes):
54                 self.styles = {}
55                 self.names = {}
56                 self.table_styles = {}
57                 for node in nodes:
58                         for style in node.getElementsByTagName('blockTableStyle'):
59                                 self.table_styles[style.getAttribute('id')] = self._table_style_get(style)
60                         for style in node.getElementsByTagName('paraStyle'):
61                                 self.styles[style.getAttribute('name')] = self._para_style_get(style)
62                         for variable in node.getElementsByTagName('initialize'):
63                                 for name in variable.getElementsByTagName('name'):
64                                         self.names[ name.getAttribute('id')] = name.getAttribute('value')
65
66         def _para_style_update(self, style, node):
67                 for attr in ['textColor', 'backColor', 'bulletColor']:
68                         if node.hasAttribute(attr):
69                                 style.__dict__[attr] = color.get(node.getAttribute(attr))
70                 for attr in ['fontName', 'bulletFontName', 'bulletText']:
71                         if node.hasAttribute(attr):
72                                 style.__dict__[attr] = node.getAttribute(attr)
73                 for attr in ['fontSize', 'leftIndent', 'rightIndent', 'spaceBefore', 'spaceAfter', 'firstLineIndent', 'bulletIndent', 'bulletFontSize', 'leading']:
74                         if node.hasAttribute(attr):
75                                 style.__dict__[attr] = utils.unit_get(node.getAttribute(attr))
76                 if node.hasAttribute('alignment'):
77                         align = {
78                                 'right':reportlab.lib.enums.TA_RIGHT,
79                                 'center':reportlab.lib.enums.TA_CENTER,
80                                 'justify':reportlab.lib.enums.TA_JUSTIFY
81                         }
82                         style.alignment = align.get(node.getAttribute('alignment').lower(), reportlab.lib.enums.TA_LEFT)
83                 return style
84
85         def _table_style_get(self, style_node):
86                 styles = []
87                 for node in style_node.childNodes:
88                         if node.nodeType==node.ELEMENT_NODE:
89                                 start = utils.tuple_int_get(node, 'start', (0,0) )
90                                 stop = utils.tuple_int_get(node, 'stop', (-1,-1) )
91                                 if node.localName=='blockValign':
92                                         styles.append(('VALIGN', start, stop, str(node.getAttribute('value'))))
93                                 elif node.localName=='blockFont':
94                                         styles.append(('FONT', start, stop, str(node.getAttribute('name'))))
95                                 elif node.localName=='blockTextColor':
96                                         styles.append(('TEXTCOLOR', start, stop, color.get(str(node.getAttribute('colorName')))))
97                                 elif node.localName=='blockLeading':
98                                         styles.append(('LEADING', start, stop, utils.unit_get(node.getAttribute('length'))))
99                                 elif node.localName=='blockAlignment':
100                                         styles.append(('ALIGNMENT', start, stop, str(node.getAttribute('value'))))
101                                 elif node.localName=='blockSpan':
102                                         styles.append(('SPAN', start, stop))
103                                 elif node.localName=='blockLeftPadding':
104                                         styles.append(('LEFTPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
105                                 elif node.localName=='blockRightPadding':
106                                         styles.append(('RIGHTPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
107                                 elif node.localName=='blockTopPadding':
108                                         styles.append(('TOPPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
109                                 elif node.localName=='blockBottomPadding':
110                                         styles.append(('BOTTOMPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
111                                 elif node.localName=='blockBackground':
112                                         styles.append(('BACKGROUND', start, stop, color.get(node.getAttribute('colorName'))))
113                                 if node.hasAttribute('size'):
114                                         styles.append(('FONTSIZE', start, stop, utils.unit_get(node.getAttribute('size'))))
115                                 elif node.localName=='lineStyle':
116                                         kind = node.getAttribute('kind')
117                                         kind_list = [ 'GRID', 'BOX', 'OUTLINE', 'INNERGRID', 'LINEBELOW', 'LINEABOVE','LINEBEFORE', 'LINEAFTER' ]
118                                         assert kind in kind_list
119                                         thick = 1
120                                         if node.hasAttribute('thickness'):
121                                                 thick = float(node.getAttribute('thickness'))
122                                         styles.append((kind, start, stop, thick, color.get(node.getAttribute('colorName'))))
123                 return platypus.tables.TableStyle(styles)
124
125         def _para_style_get(self, node):
126                 styles = reportlab.lib.styles.getSampleStyleSheet()
127                 style = copy.deepcopy(styles["Normal"])
128                 self._para_style_update(style, node)
129                 return style
130
131         def para_style_get(self, node):
132                 style = False
133                 if node.hasAttribute('style'):
134                         if node.getAttribute('style') in self.styles:
135                                 style = copy.deepcopy(self.styles[node.getAttribute('style')])
136                         else:
137                                 sys.stderr.write('Warning: style not found, %s - setting default!\n' % (node.getAttribute('style'),) )
138                 if not style:
139                         styles = reportlab.lib.styles.getSampleStyleSheet()
140                         style = copy.deepcopy(styles['Normal'])
141                 return self._para_style_update(style, node)
142
143 class _rml_doc(object):
144         def __init__(self, data, images={}, path='.'):
145                 self.dom = xml.dom.minidom.parseString(data)
146                 self.filename = self.dom.documentElement.getAttribute('filename')
147                 self.images = images
148                 self.path = path
149
150         def docinit(self, els):
151                 from reportlab.lib.fonts import addMapping
152                 from reportlab.pdfbase import pdfmetrics
153                 from reportlab.pdfbase.ttfonts import TTFont
154
155                 for node in els:
156                         for font in node.getElementsByTagName('registerFont'):
157                                 name = font.getAttribute('fontName').encode('ascii')
158                                 fname = font.getAttribute('fontFile').encode('ascii')
159                                 pdfmetrics.registerFont(TTFont(name, fname ))
160                                 addMapping(name, 0, 0, name)    #normal
161                                 addMapping(name, 0, 1, name)    #italic
162                                 addMapping(name, 1, 0, name)    #bold
163                                 addMapping(name, 1, 1, name)    #italic and bold
164
165         def _textual_image(self, node):
166                 import base64
167                 rc = ''
168                 for n in node.childNodes:
169                         if n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
170                                 rc += n.data
171                 return base64.decodestring(rc)
172
173         def _images(self, el):
174                 result = {}
175                 for node in el.getElementsByTagName('image'):
176                         result[node.getAttribute('name')] = self._textual_image(node)
177                 return result
178
179         def render(self, out):
180                 el = self.dom.documentElement.getElementsByTagName('docinit')
181                 if el:
182                         self.docinit(el)
183
184                 el = self.dom.documentElement.getElementsByTagName('stylesheet')
185                 self.styles = _rml_styles(el)
186
187                 el = self.dom.documentElement.getElementsByTagName('images')
188                 if el:
189                         self.images.update( self._images(el[0]) )
190
191                 el = self.dom.documentElement.getElementsByTagName('template')
192                 if len(el):
193                         pt_obj = _rml_template(out, el[0], self, images=self.images, path=self.path)
194                         pt_obj.render(self.dom.documentElement.getElementsByTagName('story'))
195                 else:
196                         self.canvas = canvas.Canvas(out)
197                         pd = self.dom.documentElement.getElementsByTagName('pageDrawing')[0]
198                         pd_obj = _rml_canvas(self.canvas, None, self, self.images, path=self.path)
199                         pd_obj.render(pd)
200                         self.canvas.showPage()
201                         self.canvas.save()
202
203 class _rml_canvas(object):
204         def __init__(self, canvas, doc_tmpl=None, doc=None, images={}, path='.'):
205                 self.canvas = canvas
206                 self.styles = doc.styles
207                 self.doc_tmpl = doc_tmpl
208                 self.doc = doc
209                 self.images = images
210                 self.path = path
211
212         def _textual(self, node):
213                 rc = ''
214                 for n in node.childNodes:
215                         if n.nodeType == n.ELEMENT_NODE:
216                                 if n.localName == 'pageNumber':
217                                         rc += str(self.canvas.getPageNumber())
218                         elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
219                                 # this doesn't need to be "entities" encoded like flowables need to
220                                 rc += n.data
221                 return rc.encode(encoding, 'replace')
222
223         def _drawString(self, node):
224                 self.canvas.drawString(text=self._textual(node), **utils.attr_get(node, ['x','y']))
225         def _drawCenteredString(self, node):
226                 self.canvas.drawCentredString(text=self._textual(node), **utils.attr_get(node, ['x','y']))
227         def _drawRightString(self, node):
228                 self.canvas.drawRightString(text=self._textual(node), **utils.attr_get(node, ['x','y']))
229         def _rect(self, node):
230                 if node.hasAttribute('round'):
231                         self.canvas.roundRect(radius=utils.unit_get(node.getAttribute('round')), **utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
232                 else:
233                         self.canvas.rect(**utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
234
235         def _ellipse(self, node):
236                 x1 = utils.unit_get(node.getAttribute('x'))
237                 x2 = utils.unit_get(node.getAttribute('width'))
238                 y1 = utils.unit_get(node.getAttribute('y'))
239                 y2 = utils.unit_get(node.getAttribute('height'))
240                 self.canvas.ellipse(x1,y1,x2,y2, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
241         def _curves(self, node):
242                 line_str = utils.text_get(node).split()
243                 lines = []
244                 while len(line_str)>7:
245                         self.canvas.bezier(*[utils.unit_get(l) for l in line_str[0:8]])
246                         line_str = line_str[8:]
247         def _lines(self, node):
248                 line_str = utils.text_get(node).split()
249                 lines = []
250                 while len(line_str)>3:
251                         lines.append([utils.unit_get(l) for l in line_str[0:4]])
252                         line_str = line_str[4:]
253                 self.canvas.lines(lines)
254         def _grid(self, node):
255                 xlist = [utils.unit_get(s) for s in node.getAttribute('xs').split(',')]
256                 ylist = [utils.unit_get(s) for s in node.getAttribute('ys').split(',')]
257                 self.canvas.grid(xlist, ylist)
258         def _translate(self, node):
259                 dx = 0
260                 dy = 0
261                 if node.hasAttribute('dx'):
262                         dx = utils.unit_get(node.getAttribute('dx'))
263                 if node.hasAttribute('dy'):
264                         dy = utils.unit_get(node.getAttribute('dy'))
265                 self.canvas.translate(dx,dy)
266
267         def _circle(self, node):
268                 self.canvas.circle(x_cen=utils.unit_get(node.getAttribute('x')), y_cen=utils.unit_get(node.getAttribute('y')), r=utils.unit_get(node.getAttribute('radius')), **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
269
270         def _place(self, node):
271                 flows = _rml_flowable(self.doc, images=self.images, path=self.path).render(node)
272                 infos = utils.attr_get(node, ['x','y','width','height'])
273
274                 infos['y']+=infos['height']
275                 for flow in flows:
276                         w,h = flow.wrap(infos['width'], infos['height'])
277                         if w<=infos['width'] and h<=infos['height']:
278                                 infos['y']-=h
279                                 flow.drawOn(self.canvas,infos['x'],infos['y'])
280                                 infos['height']-=h
281                         else:
282                                 raise ValueError, "Not enough space"
283
284         def _line_mode(self, node):
285                 ljoin = {'round':1, 'mitered':0, 'bevelled':2}
286                 lcap = {'default':0, 'round':1, 'square':2}
287                 if node.hasAttribute('width'):
288                         self.canvas.setLineWidth(utils.unit_get(node.getAttribute('width')))
289                 if node.hasAttribute('join'):
290                         self.canvas.setLineJoin(ljoin[node.getAttribute('join')])
291                 if node.hasAttribute('cap'):
292                         self.canvas.setLineCap(lcap[node.getAttribute('cap')])
293                 if node.hasAttribute('miterLimit'):
294                         self.canvas.setDash(utils.unit_get(node.getAttribute('miterLimit')))
295                 if node.hasAttribute('dash'):
296                         dashes = node.getAttribute('dash').split(',')
297                         for x in range(len(dashes)):
298                                 dashes[x]=utils.unit_get(dashes[x])
299                         self.canvas.setDash(node.getAttribute('dash').split(','))
300
301         def _image(self, node):
302                 import urllib
303                 from reportlab.lib.utils import ImageReader
304
305                 if not node.hasAttribute('file'):
306                         if node.getAttribute('name') in self.images:
307                                 scontent = self.images[node.getAttribute('name')]
308                                 s = StringIO.StringIO(scontent)
309                         else:
310                                 return True
311                 else:
312                         if node.getAttribute('file') in self.images:
313                                 s = StringIO.StringIO(self.images[node.getAttribute('file')])
314                         else:
315                                 try:
316                                         u = urllib.urlopen(str(node.getAttribute('file')))
317                                         s = StringIO.StringIO(u.read())
318                                 except:
319                                         u = file(os.path.join(self.path,str(node.getAttribute('file'))), 'rb')
320                                         s = StringIO.StringIO(u.read())
321                 img = ImageReader(s)
322                 (sx,sy) = img.getSize()
323
324                 args = {}
325                 for tag in ('width','height','x','y'):
326                         if node.hasAttribute(tag):
327                                 args[tag] = utils.unit_get(node.getAttribute(tag))
328                 if ('width' in args) and (not 'height' in args):
329                         args['height'] = sy * args['width'] / sx
330                 elif ('height' in args) and (not 'width' in args):
331                         args['width'] = sx * args['height'] / sy
332                 elif ('width' in args) and ('height' in args):
333                         if (float(args['width'])/args['height'])>(float(sx)>sy):
334                                 args['width'] = sx * args['height'] / sy
335                         else:
336                                 args['height'] = sy * args['width'] / sx
337                 self.canvas.drawImage(img, **args)
338
339         def _path(self, node):
340                 self.path = self.canvas.beginPath()
341                 self.path.moveTo(**utils.attr_get(node, ['x','y']))
342                 for n in node.childNodes:
343                         if n.nodeType == node.ELEMENT_NODE:
344                                 if n.localName=='moveto':
345                                         vals = utils.text_get(n).split()
346                                         self.path.moveTo(utils.unit_get(vals[0]), utils.unit_get(vals[1]))
347                                 elif n.localName=='curvesto':
348                                         vals = utils.text_get(n).split()
349                                         while len(vals)>5:
350                                                 pos=[]
351                                                 while len(pos)<6:
352                                                         pos.append(utils.unit_get(vals.pop(0)))
353                                                 self.path.curveTo(*pos)
354                         elif (n.nodeType == node.TEXT_NODE):
355                                 data = n.data.split()               # Not sure if I must merge all TEXT_NODE ?
356                                 while len(data)>1:
357                                         x = utils.unit_get(data.pop(0))
358                                         y = utils.unit_get(data.pop(0))
359                                         self.path.lineTo(x,y)
360                 if (not node.hasAttribute('close')) or utils.bool_get(node.getAttribute('close')):
361                         self.path.close()
362                 self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
363
364         def render(self, node):
365                 tags = {
366                         'drawCentredString': self._drawCenteredString,
367                         'drawRightString': self._drawRightString,
368                         'drawString': self._drawString,
369                         'rect': self._rect,
370                         'ellipse': self._ellipse,
371                         'lines': self._lines,
372                         'grid': self._grid,
373                         'curves': self._curves,
374                         'fill': lambda node: self.canvas.setFillColor(color.get(node.getAttribute('color'))),
375                         'stroke': lambda node: self.canvas.setStrokeColor(color.get(node.getAttribute('color'))),
376                         'setFont': lambda node: self.canvas.setFont(node.getAttribute('name'), utils.unit_get(node.getAttribute('size'))),
377                         'place': self._place,
378                         'circle': self._circle,
379                         'lineMode': self._line_mode,
380                         'path': self._path,
381                         'rotate': lambda node: self.canvas.rotate(float(node.getAttribute('degrees'))),
382                         'translate': self._translate,
383                         'image': self._image
384                 }
385                 for nd in node.childNodes:
386                         if nd.nodeType==nd.ELEMENT_NODE:
387                                 for tag in tags:
388                                         if nd.localName==tag:
389                                                 tags[tag](nd)
390                                                 break
391
392 class _rml_draw(object):
393         def __init__(self, node, styles, images={}, path='.'):
394                 self.node = node
395                 self.styles = styles
396                 self.canvas = None
397                 self.images = images
398                 self.path = path
399
400         def render(self, canvas, doc):
401                 canvas.saveState()
402                 cnv = _rml_canvas(canvas, doc, self.styles, images=self.images, path=self.path)
403                 cnv.render(self.node)
404                 canvas.restoreState()
405
406 class _rml_flowable(object):
407         def __init__(self, doc, images={}, path='.'):
408                 self.doc = doc
409                 self.styles = doc.styles
410                 self.images = images
411                 self.path = path
412
413         def _textual(self, node):
414                 rc = ''
415                 for n in node.childNodes:
416                         if n.nodeType == node.ELEMENT_NODE:
417                                 if n.localName == 'getName':
418                                         newNode = self.doc.dom.createTextNode(self.styles.names.get(n.getAttribute('id'),'Unknown name'))
419                                         node.insertBefore(newNode, n)
420                                         node.removeChild(n)
421                                         n = newNode
422                                 elif n.localName == 'pageNumber':
423                                         rc += '<pageNumber/>'            # TODO: change this !
424                                 else:
425                                         #CHECKME: I wonder if this is useful since we don't stock the result. Maybe for the getName tag?
426                                         self._textual(n)
427                                 rc += n.toxml()
428                         elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
429                                 rc += str2xml(n.data)
430                 return rc.encode(encoding, 'replace')
431
432         def _table(self, node):
433                 length = 0
434                 colwidths = None
435                 rowheights = None
436                 data = []
437                 childs = _child_get(node,'tr')
438                 if not childs:
439                         return None
440                 for tr in childs:
441                         data2 = []
442                         for td in _child_get(tr, 'td'):
443                                 flow = []
444                                 for n in td.childNodes:
445                                         if n.nodeType==node.ELEMENT_NODE:
446                                                 flow.append( self._flowable(n) )
447                                 if not len(flow):
448                                         flow = self._textual(td)
449                                 data2.append( flow )
450                         if len(data2)>length:
451                                 length=len(data2)
452                                 for ab in data:
453                                         while len(ab)<length:
454                                                 ab.append('')
455                         while len(data2)<length:
456                                 data2.append('')
457                         data.append( data2 )
458                 if node.hasAttribute('colWidths'):
459                         assert length == len(node.getAttribute('colWidths').split(','))
460                         colwidths = [utils.unit_get(f.strip()) for f in node.getAttribute('colWidths').split(',')]
461                 if node.hasAttribute('rowHeights'):
462                         rowheights = [utils.unit_get(f.strip()) for f in node.getAttribute('rowHeights').split(',')]
463                         if len(rowheights) == 1:
464                                 rowheights = rowheights[0]
465                 table = platypus.Table(data = data, colWidths=colwidths, rowHeights=rowheights, **(utils.attr_get(node, ['splitByRow'] ,{'repeatRows':'int','repeatCols':'int'})))
466                 if node.hasAttribute('style'):
467                         table.setStyle(self.styles.table_styles[node.getAttribute('style')])
468                 return table
469
470         def _illustration(self, node):
471                 class Illustration(platypus.flowables.Flowable):
472                         def __init__(self, node, styles, self2):
473                                 self.node = node
474                                 self.styles = styles
475                                 self.width = utils.unit_get(node.getAttribute('width'))
476                                 self.height = utils.unit_get(node.getAttribute('height'))
477                                 self.self2 = self2
478                         def wrap(self, *args):
479                                 return (self.width, self.height)
480                         def draw(self):
481                                 canvas = self.canv
482                                 drw = _rml_draw(self.node, self.styles, images=self.self2.images, path=self.self2.path)
483                                 drw.render(self.canv, None)
484                 return Illustration(node, self.styles, self)
485
486         def _textual_image(self, node):
487                 import base64
488                 rc = ''
489                 for n in node.childNodes:
490                         if n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
491                                 rc += n.data
492                 return base64.decodestring(rc)
493
494         def _flowable(self, node):
495                 if node.localName=='para':
496                         style = self.styles.para_style_get(node)
497                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
498                 elif node.localName=='barCode':
499                         try:
500                                 from reportlab.graphics.barcode import code128
501                                 from reportlab.graphics.barcode import code39
502                                 from reportlab.graphics.barcode import code93
503                                 from reportlab.graphics.barcode import common
504                                 from reportlab.graphics.barcode import fourstate
505                                 from reportlab.graphics.barcode import usps
506                         except Exception, e:
507                                 print 'Warning: Reportlab barcode extension not installed !'
508                                 return None
509                         args = utils.attr_get(node, [], {'ratio':'float','xdim':'unit','height':'unit','checksum':'bool','quiet':'bool'})
510                         codes = {
511                                 'codabar': lambda x: common.Codabar(x, **args),
512                                 'code11': lambda x: common.Code11(x, **args),
513                                 'code128': lambda x: code128.Code128(x, **args),
514                                 'standard39': lambda x: code39.Standard39(x, **args),
515                                 'standard93': lambda x: code93.Standard93(x, **args),
516                                 'i2of5': lambda x: common.I2of5(x, **args),
517                                 'extended39': lambda x: code39.Extended39(x, **args),
518                                 'extended93': lambda x: code93.Extended93(x, **args),
519                                 'msi': lambda x: common.MSI(x, **args),
520                                 'fim': lambda x: usps.FIM(x, **args),
521                                 'postnet': lambda x: usps.POSTNET(x, **args),
522                         }
523                         code = 'code128'
524                         if node.hasAttribute('code'):
525                                 code = node.getAttribute('code').lower()
526                         return codes[code](self._textual(node))
527                 elif node.localName=='name':
528                         self.styles.names[ node.getAttribute('id')] = node.getAttribute('value')
529                         return None
530                 elif node.localName=='xpre':
531                         style = self.styles.para_style_get(node)
532                         return platypus.XPreformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int','frags':'int'})))
533                 elif node.localName=='pre':
534                         style = self.styles.para_style_get(node)
535                         return platypus.Preformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int'})))
536                 elif node.localName=='illustration':
537                         return  self._illustration(node)
538                 elif node.localName=='blockTable':
539                         return  self._table(node)
540                 elif node.localName=='title':
541                         styles = reportlab.lib.styles.getSampleStyleSheet()
542                         style = styles['Title']
543                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
544                 elif node.localName=='h1':
545                         styles = reportlab.lib.styles.getSampleStyleSheet()
546                         style = styles['Heading1']
547                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
548                 elif node.localName=='h2':
549                         styles = reportlab.lib.styles.getSampleStyleSheet()
550                         style = styles['Heading2']
551                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
552                 elif node.localName=='h3':
553                         styles = reportlab.lib.styles.getSampleStyleSheet()
554                         style = styles['Heading3']
555                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
556                 elif node.localName=='image':
557                         if not node.hasAttribute('file'):
558                                 if node.hasAttribute('name'):
559                                         image_data = self.doc.images[node.getAttribute('name')].read()
560                                 else:
561                                         import base64
562                                         image_data = base64.decodestring(node.firstChild.nodeValue)
563                                 image = StringIO.StringIO(image_data)
564                                 return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
565                         else:
566                                 return platypus.Image(node.getAttribute('file'), mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
567
568                         from reportlab.lib.utils import ImageReader
569                         name = str(node.getAttribute('file'))
570                         img = ImageReader(name)
571                         (sx,sy) = img.getSize()
572
573                         args = {}
574                         for tag in ('width','height'):
575                                 if node.hasAttribute(tag):
576                                         args[tag] = utils.unit_get(node.getAttribute(tag))
577                         if ('width' in args) and (not 'height' in args):
578                                 args['height'] = sy * args['width'] / sx
579                         elif ('height' in args) and (not 'width' in args):
580                                 args['width'] = sx * args['height'] / sy
581                         elif ('width' in args) and ('height' in args):
582                                 if (float(args['width'])/args['height'])>(float(sx)>sy):
583                                         args['width'] = sx * args['height'] / sy
584                                 else:
585                                         args['height'] = sy * args['width'] / sx
586                         return platypus.Image(name, mask=(250,255,250,255,250,255), **args)
587                 elif node.localName=='spacer':
588                         if node.hasAttribute('width'):
589                                 width = utils.unit_get(node.getAttribute('width'))
590                         else:
591                                 width = utils.unit_get('1cm')
592                         length = utils.unit_get(node.getAttribute('length'))
593                         return platypus.Spacer(width=width, height=length)
594                 elif node.localName=='section':
595                         return self.render(node)
596                 elif node.localName in ('pageBreak', 'nextPage'):
597                         return platypus.PageBreak()
598                 elif node.localName=='condPageBreak':
599                         return platypus.CondPageBreak(**(utils.attr_get(node, ['height'])))
600                 elif node.localName=='setNextTemplate':
601                         return platypus.NextPageTemplate(str(node.getAttribute('name')))
602                 elif node.localName=='nextFrame':
603                         return platypus.CondPageBreak(1000)           # TODO: change the 1000 !
604                 elif node.localName == 'setNextFrame':
605                         from reportlab.platypus.doctemplate import NextFrameFlowable
606                         return NextFrameFlowable(str(node.getAttribute('name')))
607                 elif node.localName == 'currentFrame':
608                         from reportlab.platypus.doctemplate import CurrentFrameFlowable
609                         return CurrentFrameFlowable(str(node.getAttribute('name')))
610                 elif node.localName == 'frameEnd':
611                         return EndFrameFlowable()
612                 else:
613                         sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.localName,))
614                         return None
615
616         def render(self, node_story):
617                 story = []
618                 node = node_story.firstChild
619                 while node:
620                         if node.nodeType == node.ELEMENT_NODE:
621                                 flow = self._flowable(node) 
622                                 if flow:
623                                         if type(flow) == type([]):
624                                                 story = story + flow
625                                         else:
626                                                 story.append(flow)
627                         node = node.nextSibling
628                 return story
629
630 from reportlab.platypus.doctemplate import ActionFlowable
631
632 class EndFrameFlowable(ActionFlowable):
633         def __init__(self,resume=0):
634                 ActionFlowable.__init__(self,('frameEnd',resume))
635
636 class TinyDocTemplate(platypus.BaseDocTemplate):
637
638         def ___handle_pageBegin(self):
639                 self.page = self.page + 1
640                 self.pageTemplate.beforeDrawPage(self.canv,self)
641                 self.pageTemplate.checkPageSize(self.canv,self)
642                 self.pageTemplate.onPage(self.canv,self)
643                 for f in self.pageTemplate.frames: f._reset()
644                 self.beforePage()
645                 #keep a count of flowables added to this page.  zero indicates bad stuff
646                 self._curPageFlowableCount = 0
647                 if hasattr(self,'_nextFrameIndex'):
648                         del self._nextFrameIndex
649                 for f in self.pageTemplate.frames:
650                         if f.id == 'first':
651                                 self.frame = f
652                                 break
653                 self.handle_frameBegin()
654
655 class _rml_template(object):
656         def __init__(self, out, node, doc, images={}, path='.'):
657                 self.images= images
658                 self.path = path
659                 if not node.hasAttribute('pageSize'):
660                         pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm'))
661                 else:
662                         ps = map(lambda x:x.strip(), node.getAttribute('pageSize').replace(')', '').replace('(', '').split(','))
663                         pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
664                 cm = reportlab.lib.units.cm
665                 self.doc_tmpl = TinyDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','title':'str','author':'str'}))
666                 self.page_templates = []
667                 self.styles = doc.styles
668                 self.doc = doc
669                 pts = node.getElementsByTagName('pageTemplate')
670                 for pt in pts:
671                         frames = []
672                         for frame_el in pt.getElementsByTagName('frame'):
673                                 frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'str', 'showBoundary':'bool'})) )
674                                 if utils.attr_get(frame_el, ['last']):
675                                         frame.lastFrame = True
676                                 frames.append( frame )
677                         gr = pt.getElementsByTagName('pageGraphics')
678                         if len(gr):
679                                 drw = _rml_draw(gr[0], self.doc, images=images, path=self.path)
680                                 self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
681                         else:
682                                 self.page_templates.append( platypus.PageTemplate(frames=frames, **utils.attr_get(pt, [], {'id':'str'}) ))
683                 self.doc_tmpl.addPageTemplates(self.page_templates)
684
685         def render(self, node_stories):
686                 fis = []
687                 r = _rml_flowable(self.doc,images=self.images, path=self.path)
688                 for node_story in node_stories:
689                         fis += r.render(node_story)
690                         fis.append(platypus.PageBreak())
691                 self.doc_tmpl.build(fis)
692
693 def parseString(data, fout=None, images={}, path='.'):
694         r = _rml_doc(data, images, path)
695         if fout:
696                 fp = file(fout,'wb')
697                 r.render(fp)
698                 fp.close()
699                 return fout
700         else:
701                 fp = StringIO.StringIO()
702                 r.render(fp)
703                 return fp.getvalue()
704
705 def trml2pdf_help():
706         print 'Usage: trml2pdf input.rml >output.pdf'
707         print 'Render the standard input (RML) and output a PDF file'
708         sys.exit(0)
709
710 if __name__=="__main__":
711         if len(sys.argv)>1:
712                 if sys.argv[1]=='--help':
713                         trml2pdf_help()
714                 print parseString(file(sys.argv[1], 'r').read()),
715         else:
716                 print 'Usage: trml2pdf input.rml >output.pdf'
717                 print 'Try \'trml2pdf --help\' for more information.'