support <pageNumber /> in para
[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                 self.canvas = canvas.Canvas(out)
181                 el = self.dom.documentElement.getElementsByTagName('docinit')
182                 if el:
183                         self.docinit(el)
184
185                 el = self.dom.documentElement.getElementsByTagName('stylesheet')
186                 self.styles = _rml_styles(el)
187
188                 el = self.dom.documentElement.getElementsByTagName('images')
189                 if el:
190                         self.images.update( self._images(el[0]) )
191
192                 el = self.dom.documentElement.getElementsByTagName('template')
193                 if len(el):
194                         pt_obj = _rml_template(out, el[0], self, images=self.images, path=self.path)
195                         pt_obj.render(self.dom.documentElement.getElementsByTagName('story'))
196                         del self.canvas
197                 else:
198                         pd = self.dom.documentElement.getElementsByTagName('pageDrawing')[0]
199                         pd_obj = _rml_canvas(self.canvas, None, self, self.images, path=self.path)
200                         pd_obj.render(pd)
201                         self.canvas.showPage()
202                         self.canvas.save()
203
204 class _rml_canvas(object):
205         def __init__(self, canvas, doc_tmpl=None, doc=None, images={}, path='.'):
206                 self.canvas = canvas
207                 self.styles = doc.styles
208                 self.doc_tmpl = doc_tmpl
209                 self.doc = doc
210                 self.images = images
211                 self.path = path
212
213         def _textual(self, node):
214                 rc = ''
215                 for n in node.childNodes:
216                         if n.nodeType == n.ELEMENT_NODE:
217                                 if n.localName == 'pageNumber':
218                                         rc += str(self.canvas.getPageNumber())
219                         elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
220                                 # this doesn't need to be "entities" encoded like flowables need to
221                                 rc += n.data
222                 return rc.encode(encoding, 'replace')
223
224         def _drawString(self, node):
225                 self.canvas.drawString(text=self._textual(node), **utils.attr_get(node, ['x','y']))
226         def _drawCenteredString(self, node):
227                 self.canvas.drawCentredString(text=self._textual(node), **utils.attr_get(node, ['x','y']))
228         def _drawRightString(self, node):
229                 self.canvas.drawRightString(text=self._textual(node), **utils.attr_get(node, ['x','y']))
230         def _rect(self, node):
231                 if node.hasAttribute('round'):
232                         self.canvas.roundRect(radius=utils.unit_get(node.getAttribute('round')), **utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
233                 else:
234                         self.canvas.rect(**utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
235
236         def _ellipse(self, node):
237                 x1 = utils.unit_get(node.getAttribute('x'))
238                 x2 = utils.unit_get(node.getAttribute('width'))
239                 y1 = utils.unit_get(node.getAttribute('y'))
240                 y2 = utils.unit_get(node.getAttribute('height'))
241                 self.canvas.ellipse(x1,y1,x2,y2, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
242         def _curves(self, node):
243                 line_str = utils.text_get(node).split()
244                 lines = []
245                 while len(line_str)>7:
246                         self.canvas.bezier(*[utils.unit_get(l) for l in line_str[0:8]])
247                         line_str = line_str[8:]
248         def _lines(self, node):
249                 line_str = utils.text_get(node).split()
250                 lines = []
251                 while len(line_str)>3:
252                         lines.append([utils.unit_get(l) for l in line_str[0:4]])
253                         line_str = line_str[4:]
254                 self.canvas.lines(lines)
255         def _grid(self, node):
256                 xlist = [utils.unit_get(s) for s in node.getAttribute('xs').split(',')]
257                 ylist = [utils.unit_get(s) for s in node.getAttribute('ys').split(',')]
258                 self.canvas.grid(xlist, ylist)
259         def _translate(self, node):
260                 dx = 0
261                 dy = 0
262                 if node.hasAttribute('dx'):
263                         dx = utils.unit_get(node.getAttribute('dx'))
264                 if node.hasAttribute('dy'):
265                         dy = utils.unit_get(node.getAttribute('dy'))
266                 self.canvas.translate(dx,dy)
267
268         def _circle(self, node):
269                 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'}))
270
271         def _place(self, node):
272                 flows = _rml_flowable(self.doc, images=self.images, path=self.path).render(node)
273                 infos = utils.attr_get(node, ['x','y','width','height'])
274
275                 infos['y']+=infos['height']
276                 for flow in flows:
277                         w,h = flow.wrap(infos['width'], infos['height'])
278                         if w<=infos['width'] and h<=infos['height']:
279                                 infos['y']-=h
280                                 flow.drawOn(self.canvas,infos['x'],infos['y'])
281                                 infos['height']-=h
282                         else:
283                                 raise ValueError, "Not enough space"
284
285         def _line_mode(self, node):
286                 ljoin = {'round':1, 'mitered':0, 'bevelled':2}
287                 lcap = {'default':0, 'round':1, 'square':2}
288                 if node.hasAttribute('width'):
289                         self.canvas.setLineWidth(utils.unit_get(node.getAttribute('width')))
290                 if node.hasAttribute('join'):
291                         self.canvas.setLineJoin(ljoin[node.getAttribute('join')])
292                 if node.hasAttribute('cap'):
293                         self.canvas.setLineCap(lcap[node.getAttribute('cap')])
294                 if node.hasAttribute('miterLimit'):
295                         self.canvas.setDash(utils.unit_get(node.getAttribute('miterLimit')))
296                 if node.hasAttribute('dash'):
297                         dashes = node.getAttribute('dash').split(',')
298                         for x in range(len(dashes)):
299                                 dashes[x]=utils.unit_get(dashes[x])
300                         self.canvas.setDash(node.getAttribute('dash').split(','))
301
302         def _image(self, node):
303                 import urllib
304                 from reportlab.lib.utils import ImageReader
305
306                 if not node.hasAttribute('file'):
307                         if node.getAttribute('name') in self.images:
308                                 scontent = self.images[node.getAttribute('name')]
309                                 s = StringIO.StringIO(scontent)
310                         else:
311                                 return True
312                 else:
313                         if node.getAttribute('file') in self.images:
314                                 s = StringIO.StringIO(self.images[node.getAttribute('file')])
315                         else:
316                                 try:
317                                         u = urllib.urlopen(str(node.getAttribute('file')))
318                                         s = StringIO.StringIO(u.read())
319                                 except:
320                                         u = file(os.path.join(self.path,str(node.getAttribute('file'))), 'rb')
321                                         s = StringIO.StringIO(u.read())
322                 img = ImageReader(s)
323                 (sx,sy) = img.getSize()
324
325                 args = {}
326                 for tag in ('width','height','x','y'):
327                         if node.hasAttribute(tag):
328                                 args[tag] = utils.unit_get(node.getAttribute(tag))
329                 if ('width' in args) and (not 'height' in args):
330                         args['height'] = sy * args['width'] / sx
331                 elif ('height' in args) and (not 'width' in args):
332                         args['width'] = sx * args['height'] / sy
333                 elif ('width' in args) and ('height' in args):
334                         if (float(args['width'])/args['height'])>(float(sx)>sy):
335                                 args['width'] = sx * args['height'] / sy
336                         else:
337                                 args['height'] = sy * args['width'] / sx
338                 self.canvas.drawImage(img, **args)
339
340         def _path(self, node):
341                 self.path = self.canvas.beginPath()
342                 self.path.moveTo(**utils.attr_get(node, ['x','y']))
343                 for n in node.childNodes:
344                         if n.nodeType == node.ELEMENT_NODE:
345                                 if n.localName=='moveto':
346                                         vals = utils.text_get(n).split()
347                                         self.path.moveTo(utils.unit_get(vals[0]), utils.unit_get(vals[1]))
348                                 elif n.localName=='curvesto':
349                                         vals = utils.text_get(n).split()
350                                         while len(vals)>5:
351                                                 pos=[]
352                                                 while len(pos)<6:
353                                                         pos.append(utils.unit_get(vals.pop(0)))
354                                                 self.path.curveTo(*pos)
355                         elif (n.nodeType == node.TEXT_NODE):
356                                 data = n.data.split()               # Not sure if I must merge all TEXT_NODE ?
357                                 while len(data)>1:
358                                         x = utils.unit_get(data.pop(0))
359                                         y = utils.unit_get(data.pop(0))
360                                         self.path.lineTo(x,y)
361                 if (not node.hasAttribute('close')) or utils.bool_get(node.getAttribute('close')):
362                         self.path.close()
363                 self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
364
365         def render(self, node):
366                 tags = {
367                         'drawCentredString': self._drawCenteredString,
368                         'drawRightString': self._drawRightString,
369                         'drawString': self._drawString,
370                         'rect': self._rect,
371                         'ellipse': self._ellipse,
372                         'lines': self._lines,
373                         'grid': self._grid,
374                         'curves': self._curves,
375                         'fill': lambda node: self.canvas.setFillColor(color.get(node.getAttribute('color'))),
376                         'stroke': lambda node: self.canvas.setStrokeColor(color.get(node.getAttribute('color'))),
377                         'setFont': lambda node: self.canvas.setFont(node.getAttribute('name'), utils.unit_get(node.getAttribute('size'))),
378                         'place': self._place,
379                         'circle': self._circle,
380                         'lineMode': self._line_mode,
381                         'path': self._path,
382                         'rotate': lambda node: self.canvas.rotate(float(node.getAttribute('degrees'))),
383                         'translate': self._translate,
384                         'image': self._image
385                 }
386                 for nd in node.childNodes:
387                         if nd.nodeType==nd.ELEMENT_NODE:
388                                 for tag in tags:
389                                         if nd.localName==tag:
390                                                 tags[tag](nd)
391                                                 break
392
393 class _rml_draw(object):
394         def __init__(self, node, styles, images={}, path='.'):
395                 self.node = node
396                 self.styles = styles
397                 self.canvas = None
398                 self.images = images
399                 self.path = path
400
401         def render(self, canvas, doc):
402                 canvas.saveState()
403                 cnv = _rml_canvas(canvas, doc, self.styles, images=self.images, path=self.path)
404                 cnv.render(self.node)
405                 canvas.restoreState()
406
407 class _rml_flowable(object):
408         def __init__(self, doc, images={}, path='.'):
409                 self.doc = doc
410                 self.styles = doc.styles
411                 self.images = images
412                 self.path = path
413
414         def _textual(self, node):
415                 rc = ''
416                 for n in node.childNodes:
417                         if n.nodeType == node.ELEMENT_NODE:
418                                 if n.localName == 'getName':
419                                         newNode = self.doc.dom.createTextNode(self.styles.names.get(n.getAttribute('id'),'Unknown name'))
420                                         node.insertBefore(newNode, n)
421                                         node.removeChild(n)
422                                         n = newNode
423                                 elif n.localName == 'pageNumber':
424                                         newNode = self.doc.dom.createTextNode(str(self.doc.canvas.getPageNumber()))
425                                         node.insertBefore(newNode, n)
426                                         node.removeChild(n)
427                                         n = newNode
428                                 else:
429                                         #CHECKME: I wonder if this is useful since we don't stock the result. Maybe for the getName tag?
430                                         self._textual(n)
431                                 rc += n.toxml()
432                         elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
433                                 rc += str2xml(n.data)
434                 return rc.encode(encoding, 'replace')
435
436         def _table(self, node):
437                 length = 0
438                 colwidths = None
439                 rowheights = None
440                 data = []
441                 childs = _child_get(node,'tr')
442                 if not childs:
443                         return None
444                 for tr in childs:
445                         data2 = []
446                         for td in _child_get(tr, 'td'):
447                                 flow = []
448                                 for n in td.childNodes:
449                                         if n.nodeType==node.ELEMENT_NODE:
450                                                 flow.append( self._flowable(n) )
451                                 if not len(flow):
452                                         flow = self._textual(td)
453                                 data2.append( flow )
454                         if len(data2)>length:
455                                 length=len(data2)
456                                 for ab in data:
457                                         while len(ab)<length:
458                                                 ab.append('')
459                         while len(data2)<length:
460                                 data2.append('')
461                         data.append( data2 )
462                 if node.hasAttribute('colWidths'):
463                         assert length == len(node.getAttribute('colWidths').split(','))
464                         colwidths = [utils.unit_get(f.strip()) for f in node.getAttribute('colWidths').split(',')]
465                 if node.hasAttribute('rowHeights'):
466                         rowheights = [utils.unit_get(f.strip()) for f in node.getAttribute('rowHeights').split(',')]
467                         if len(rowheights) == 1:
468                                 rowheights = rowheights[0]
469                 table = platypus.Table(data = data, colWidths=colwidths, rowHeights=rowheights, **(utils.attr_get(node, ['splitByRow'] ,{'repeatRows':'int','repeatCols':'int'})))
470                 if node.hasAttribute('style'):
471                         table.setStyle(self.styles.table_styles[node.getAttribute('style')])
472                 return table
473
474         def _illustration(self, node):
475                 class Illustration(platypus.flowables.Flowable):
476                         def __init__(self, node, styles, self2):
477                                 self.node = node
478                                 self.styles = styles
479                                 self.width = utils.unit_get(node.getAttribute('width'))
480                                 self.height = utils.unit_get(node.getAttribute('height'))
481                                 self.self2 = self2
482                         def wrap(self, *args):
483                                 return (self.width, self.height)
484                         def draw(self):
485                                 canvas = self.canv
486                                 drw = _rml_draw(self.node, self.styles, images=self.self2.images, path=self.self2.path)
487                                 drw.render(self.canv, None)
488                 return Illustration(node, self.styles, self)
489
490         def _textual_image(self, node):
491                 import base64
492                 rc = ''
493                 for n in node.childNodes:
494                         if n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
495                                 rc += n.data
496                 return base64.decodestring(rc)
497
498         def _flowable(self, node):
499                 if node.localName=='para':
500                         style = self.styles.para_style_get(node)
501                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
502                 elif node.localName=='barCode':
503                         try:
504                                 from reportlab.graphics.barcode import code128
505                                 from reportlab.graphics.barcode import code39
506                                 from reportlab.graphics.barcode import code93
507                                 from reportlab.graphics.barcode import common
508                                 from reportlab.graphics.barcode import fourstate
509                                 from reportlab.graphics.barcode import usps
510                         except Exception, e:
511                                 print 'Warning: Reportlab barcode extension not installed !'
512                                 return None
513                         args = utils.attr_get(node, [], {'ratio':'float','xdim':'unit','height':'unit','checksum':'bool','quiet':'bool'})
514                         codes = {
515                                 'codabar': lambda x: common.Codabar(x, **args),
516                                 'code11': lambda x: common.Code11(x, **args),
517                                 'code128': lambda x: code128.Code128(x, **args),
518                                 'standard39': lambda x: code39.Standard39(x, **args),
519                                 'standard93': lambda x: code93.Standard93(x, **args),
520                                 'i2of5': lambda x: common.I2of5(x, **args),
521                                 'extended39': lambda x: code39.Extended39(x, **args),
522                                 'extended93': lambda x: code93.Extended93(x, **args),
523                                 'msi': lambda x: common.MSI(x, **args),
524                                 'fim': lambda x: usps.FIM(x, **args),
525                                 'postnet': lambda x: usps.POSTNET(x, **args),
526                         }
527                         code = 'code128'
528                         if node.hasAttribute('code'):
529                                 code = node.getAttribute('code').lower()
530                         return codes[code](self._textual(node))
531                 elif node.localName=='name':
532                         self.styles.names[ node.getAttribute('id')] = node.getAttribute('value')
533                         return None
534                 elif node.localName=='xpre':
535                         style = self.styles.para_style_get(node)
536                         return platypus.XPreformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int','frags':'int'})))
537                 elif node.localName=='pre':
538                         style = self.styles.para_style_get(node)
539                         return platypus.Preformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int'})))
540                 elif node.localName=='illustration':
541                         return  self._illustration(node)
542                 elif node.localName=='blockTable':
543                         return  self._table(node)
544                 elif node.localName=='title':
545                         styles = reportlab.lib.styles.getSampleStyleSheet()
546                         style = styles['Title']
547                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
548                 elif node.localName=='h1':
549                         styles = reportlab.lib.styles.getSampleStyleSheet()
550                         style = styles['Heading1']
551                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
552                 elif node.localName=='h2':
553                         styles = reportlab.lib.styles.getSampleStyleSheet()
554                         style = styles['Heading2']
555                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
556                 elif node.localName=='h3':
557                         styles = reportlab.lib.styles.getSampleStyleSheet()
558                         style = styles['Heading3']
559                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
560                 elif node.localName=='image':
561                         if not node.hasAttribute('file'):
562                                 if node.hasAttribute('name'):
563                                         image_data = self.doc.images[node.getAttribute('name')].read()
564                                 else:
565                                         import base64
566                                         image_data = base64.decodestring(node.firstChild.nodeValue)
567                                 image = StringIO.StringIO(image_data)
568                                 return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
569                         else:
570                                 return platypus.Image(node.getAttribute('file'), mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
571
572                         from reportlab.lib.utils import ImageReader
573                         name = str(node.getAttribute('file'))
574                         img = ImageReader(name)
575                         (sx,sy) = img.getSize()
576
577                         args = {}
578                         for tag in ('width','height'):
579                                 if node.hasAttribute(tag):
580                                         args[tag] = utils.unit_get(node.getAttribute(tag))
581                         if ('width' in args) and (not 'height' in args):
582                                 args['height'] = sy * args['width'] / sx
583                         elif ('height' in args) and (not 'width' in args):
584                                 args['width'] = sx * args['height'] / sy
585                         elif ('width' in args) and ('height' in args):
586                                 if (float(args['width'])/args['height'])>(float(sx)>sy):
587                                         args['width'] = sx * args['height'] / sy
588                                 else:
589                                         args['height'] = sy * args['width'] / sx
590                         return platypus.Image(name, mask=(250,255,250,255,250,255), **args)
591                 elif node.localName=='spacer':
592                         if node.hasAttribute('width'):
593                                 width = utils.unit_get(node.getAttribute('width'))
594                         else:
595                                 width = utils.unit_get('1cm')
596                         length = utils.unit_get(node.getAttribute('length'))
597                         return platypus.Spacer(width=width, height=length)
598                 elif node.localName=='section':
599                         return self.render(node)
600                 elif node.localName in ('pageBreak', 'nextPage'):
601                         return platypus.PageBreak()
602                 elif node.localName=='condPageBreak':
603                         return platypus.CondPageBreak(**(utils.attr_get(node, ['height'])))
604                 elif node.localName=='setNextTemplate':
605                         return platypus.NextPageTemplate(str(node.getAttribute('name')))
606                 elif node.localName=='nextFrame':
607                         return platypus.CondPageBreak(1000)           # TODO: change the 1000 !
608                 elif node.localName == 'setNextFrame':
609                         from reportlab.platypus.doctemplate import NextFrameFlowable
610                         return NextFrameFlowable(str(node.getAttribute('name')))
611                 elif node.localName == 'currentFrame':
612                         from reportlab.platypus.doctemplate import CurrentFrameFlowable
613                         return CurrentFrameFlowable(str(node.getAttribute('name')))
614                 elif node.localName == 'frameEnd':
615                         return EndFrameFlowable()
616                 else:
617                         sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.localName,))
618                         return None
619
620         def render(self, node_story):
621                 story = []
622                 node = node_story.firstChild
623                 while node:
624                         if node.nodeType == node.ELEMENT_NODE:
625                                 flow = self._flowable(node) 
626                                 if flow:
627                                         if type(flow) == type([]):
628                                                 story = story + flow
629                                         else:
630                                                 story.append(flow)
631                         node = node.nextSibling
632                 return story
633
634 from reportlab.platypus.doctemplate import ActionFlowable
635
636 class EndFrameFlowable(ActionFlowable):
637         def __init__(self,resume=0):
638                 ActionFlowable.__init__(self,('frameEnd',resume))
639
640 class TinyDocTemplate(platypus.BaseDocTemplate):
641
642         def ___handle_pageBegin(self):
643                 self.page = self.page + 1
644                 self.pageTemplate.beforeDrawPage(self.canv,self)
645                 self.pageTemplate.checkPageSize(self.canv,self)
646                 self.pageTemplate.onPage(self.canv,self)
647                 for f in self.pageTemplate.frames: f._reset()
648                 self.beforePage()
649                 #keep a count of flowables added to this page.  zero indicates bad stuff
650                 self._curPageFlowableCount = 0
651                 if hasattr(self,'_nextFrameIndex'):
652                         del self._nextFrameIndex
653                 for f in self.pageTemplate.frames:
654                         if f.id == 'first':
655                                 self.frame = f
656                                 break
657                 self.handle_frameBegin()
658
659 class _rml_template(object):
660         def __init__(self, out, node, doc, images={}, path='.'):
661                 self.images= images
662                 self.path = path
663                 if not node.hasAttribute('pageSize'):
664                         pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm'))
665                 else:
666                         ps = map(lambda x:x.strip(), node.getAttribute('pageSize').replace(')', '').replace('(', '').split(','))
667                         pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
668                 cm = reportlab.lib.units.cm
669                 self.doc_tmpl = TinyDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','title':'str','author':'str'}))
670                 self.page_templates = []
671                 self.styles = doc.styles
672                 self.doc = doc
673                 pts = node.getElementsByTagName('pageTemplate')
674                 for pt in pts:
675                         frames = []
676                         for frame_el in pt.getElementsByTagName('frame'):
677                                 frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'str', 'showBoundary':'bool'})) )
678                                 if utils.attr_get(frame_el, ['last']):
679                                         frame.lastFrame = True
680                                 frames.append( frame )
681                         gr = pt.getElementsByTagName('pageGraphics')
682                         if len(gr):
683                                 drw = _rml_draw(gr[0], self.doc, images=images, path=self.path)
684                                 self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
685                         else:
686                                 self.page_templates.append( platypus.PageTemplate(frames=frames, **utils.attr_get(pt, [], {'id':'str'}) ))
687                 self.doc_tmpl.addPageTemplates(self.page_templates)
688
689         def render(self, node_stories):
690                 fis = []
691                 r = _rml_flowable(self.doc,images=self.images, path=self.path)
692                 for node_story in node_stories:
693                         fis += r.render(node_story)
694                         fis.append(platypus.PageBreak())
695                 self.doc_tmpl.build(fis)
696
697 def parseString(data, fout=None, images={}, path='.'):
698         r = _rml_doc(data, images, path)
699         if fout:
700                 fp = file(fout,'wb')
701                 r.render(fp)
702                 fp.close()
703                 return fout
704         else:
705                 fp = StringIO.StringIO()
706                 r.render(fp)
707                 return fp.getvalue()
708
709 def trml2pdf_help():
710         print 'Usage: trml2pdf input.rml >output.pdf'
711         print 'Render the standard input (RML) and output a PDF file'
712         sys.exit(0)
713
714 if __name__=="__main__":
715         if len(sys.argv)>1:
716                 if sys.argv[1]=='--help':
717                         trml2pdf_help()
718                 print parseString(file(sys.argv[1], 'r').read())
719         else:
720                 print 'Usage: trml2pdf input.rml >output.pdf'
721                 print 'Try \'trml2pdf --help\' for more information.'