summaryrefslogtreecommitdiff
path: root/bin/report/render
diff options
context:
space:
mode:
authorpinky <>2006-12-07 13:41:40 +0000
committerpinky <>2006-12-07 13:41:40 +0000
commit004a0b996ff8f269451e07346f71a129a1f3fbaf (patch)
tree48f43c1fc2ca4c1f4d5648e7e73553d3f4ccae06 /bin/report/render
New trunk
bzr revid: pinky-3f10ee12cea3c4c75cef44ab04ad33ef47432907
Diffstat (limited to 'bin/report/render')
-rw-r--r--bin/report/render/__init__.py37
-rw-r--r--bin/report/render/render.py80
-rw-r--r--bin/report/render/rml.py50
-rw-r--r--bin/report/render/rml2html/__init__.py18
-rwxr-xr-xbin/report/render/rml2html/rml2html.py365
-rw-r--r--bin/report/render/rml2html/utils.py67
-rw-r--r--bin/report/render/rml2pdf/__init__.py46
-rw-r--r--bin/report/render/rml2pdf/color.py36
-rwxr-xr-xbin/report/render/rml2pdf/trml2pdf.py640
-rw-r--r--bin/report/render/rml2pdf/utils.py93
-rw-r--r--bin/report/render/simple.py90
11 files changed, 1522 insertions, 0 deletions
diff --git a/bin/report/render/__init__.py b/bin/report/render/__init__.py
new file mode 100644
index 00000000000..9dd43fdc44b
--- /dev/null
+++ b/bin/report/render/__init__.py
@@ -0,0 +1,37 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+from simple import simple
+from rml import rml, rml2html
+
+from render import render
+
+try:
+ import Image
+except:
+ print 'WARNING; Python Imaging not installed, you can use only .JPG pictures !'
diff --git a/bin/report/render/render.py b/bin/report/render/render.py
new file mode 100644
index 00000000000..437f8f7c567
--- /dev/null
+++ b/bin/report/render/render.py
@@ -0,0 +1,80 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+# Why doing some multi-thread instead of using OSE capabilities ?
+# For progress bar.
+
+#
+# Add a transparant multi-thread layer to all report rendering layers
+#
+
+import threading
+
+#
+# TODO: method to stock on the disk
+# Les class de reporting doivent surclasser cette classe
+# Les seules methodes qui peuvent etre redefinies sont:
+# __init__
+# _render
+#
+class render(object):
+ def __init__(self, bin_datas={}, path='.'):
+ self.done = False
+ self.bin_datas = bin_datas
+ self.path = path
+
+ def _render(self):
+ return None
+
+ def render(self):
+ self.done = False
+ result = self._render()
+ self._result = result
+ self.done = True
+ return True
+
+ def is_done(self):
+ res = self.done
+ return res
+
+ def get(self):
+ if self.is_done():
+ return self._result
+ else:
+ return None
+
+if __name__=='__main__':
+ import time
+ print 'Multi-thread code !'
+ r = render()
+ r.render()
+ while not r.is_done():
+ print 'not yet!'
+ time.sleep(1)
+ print 'done!'
+
diff --git a/bin/report/render/rml.py b/bin/report/render/rml.py
new file mode 100644
index 00000000000..ae43600ce23
--- /dev/null
+++ b/bin/report/render/rml.py
@@ -0,0 +1,50 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import render
+import rml2pdf
+import rml2html as htmlizer
+
+class rml(render.render):
+ def __init__(self, xml, datas={}, path='.'):
+ render.render.__init__(self, datas)
+ self.xml = xml
+ self.output_type = 'pdf'
+ self.path = path
+
+ def _render(self):
+ return rml2pdf.parseString(self.xml, images=self.bin_datas, path=self.path)
+
+class rml2html(render.render):
+ def __init__(self, xml, datas={}):
+ super(rml2html, self).__init__(datas)
+ self.xml = xml
+ self.output_type = 'html'
+
+ def _render(self):
+ return htmlizer.parseString(self.xml)
diff --git a/bin/report/render/rml2html/__init__.py b/bin/report/render/rml2html/__init__.py
new file mode 100644
index 00000000000..8c8e89f48e5
--- /dev/null
+++ b/bin/report/render/rml2html/__init__.py
@@ -0,0 +1,18 @@
+# trml2pdf - An RML to PDF converter
+# Copyright (C) 2003, Fabien Pinckaers, UCL, FSA
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from rml2html import parseString
diff --git a/bin/report/render/rml2html/rml2html.py b/bin/report/render/rml2html/rml2html.py
new file mode 100755
index 00000000000..868422fe5b4
--- /dev/null
+++ b/bin/report/render/rml2html/rml2html.py
@@ -0,0 +1,365 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2005, Fabien Pinckaers, UCL, FSA
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import sys
+import StringIO
+import xml.dom.minidom
+import copy
+
+import utils
+
+class _flowable(object):
+ def __init__(self, template, doc):
+ self._tags = {
+ 'title': self._tag_title,
+ 'spacer': self._tag_spacer,
+ 'para': self._tag_para,
+ 'nextFrame': self._tag_next_frame,
+ 'blockTable': self._tag_table,
+ 'pageBreak': self._tag_page_break,
+ 'setNextTemplate': self._tag_next_template,
+ }
+ self.template = template
+ self.doc = doc
+
+ def _tag_page_break(self, node):
+ return '<br/>'*3
+
+ def _tag_next_template(self, node):
+ return ''
+
+ def _tag_next_frame(self, node):
+ result=self.template.frame_stop()
+ result+='<br/>'
+ result+=self.template.frame_start()
+ return result
+
+ def _tag_title(self, node):
+ node.tagName='h1'
+ return node.toxml()
+
+ def _tag_spacer(self, node):
+ length = 1+int(utils.unit_get(node.getAttribute('length')))/35
+ return "<br/>"*length
+
+ def _tag_table(self, node):
+ node.tagName='table'
+ if node.hasAttribute('colWidths'):
+ sizes = map(lambda x: utils.unit_get(x), node.getAttribute('colWidths').split(','))
+ tr = self.doc.createElement('tr')
+ for s in sizes:
+ td = self.doc.createElement('td')
+ td.setAttribute("width", str(s))
+ tr.appendChild(td)
+ node.appendChild(tr)
+ return node.toxml()
+
+ def _tag_para(self, node):
+ node.tagName='p'
+ if node.hasAttribute('style'):
+ node.setAttribute('class', node.getAttribute('style'))
+ return node.toxml()
+
+ def render(self, node):
+ result = self.template.start()
+ result += self.template.frame_start()
+ for n in node.childNodes:
+ if n.nodeType==node.ELEMENT_NODE:
+ if n.localName in self._tags:
+ result += self._tags[n.localName](n)
+ else:
+ pass
+ #print 'tag', n.localName, 'not yet implemented!'
+ result += self.template.frame_stop()
+ result += self.template.end()
+ return result
+
+class _rml_tmpl_tag(object):
+ def __init__(self, *args):
+ pass
+ def tag_start(self):
+ return ''
+ def tag_end(self):
+ return False
+ def tag_stop(self):
+ return ''
+ def tag_mergeable(self):
+ return True
+
+class _rml_tmpl_frame(_rml_tmpl_tag):
+ def __init__(self, posx, width):
+ self.width = width
+ self.posx = posx
+ def tag_start(self):
+ return '<table border="0" width="%d"><tr><td width="%d">&nbsp;</td><td>' % (self.width+self.posx,self.posx)
+ def tag_end(self):
+ return True
+ def tag_stop(self):
+ return '</td></tr></table><br/>'
+ def tag_mergeable(self):
+ return False
+
+ # An awfull workaround since I don't really understand the semantic behind merge.
+ def merge(self, frame):
+ pass
+
+class _rml_tmpl_draw_string(_rml_tmpl_tag):
+ def __init__(self, node, style):
+ self.posx = utils.unit_get(node.getAttribute('x'))
+ self.posy = utils.unit_get(node.getAttribute('y'))
+ aligns = {
+ 'drawString': 'left',
+ 'drawRightString': 'right',
+ 'drawCentredString': 'center'
+ }
+ align = aligns[node.localName]
+ self.pos = [(self.posx, self.posy, align, utils.text_get(node), style.get('td'), style.font_size_get('td'))]
+
+ def tag_start(self):
+ self.pos.sort()
+ res = '<table border="0" cellpadding="0" cellspacing="0"><tr>'
+ posx = 0
+ i = 0
+ for (x,y,align,txt, style, fs) in self.pos:
+ if align=="left":
+ pos2 = len(txt)*fs
+ res+='<td width="%d"></td><td style="%s" width="%d">%s</td>' % (x - posx, style, pos2, txt)
+ posx = x+pos2
+ if align=="right":
+ res+='<td width="%d" align="right" style="%s">%s</td>' % (x - posx, style, txt)
+ posx = x
+ if align=="center":
+ res+='<td width="%d" align="center" style="%s">%s</td>' % ((x - posx)*2, style, txt)
+ posx = 2*x-posx
+ i+=1
+ res+='</tr></table>'
+ return res
+ def merge(self, ds):
+ self.pos+=ds.pos
+
+class _rml_tmpl_draw_lines(_rml_tmpl_tag):
+ def __init__(self, node, style):
+ coord = [utils.unit_get(x) for x in utils.text_get(node).split(' ')]
+ self.ok = False
+ self.posx = coord[0]
+ self.posy = coord[1]
+ self.width = coord[2]-coord[0]
+ self.ok = coord[1]==coord[3]
+ self.style = style
+ self.style = style.get('hr')
+
+ def tag_start(self):
+ if self.ok:
+ 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)
+ else:
+ return ''
+
+class _rml_stylesheet(object):
+ def __init__(self, stylesheet, doc):
+ self.doc = doc
+ self.attrs = {}
+ self._tags = {
+ 'fontSize': lambda x: ('font-size',str(utils.unit_get(x))+'px'),
+ 'alignment': lambda x: ('text-align',str(x))
+ }
+ result = ''
+ for ps in stylesheet.getElementsByTagName('paraStyle'):
+ attr = {}
+ attrs = ps.attributes
+ for i in range(attrs.length):
+ name = attrs.item(i).localName
+ attr[name] = ps.getAttribute(name)
+ attrs = []
+ for a in attr:
+ if a in self._tags:
+ attrs.append("%s:%s" % self._tags[a](attr[a]))
+ if len(attrs):
+ result += "p."+attr['name']+" {"+'; '.join(attrs)+"}\n"
+ self.result = result
+
+ def render(self):
+ return self.result
+
+class _rml_draw_style(object):
+ def __init__(self):
+ self.style = {}
+ self._styles = {
+ 'fill': lambda x: {'td': {'color':x.getAttribute('color')}},
+ 'setFont': lambda x: {'td': {'font-size':x.getAttribute('size')+'px'}},
+ 'stroke': lambda x: {'hr': {'color':x.getAttribute('color')}},
+ }
+ def update(self, node):
+ if node.localName in self._styles:
+ result = self._styles[node.localName](node)
+ for key in result:
+ if key in self.style:
+ self.style[key].update(result[key])
+ else:
+ self.style[key] = result[key]
+ def font_size_get(self,tag):
+ size = utils.unit_get(self.style.get('td', {}).get('font-size','16'))
+ return size
+
+ def get(self,tag):
+ if not tag in self.style:
+ return ""
+ return ';'.join(['%s:%s' % (x[0],x[1]) for x in self.style[tag].items()])
+
+class _rml_template(object):
+ def __init__(self, template):
+ self.frame_pos = -1
+ self.frames = []
+ self.template_order = []
+ self.page_template = {}
+ self.loop = 0
+ self._tags = {
+ 'drawString': _rml_tmpl_draw_string,
+ 'drawRightString': _rml_tmpl_draw_string,
+ 'drawCentredString': _rml_tmpl_draw_string,
+ 'lines': _rml_tmpl_draw_lines
+ }
+ self.style = _rml_draw_style()
+ for pt in template.getElementsByTagName('pageTemplate'):
+ frames = {}
+ id = pt.getAttribute('id')
+ self.template_order.append(id)
+ for tmpl in pt.getElementsByTagName('frame'):
+ posy = int(utils.unit_get(tmpl.getAttribute('y1'))) #+utils.unit_get(tmpl.getAttribute('height')))
+ posx = int(utils.unit_get(tmpl.getAttribute('x1')))
+ frames[(posy,posx,tmpl.getAttribute('id'))] = _rml_tmpl_frame(posx, utils.unit_get(tmpl.getAttribute('width')))
+ for tmpl in template.getElementsByTagName('pageGraphics'):
+ for n in tmpl.childNodes:
+ if n.nodeType==n.ELEMENT_NODE:
+ if n.localName in self._tags:
+ t = self._tags[n.localName](n, self.style)
+ frames[(t.posy,t.posx,n.localName)] = t
+ else:
+ self.style.update(n)
+ keys = frames.keys()
+ keys.sort()
+ keys.reverse()
+ self.page_template[id] = []
+ for key in range(len(keys)):
+ if key>0 and keys[key-1][0] == keys[key][0]:
+ if type(self.page_template[id][-1]) == type(frames[keys[key]]):
+ if self.page_template[id][-1].tag_mergeable():
+ self.page_template[id][-1].merge(frames[keys[key]])
+ continue
+ self.page_template[id].append(frames[keys[key]])
+ self.template = self.template_order[0]
+
+ def _get_style(self):
+ return self.style
+
+ def set_next_template(self):
+ self.template = self.template_order[(self.template_order.index(name)+1) % self.template_order]
+ self.frame_pos = -1
+
+ def set_template(self, name):
+ self.template = name
+ self.frame_pos = -1
+
+ def frame_start(self):
+ result = ''
+ frames = self.page_template[self.template]
+ ok = True
+ while ok:
+ self.frame_pos += 1
+ if self.frame_pos>=len(frames):
+ self.frame_pos=0
+ self.loop=1
+ ok = False
+ continue
+ f = frames[self.frame_pos]
+ result+=f.tag_start()
+ ok = not f.tag_end()
+ if ok:
+ result+=f.tag_stop()
+ return result
+
+ def frame_stop(self):
+ frames = self.page_template[self.template]
+ f = frames[self.frame_pos]
+ result=f.tag_stop()
+ return result
+
+ def start(self):
+ return ''
+
+ def end(self):
+ result = ''
+ while not self.loop:
+ result += self.frame_start()
+ result += self.frame_stop()
+ return result
+
+class _rml_doc(object):
+ def __init__(self, data):
+ self.dom = xml.dom.minidom.parseString(data)
+ self.filename = self.dom.documentElement.getAttribute('filename')
+ self.result = ''
+
+ def render(self, out):
+ self.result += '''<!DOCTYPE HTML PUBLIC "-//w3c//DTD HTML 4.0 Frameset//EN">
+<html>
+<head>
+ <style type="text/css">
+ p {margin:0px; font-size:12px;}
+ td {font-size:14px;}
+'''
+ style = self.dom.documentElement.getElementsByTagName('stylesheet')[0]
+ s = _rml_stylesheet(style, self.dom)
+ self.result += s.render()
+ self.result+='''
+ </style>
+</head>
+<body>'''
+
+ template = _rml_template(self.dom.documentElement.getElementsByTagName('template')[0])
+ f = _flowable(template, self.dom)
+ self.result += f.render(self.dom.documentElement.getElementsByTagName('story')[0])
+ del f
+ self.result += '</body></html>'
+ out.write( self.result)
+
+def parseString(data, fout=None):
+ r = _rml_doc(data)
+ if fout:
+ fp = file(fout,'wb')
+ r.render(fp)
+ fp.close()
+ return fout
+ else:
+ fp = StringIO.StringIO()
+ r.render(fp)
+ return fp.getvalue()
+
+def trml2pdf_help():
+ print 'Usage: rml2html input.rml >output.html'
+ print 'Render the standard input (RML) and output an HTML file'
+ sys.exit(0)
+
+if __name__=="__main__":
+ if len(sys.argv)>1:
+ if sys.argv[1]=='--help':
+ trml2pdf_help()
+ print parseString(file(sys.argv[1], 'r').read()),
+ else:
+ print 'Usage: trml2pdf input.rml >output.pdf'
+ print 'Try \'trml2pdf --help\' for more information.'
diff --git a/bin/report/render/rml2html/utils.py b/bin/report/render/rml2html/utils.py
new file mode 100644
index 00000000000..4ddc6ca38a0
--- /dev/null
+++ b/bin/report/render/rml2html/utils.py
@@ -0,0 +1,67 @@
+# trml2pdf - An RML to PDF converter
+# Copyright (C) 2003, Fabien Pinckaers, UCL, FSA
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import re
+import reportlab
+import reportlab.lib.units
+
+def text_get(node):
+ rc = ''
+ for node in node.childNodes:
+ if node.nodeType == node.TEXT_NODE:
+ rc = rc + node.data
+ return rc
+
+units = [
+ (re.compile('^(-?[0-9\.]+)\s*in$'), reportlab.lib.units.inch),
+ (re.compile('^(-?[0-9\.]+)\s*cm$'), reportlab.lib.units.cm),
+ (re.compile('^(-?[0-9\.]+)\s*mm$'), reportlab.lib.units.mm),
+ (re.compile('^(-?[0-9\.]+)\s*px$'), 0.7),
+ (re.compile('^(-?[0-9\.]+)\s*$'), 1)
+]
+
+def unit_get(size):
+ global units
+ for unit in units:
+ res = unit[0].search(size, 0)
+ if res:
+ return int(unit[1]*float(res.group(1))*1.3)
+ return False
+
+def tuple_int_get(node, attr_name, default=None):
+ if not node.hasAttribute(attr_name):
+ return default
+ res = [int(x) for x in node.getAttribute(attr_name).split(',')]
+ return res
+
+def bool_get(value):
+ return (str(value)=="1") or (value.lower()=='yes')
+
+def attr_get(node, attrs, dict={}):
+ res = {}
+ for name in attrs:
+ if node.hasAttribute(name):
+ res[name] = unit_get(node.getAttribute(name))
+ for key in dict:
+ if node.hasAttribute(key):
+ if dict[key]=='str':
+ res[key] = str(node.getAttribute(key))
+ elif dict[key]=='bool':
+ res[key] = bool_get(node.getAttribute(key))
+ elif dict[key]=='int':
+ res[key] = int(node.getAttribute(key))
+ return res
diff --git a/bin/report/render/rml2pdf/__init__.py b/bin/report/render/rml2pdf/__init__.py
new file mode 100644
index 00000000000..dd436c08cad
--- /dev/null
+++ b/bin/report/render/rml2pdf/__init__.py
@@ -0,0 +1,46 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+# trml2pdf - An RML to PDF converter
+# Copyright (C) 2003, Fabien Pinckaers, UCL, FSA
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from trml2pdf import parseString
diff --git a/bin/report/render/rml2pdf/color.py b/bin/report/render/rml2pdf/color.py
new file mode 100644
index 00000000000..b321f9dff0a
--- /dev/null
+++ b/bin/report/render/rml2pdf/color.py
@@ -0,0 +1,36 @@
+# trml2pdf - An RML to PDF converter
+# Copyright (C) 2003, Fabien Pinckaers, UCL, FSA
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+from reportlab.lib import colors
+import re
+
+allcols = colors.getAllNamedColors()
+
+regex_t = re.compile('\(([0-9\.]*),([0-9\.]*),([0-9\.]*)\)')
+regex_h = re.compile('#([0-9a-zA-Z][0-9a-zA-Z])([0-9a-zA-Z][0-9a-zA-Z])([0-9a-zA-Z][0-9a-zA-Z])')
+
+def get(col_str):
+ global allcols
+ if col_str in allcols.keys():
+ return allcols[col_str]
+ res = regex_t.search(col_str, 0)
+ if res:
+ return (float(res.group(1)),float(res.group(2)),float(res.group(3)))
+ res = regex_h.search(col_str, 0)
+ if res:
+ return tuple([ float(int(res.group(i),16))/255 for i in range(1,4)])
+ return colors.red
diff --git a/bin/report/render/rml2pdf/trml2pdf.py b/bin/report/render/rml2pdf/trml2pdf.py
new file mode 100755
index 00000000000..aeec85dc2ef
--- /dev/null
+++ b/bin/report/render/rml2pdf/trml2pdf.py
@@ -0,0 +1,640 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# trml2pdf - An RML to PDF converter
+# Copyright (C) 2003, Fabien Pinckaers, UCL, FSA
+# Contributors
+# Richard Waid <richard@iopen.net>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import sys
+import StringIO
+import xml.dom.minidom
+import copy
+
+import reportlab
+from reportlab.pdfgen import canvas
+from reportlab import platypus
+
+import utils
+import color
+import os
+
+#
+# Change this to UTF-8 if you plan tu use Reportlab's UTF-8 support
+#
+# reportlab use "code page 1252" encoding by default. cfr reportlab user guide p.46
+encoding = 'cp1252'
+
+def str2xml(s):
+ return s.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
+
+def _child_get(node, childs):
+ clds = []
+ for n in node.childNodes:
+ if (n.nodeType==n.ELEMENT_NODE) and (n.localName==childs):
+ clds.append(n)
+ return clds
+
+class _rml_styles(object):
+ def __init__(self, nodes):
+ self.styles = {}
+ self.names = {}
+ self.table_styles = {}
+ for node in nodes:
+ for style in node.getElementsByTagName('blockTableStyle'):
+ self.table_styles[style.getAttribute('id')] = self._table_style_get(style)
+ for style in node.getElementsByTagName('paraStyle'):
+ self.styles[style.getAttribute('name')] = self._para_style_get(style)
+ for variable in node.getElementsByTagName('initialize'):
+ for name in variable.getElementsByTagName('name'):
+ self.names[ name.getAttribute('id')] = name.getAttribute('value')
+
+ def _para_style_update(self, style, node):
+ for attr in ['textColor', 'backColor', 'bulletColor']:
+ if node.hasAttribute(attr):
+ style.__dict__[attr] = color.get(node.getAttribute(attr))
+ for attr in ['fontName', 'bulletFontName', 'bulletText']:
+ if node.hasAttribute(attr):
+ style.__dict__[attr] = node.getAttribute(attr)
+ for attr in ['fontSize', 'leftIndent', 'rightIndent', 'spaceBefore', 'spaceAfter', 'firstLineIndent', 'bulletIndent', 'bulletFontSize', 'leading']:
+ if node.hasAttribute(attr):
+ style.__dict__[attr] = utils.unit_get(node.getAttribute(attr))
+ if node.hasAttribute('alignment'):
+ align = {
+ 'right':reportlab.lib.enums.TA_RIGHT,
+ 'center':reportlab.lib.enums.TA_CENTER,
+ 'justify':reportlab.lib.enums.TA_JUSTIFY
+ }
+ style.alignment = align.get(node.getAttribute('alignment').lower(), reportlab.lib.enums.TA_LEFT)
+ return style
+
+ def _table_style_get(self, style_node):
+ styles = []
+ for node in style_node.childNodes:
+ if node.nodeType==node.ELEMENT_NODE:
+ start = utils.tuple_int_get(node, 'start', (0,0) )
+ stop = utils.tuple_int_get(node, 'stop', (-1,-1) )
+ if node.localName=='blockValign':
+ styles.append(('VALIGN', start, stop, str(node.getAttribute('value'))))
+ elif node.localName=='blockFont':
+ styles.append(('FONT', start, stop, str(node.getAttribute('name'))))
+ elif node.localName=='blockTextColor':
+ styles.append(('TEXTCOLOR', start, stop, color.get(str(node.getAttribute('colorName')))))
+ elif node.localName=='blockLeading':
+ styles.append(('LEADING', start, stop, utils.unit_get(node.getAttribute('length'))))
+ elif node.localName=='blockAlignment':
+ styles.append(('ALIGNMENT', start, stop, str(node.getAttribute('value'))))
+ elif node.localName=='blockLeftPadding':
+ styles.append(('LEFTPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
+ elif node.localName=='blockRightPadding':
+ styles.append(('RIGHTPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
+ elif node.localName=='blockTopPadding':
+ styles.append(('TOPPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
+ elif node.localName=='blockBottomPadding':
+ styles.append(('BOTTOMPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
+ elif node.localName=='blockBackground':
+ styles.append(('BACKGROUND', start, stop, color.get(node.getAttribute('colorName'))))
+ if node.hasAttribute('size'):
+ styles.append(('FONTSIZE', start, stop, utils.unit_get(node.getAttribute('size'))))
+ elif node.localName=='lineStyle':
+ kind = node.getAttribute('kind')
+ kind_list = [ 'GRID', 'BOX', 'OUTLINE', 'INNERGRID', 'LINEBELOW', 'LINEABOVE','LINEBEFORE', 'LINEAFTER' ]
+ assert kind in kind_list
+ thick = 1
+ if node.hasAttribute('thickness'):
+ thick = float(node.getAttribute('thickness'))
+ styles.append((kind, start, stop, thick, color.get(node.getAttribute('colorName'))))
+ return platypus.tables.TableStyle(styles)
+
+ def _para_style_get(self, node):
+ styles = reportlab.lib.styles.getSampleStyleSheet()
+ style = copy.deepcopy(styles["Normal"])
+ self._para_style_update(style, node)
+ return style
+
+ def para_style_get(self, node):
+ style = False
+ if node.hasAttribute('style'):
+ if node.getAttribute('style') in self.styles:
+ style = copy.deepcopy(self.styles[node.getAttribute('style')])
+ else:
+ sys.stderr.write('Warning: style not found, %s - setting default!\n' % (node.getAttribute('style'),) )
+ if not style:
+ styles = reportlab.lib.styles.getSampleStyleSheet()
+ style = copy.deepcopy(styles['Normal'])
+ return self._para_style_update(style, node)
+
+class _rml_doc(object):
+ def __init__(self, data, images={}, path='.'):
+ self.dom = xml.dom.minidom.parseString(data)
+ self.filename = self.dom.documentElement.getAttribute('filename')
+ self.images = images
+ self.path = path
+
+ def docinit(self, els):
+ from reportlab.lib.fonts import addMapping
+ from reportlab.pdfbase import pdfmetrics
+ from reportlab.pdfbase.ttfonts import TTFont
+
+ for node in els:
+ for font in node.getElementsByTagName('registerFont'):
+ name = font.getAttribute('fontName').encode('ascii')
+ fname = font.getAttribute('fontFile').encode('ascii')
+ pdfmetrics.registerFont(TTFont(name, fname ))
+ addMapping(name, 0, 0, name) #normal
+ addMapping(name, 0, 1, name) #italic
+ addMapping(name, 1, 0, name) #bold
+ addMapping(name, 1, 1, name) #italic and bold
+
+ def render(self, out):
+ el = self.dom.documentElement.getElementsByTagName('docinit')
+ if el:
+ self.docinit(el)
+
+ el = self.dom.documentElement.getElementsByTagName('stylesheet')
+ self.styles = _rml_styles(el)
+
+ el = self.dom.documentElement.getElementsByTagName('template')
+ if len(el):
+ pt_obj = _rml_template(out, el[0], self, images=self.images, path=self.path)
+ pt_obj.render(self.dom.documentElement.getElementsByTagName('story'))
+ else:
+ self.canvas = canvas.Canvas(out)
+ pd = self.dom.documentElement.getElementsByTagName('pageDrawing')[0]
+ pd_obj = _rml_canvas(self.canvas, None, self, self.images, path=self.path)
+ pd_obj.render(pd)
+ self.canvas.showPage()
+ self.canvas.save()
+
+class _rml_canvas(object):
+ def __init__(self, canvas, doc_tmpl=None, doc=None, images={}, path='.'):
+ self.canvas = canvas
+ self.styles = doc.styles
+ self.doc_tmpl = doc_tmpl
+ self.doc = doc
+ self.images = images
+ self.path = path
+
+ def _textual(self, node):
+ rc = ''
+ for n in node.childNodes:
+ if n.nodeType == n.ELEMENT_NODE:
+ if n.localName == 'pageNumber':
+ rc += str(self.canvas.getPageNumber())
+ elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
+ # this doesn't need to be "entities" encoded like flowables need to
+ rc += n.data
+ return rc.encode(encoding, 'replace')
+
+ def _drawString(self, node):
+ self.canvas.drawString(text=self._textual(node), **utils.attr_get(node, ['x','y']))
+ def _drawCenteredString(self, node):
+ self.canvas.drawCentredString(text=self._textual(node), **utils.attr_get(node, ['x','y']))
+ def _drawRightString(self, node):
+ self.canvas.drawRightString(text=self._textual(node), **utils.attr_get(node, ['x','y']))
+ def _rect(self, node):
+ if node.hasAttribute('round'):
+ self.canvas.roundRect(radius=utils.unit_get(node.getAttribute('round')), **utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
+ else:
+ self.canvas.rect(**utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
+
+ def _ellipse(self, node):
+ x1 = utils.unit_get(node.getAttribute('x'))
+ x2 = utils.unit_get(node.getAttribute('width'))
+ y1 = utils.unit_get(node.getAttribute('y'))
+ y2 = utils.unit_get(node.getAttribute('height'))
+ self.canvas.ellipse(x1,y1,x2,y2, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
+ def _curves(self, node):
+ line_str = utils.text_get(node).split()
+ lines = []
+ while len(line_str)>7:
+ self.canvas.bezier(*[utils.unit_get(l) for l in line_str[0:8]])
+ line_str = line_str[8:]
+ def _lines(self, node):
+ line_str = utils.text_get(node).split()
+ lines = []
+ while len(line_str)>3:
+ lines.append([utils.unit_get(l) for l in line_str[0:4]])
+ line_str = line_str[4:]
+ self.canvas.lines(lines)
+ def _grid(self, node):
+ xlist = [utils.unit_get(s) for s in node.getAttribute('xs').split(',')]
+ ylist = [utils.unit_get(s) for s in node.getAttribute('ys').split(',')]
+ self.canvas.grid(xlist, ylist)
+ def _translate(self, node):
+ dx = 0
+ dy = 0
+ if node.hasAttribute('dx'):
+ dx = utils.unit_get(node.getAttribute('dx'))
+ if node.hasAttribute('dy'):
+ dy = utils.unit_get(node.getAttribute('dy'))
+ self.canvas.translate(dx,dy)
+
+ def _circle(self, node):
+ 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'}))
+
+ def _place(self, node):
+ flows = _rml_flowable(self.doc, images=self.images, path=self.path).render(node)
+ infos = utils.attr_get(node, ['x','y','width','height'])
+
+ infos['y']+=infos['height']
+ for flow in flows:
+ w,h = flow.wrap(infos['width'], infos['height'])
+ if w<=infos['width'] and h<=infos['height']:
+ infos['y']-=h
+ flow.drawOn(self.canvas,infos['x'],infos['y'])
+ infos['height']-=h
+ else:
+ raise ValueError, "Not enough space"
+
+ def _line_mode(self, node):
+ ljoin = {'round':1, 'mitered':0, 'bevelled':2}
+ lcap = {'default':0, 'round':1, 'square':2}
+ if node.hasAttribute('width'):
+ self.canvas.setLineWidth(utils.unit_get(node.getAttribute('width')))
+ if node.hasAttribute('join'):
+ self.canvas.setLineJoin(ljoin[node.getAttribute('join')])
+ if node.hasAttribute('cap'):
+ self.canvas.setLineCap(lcap[node.getAttribute('cap')])
+ if node.hasAttribute('miterLimit'):
+ self.canvas.setDash(utils.unit_get(node.getAttribute('miterLimit')))
+ if node.hasAttribute('dash'):
+ dashes = node.getAttribute('dash').split(',')
+ for x in range(len(dashes)):
+ dashes[x]=utils.unit_get(dashes[x])
+ self.canvas.setDash(node.getAttribute('dash').split(','))
+
+ def _image(self, node):
+ import urllib
+ from reportlab.lib.utils import ImageReader
+
+ if not node.hasAttribute('file'):
+ s = self.images[node.getAttribute('name')]
+ else:
+ try:
+ u = urllib.urlopen(str(node.getAttribute('file')))
+ s = StringIO.StringIO(u.read())
+ except:
+ u = file(os.path.join(self.path,str(node.getAttribute('file'))), 'rb')
+ s = StringIO.StringIO(u.read())
+ img = ImageReader(s)
+ (sx,sy) = img.getSize()
+
+ args = {}
+ for tag in ('width','height','x','y'):
+ if node.hasAttribute(tag):
+ args[tag] = utils.unit_get(node.getAttribute(tag))
+ if ('width' in args) and (not 'height' in args):
+ args['height'] = sy * args['width'] / sx
+ elif ('height' in args) and (not 'width' in args):
+ args['width'] = sx * args['height'] / sy
+ elif ('width' in args) and ('height' in args):
+ if (float(args['width'])/args['height'])>(float(sx)>sy):
+ args['width'] = sx * args['height'] / sy
+ else:
+ args['height'] = sy * args['width'] / sx
+ self.canvas.drawImage(img, **args)
+
+ def _path(self, node):
+ self.path = self.canvas.beginPath()
+ self.path.moveTo(**utils.attr_get(node, ['x','y']))
+ for n in node.childNodes:
+ if n.nodeType == node.ELEMENT_NODE:
+ if n.localName=='moveto':
+ vals = utils.text_get(n).split()
+ self.path.moveTo(utils.unit_get(vals[0]), utils.unit_get(vals[1]))
+ elif n.localName=='curvesto':
+ vals = utils.text_get(n).split()
+ while len(vals)>5:
+ pos=[]
+ while len(pos)<6:
+ pos.append(utils.unit_get(vals.pop(0)))
+ self.path.curveTo(*pos)
+ elif (n.nodeType == node.TEXT_NODE):
+ data = n.data.split() # Not sure if I must merge all TEXT_NODE ?
+ while len(data)>1:
+ x = utils.unit_get(data.pop(0))
+ y = utils.unit_get(data.pop(0))
+ self.path.lineTo(x,y)
+ if (not node.hasAttribute('close')) or utils.bool_get(node.getAttribute('close')):
+ self.path.close()
+ self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
+
+ def render(self, node):
+ tags = {
+ 'drawCentredString': self._drawCenteredString,
+ 'drawRightString': self._drawRightString,
+ 'drawString': self._drawString,
+ 'rect': self._rect,
+ 'ellipse': self._ellipse,
+ 'lines': self._lines,
+ 'grid': self._grid,
+ 'curves': self._curves,
+ 'fill': lambda node: self.canvas.setFillColor(color.get(node.getAttribute('color'))),
+ 'stroke': lambda node: self.canvas.setStrokeColor(color.get(node.getAttribute('color'))),
+ 'setFont': lambda node: self.canvas.setFont(node.getAttribute('name'), utils.unit_get(node.getAttribute('size'))),
+ 'place': self._place,
+ 'circle': self._circle,
+ 'lineMode': self._line_mode,
+ 'path': self._path,
+ 'rotate': lambda node: self.canvas.rotate(float(node.getAttribute('degrees'))),
+ 'translate': self._translate,
+ 'image': self._image
+ }
+ for nd in node.childNodes:
+ if nd.nodeType==nd.ELEMENT_NODE:
+ for tag in tags:
+ if nd.localName==tag:
+ tags[tag](nd)
+ break
+
+class _rml_draw(object):
+ def __init__(self, node, styles, images={}, path='.'):
+ self.node = node
+ self.styles = styles
+ self.canvas = None
+ self.images = images
+ self.path = path
+
+ def render(self, canvas, doc):
+ canvas.saveState()
+ cnv = _rml_canvas(canvas, doc, self.styles, images=self.images, path=self.path)
+ cnv.render(self.node)
+ canvas.restoreState()
+
+class _rml_flowable(object):
+ def __init__(self, doc, images={}, path='.'):
+ self.doc = doc
+ self.styles = doc.styles
+ self.images = images
+ self.path = path
+
+ def _textual(self, node):
+ rc = ''
+ for n in node.childNodes:
+ if n.nodeType == node.ELEMENT_NODE:
+ if n.localName == 'getName':
+ newNode = self.doc.dom.createTextNode(self.styles.names.get(n.getAttribute('id'),'Unknown name'))
+ node.insertBefore(newNode, n)
+ node.removeChild(n)
+ elif n.localName == 'pageNumber':
+ rc += '<pageNumber/>' # TODO: change this !
+ else:
+ #CHECKME: I wonder if this is useful since we don't stock the result. Maybe for the getName tag?
+ self._textual(n)
+ rc += n.toxml()
+ elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
+ rc += str2xml(n.data)
+ return rc.encode(encoding, 'replace')
+
+ def _table(self, node):
+ length = 0
+ colwidths = None
+ rowheights = None
+ data = []
+ childs = _child_get(node,'tr')
+ if not childs:
+ return None
+ for tr in childs:
+ data2 = []
+ for td in _child_get(tr, 'td'):
+ flow = []
+ for n in td.childNodes:
+ if n.nodeType==node.ELEMENT_NODE:
+ flow.append( self._flowable(n) )
+ if not len(flow):
+ flow = self._textual(td)
+ data2.append( flow )
+ if len(data2)>length:
+ length=len(data2)
+ for ab in data:
+ while len(ab)<length:
+ ab.append('')
+ while len(data2)<length:
+ data2.append('')
+ data.append( data2 )
+ if node.hasAttribute('colWidths'):
+ assert length == len(node.getAttribute('colWidths').split(','))
+ colwidths = [utils.unit_get(f.strip()) for f in node.getAttribute('colWidths').split(',')]
+ if node.hasAttribute('rowHeights'):
+ rowheights = [utils.unit_get(f.strip()) for f in node.getAttribute('rowHeights').split(',')]
+ table = platypus.Table(data = data, colWidths=colwidths, rowHeights=rowheights, **(utils.attr_get(node, ['splitByRow'] ,{'repeatRows':'int','repeatCols':'int'})))
+ if node.hasAttribute('style'):
+ table.setStyle(self.styles.table_styles[node.getAttribute('style')])
+ return table
+
+ def _illustration(self, node):
+ class Illustration(platypus.flowables.Flowable):
+ def __init__(self, node, styles, self2):
+ self.node = node
+ self.styles = styles
+ self.width = utils.unit_get(node.getAttribute('width'))
+ self.height = utils.unit_get(node.getAttribute('height'))
+ self.self2 = self2
+ def wrap(self, *args):
+ return (self.width, self.height)
+ def draw(self):
+ canvas = self.canv
+ drw = _rml_draw(self.node, self.styles, images=self.self2.images, path=self.self2.path)
+ drw.render(self.canv, None)
+ return Illustration(node, self.styles, self)
+
+ def _flowable(self, node):
+ if node.localName=='para':
+ style = self.styles.para_style_get(node)
+ return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
+ elif node.localName=='barCode':
+ try:
+ from reportlab.extensions.barcode import code128
+ from reportlab.extensions.barcode import code39
+ from reportlab.extensions.barcode import code93
+ from reportlab.extensions.barcode import common
+ from reportlab.extensions.barcode import fourstate
+ from reportlab.extensions.barcode import usps
+ except Exception, e:
+ print 'Warning: Reportlab barcode extension not installed !'
+ return None
+ args = utils.attr_get(node, [], {'ratio':'float','xdim':'unit','height':'unit','checksum':'bool','quiet':'bool'})
+ codes = {
+ 'codabar': lambda x: common.Codabar(x, **args),
+ 'code11': lambda x: common.Code11(x, **args),
+ 'code128': lambda x: code128.Code128(x, **args),
+ 'standard39': lambda x: code39.Standard39(x, **args),
+ 'standard93': lambda x: code93.Standard93(x, **args),
+ 'i2of5': lambda x: common.I2of5(x, **args),
+ 'extended39': lambda x: code39.Extended39(x, **args),
+ 'extended93': lambda x: code93.Extended93(x, **args),
+ 'msi': lambda x: common.MSI(x, **args),
+ 'fim': lambda x: usps.FIM(x, **args),
+ 'postnet': lambda x: usps.POSTNET(x, **args),
+ }
+ code = 'code128'
+ if node.hasAttribute('code'):
+ code = node.getAttribute('code').lower()
+ return codes[code](self._textual(node))
+ elif node.localName=='name':
+ self.styles.names[ node.getAttribute('id')] = node.getAttribute('value')
+ return None
+ elif node.localName=='xpre':
+ style = self.styles.para_style_get(node)
+ return platypus.XPreformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int','frags':'int'})))
+ elif node.localName=='pre':
+ style = self.styles.para_style_get(node)
+ return platypus.Preformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int'})))
+ elif node.localName=='illustration':
+ return self._illustration(node)
+ elif node.localName=='blockTable':
+ return self._table(node)
+ elif node.localName=='title':
+ styles = reportlab.lib.styles.getSampleStyleSheet()
+ style = styles['Title']
+ return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
+ elif node.localName=='h1':
+ styles = reportlab.lib.styles.getSampleStyleSheet()
+ style = styles['Heading1']
+ return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
+ elif node.localName=='h2':
+ styles = reportlab.lib.styles.getSampleStyleSheet()
+ style = styles['Heading2']
+ return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
+ elif node.localName=='h3':
+ styles = reportlab.lib.styles.getSampleStyleSheet()
+ style = styles['Heading3']
+ return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
+ elif node.localName=='image':
+ if not node.hasAttribute('file'):
+ name = '/tmp/image_%d.jpg' % (int(node.getAttribute('name')),)
+ file(name,'wb+').write( self.doc.images[node.getAttribute('name')].read() )
+ return platypus.Image(name, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
+ else:
+ return platypus.Image(node.getAttribute('file'), mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
+
+ from reportlab.lib.utils import ImageReader
+ name = str(node.getAttribute('file'))
+ img = ImageReader(name)
+ (sx,sy) = img.getSize()
+
+ args = {}
+ for tag in ('width','height'):
+ if node.hasAttribute(tag):
+ args[tag] = utils.unit_get(node.getAttribute(tag))
+ if ('width' in args) and (not 'height' in args):
+ args['height'] = sy * args['width'] / sx
+ elif ('height' in args) and (not 'width' in args):
+ args['width'] = sx * args['height'] / sy
+ elif ('width' in args) and ('height' in args):
+ if (float(args['width'])/args['height'])>(float(sx)>sy):
+ args['width'] = sx * args['height'] / sy
+ else:
+ args['height'] = sy * args['width'] / sx
+ return platypus.Image(name, mask=(250,255,250,255,250,255), **args)
+ elif node.localName=='spacer':
+ if node.hasAttribute('width'):
+ width = utils.unit_get(node.getAttribute('width'))
+ else:
+ width = utils.unit_get('1cm')
+ length = utils.unit_get(node.getAttribute('length'))
+ return platypus.Spacer(width=width, height=length)
+ elif node.localName=='section':
+ return self.render(node)
+ elif node.localName in ('pageBreak', 'nextPage'):
+ return platypus.PageBreak()
+ elif node.localName=='condPageBreak':
+ return platypus.CondPageBreak(**(utils.attr_get(node, ['height'])))
+ elif node.localName=='setNextTemplate':
+ return platypus.NextPageTemplate(str(node.getAttribute('name')))
+ elif node.localName=='nextFrame':
+ return platypus.CondPageBreak(1000) # TODO: change the 1000 !
+ else:
+ sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.localName,))
+ return None
+
+ def render(self, node_story):
+ story = []
+ node = node_story.firstChild
+ while node:
+ if node.nodeType == node.ELEMENT_NODE:
+ flow = self._flowable(node)
+ if flow:
+ if type(flow) == type([]):
+ story = story + flow
+ else:
+ story.append(flow)
+ node = node.nextSibling
+ return story
+
+class _rml_template(object):
+ def __init__(self, out, node, doc, images={}, path='.'):
+ self.images= images
+ self.path = path
+ if not node.hasAttribute('pageSize'):
+ pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm'))
+ else:
+ ps = map(lambda x:x.strip(), node.getAttribute('pageSize').replace(')', '').replace('(', '').split(','))
+ pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
+ cm = reportlab.lib.units.cm
+ self.doc_tmpl = platypus.BaseDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','title':'str','author':'str'}))
+ self.page_templates = []
+ self.styles = doc.styles
+ self.doc = doc
+ pts = node.getElementsByTagName('pageTemplate')
+ for pt in pts:
+ frames = []
+ for frame_el in pt.getElementsByTagName('frame'):
+ frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'text', 'showBoundary':'bool'})) )
+ frames.append( frame )
+ gr = pt.getElementsByTagName('pageGraphics')
+ if len(gr):
+ drw = _rml_draw(gr[0], self.doc, images=images)
+ self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
+ else:
+ self.page_templates.append( platypus.PageTemplate(frames=frames, **utils.attr_get(pt, [], {'id':'str'}) ))
+ self.doc_tmpl.addPageTemplates(self.page_templates)
+
+ def render(self, node_stories):
+ fis = []
+ r = _rml_flowable(self.doc,images=self.images, path=self.path)
+ for node_story in node_stories:
+ fis += r.render(node_story)
+ fis.append(platypus.PageBreak())
+ self.doc_tmpl.build(fis)
+
+def parseString(data, fout=None, images={}, path='.'):
+ r = _rml_doc(data, images, path)
+ if fout:
+ fp = file(fout,'wb')
+ r.render(fp)
+ fp.close()
+ return fout
+ else:
+ fp = StringIO.StringIO()
+ r.render(fp)
+ return fp.getvalue()
+
+def trml2pdf_help():
+ print 'Usage: trml2pdf input.rml >output.pdf'
+ print 'Render the standard input (RML) and output a PDF file'
+ sys.exit(0)
+
+if __name__=="__main__":
+ if len(sys.argv)>1:
+ if sys.argv[1]=='--help':
+ trml2pdf_help()
+ print parseString(file(sys.argv[1], 'r').read()),
+ else:
+ print 'Usage: trml2pdf input.rml >output.pdf'
+ print 'Try \'trml2pdf --help\' for more information.'
diff --git a/bin/report/render/rml2pdf/utils.py b/bin/report/render/rml2pdf/utils.py
new file mode 100644
index 00000000000..dbb84636425
--- /dev/null
+++ b/bin/report/render/rml2pdf/utils.py
@@ -0,0 +1,93 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+# trml2pdf - An RML to PDF converter
+# Copyright (C) 2003, Fabien Pinckaers, UCL, FSA
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import re
+import reportlab
+
+def text_get(node):
+ rc = ''
+ for node in node.childNodes:
+ if node.nodeType == node.TEXT_NODE:
+ rc = rc + node.data
+ return rc
+
+units = [
+ (re.compile('^(-?[0-9\.]+)\s*in$'), reportlab.lib.units.inch),
+ (re.compile('^(-?[0-9\.]+)\s*cm$'), reportlab.lib.units.cm),
+ (re.compile('^(-?[0-9\.]+)\s*mm$'), reportlab.lib.units.mm),
+ (re.compile('^(-?[0-9\.]+)\s*$'), 1)
+]
+
+def unit_get(size):
+ global units
+ for unit in units:
+ res = unit[0].search(size, 0)
+ if res:
+ return unit[1]*float(res.group(1))
+ return False
+
+def tuple_int_get(node, attr_name, default=None):
+ if not node.hasAttribute(attr_name):
+ return default
+ res = [int(x) for x in node.getAttribute(attr_name).split(',')]
+ return res
+
+def bool_get(value):
+ return (str(value)=="1") or (value.lower()=='yes')
+
+def attr_get(node, attrs, dict={}):
+ res = {}
+ for name in attrs:
+ if node.hasAttribute(name):
+ res[name] = unit_get(node.getAttribute(name))
+ for key in dict:
+ if node.hasAttribute(key):
+ if dict[key]=='str':
+ res[key] = str(node.getAttribute(key))
+ elif dict[key]=='bool':
+ res[key] = bool_get(node.getAttribute(key))
+ elif dict[key]=='int':
+ res[key] = int(node.getAttribute(key))
+ return res
diff --git a/bin/report/render/simple.py b/bin/report/render/simple.py
new file mode 100644
index 00000000000..e7175c41b7a
--- /dev/null
+++ b/bin/report/render/simple.py
@@ -0,0 +1,90 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import render
+
+from StringIO import StringIO
+import xml.dom.minidom
+
+from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table
+from reportlab.lib.units import mm
+from reportlab.lib.pagesizes import A4
+import reportlab.lib
+
+import copy
+
+class simple(render.render):
+ def _render(self):
+ self.result = StringIO()
+ parser = xml.dom.minidom.parseString(self.xml)
+
+ title = parser.documentElement.tagName
+ doc = SimpleDocTemplate(self.result, pagesize=A4, title=title,
+ author='Tiny ERP, Fabien Pinckaers', leftmargin=10*mm, rightmargin=10*mm)
+
+ styles = reportlab.lib.styles.getSampleStyleSheet()
+ title_style = copy.deepcopy(styles["Heading1"])
+ title_style.alignment = reportlab.lib.enums.TA_CENTER
+ story = [ Paragraph(title, title_style) ]
+ style_level = {}
+ nodes = [ (parser.documentElement,0) ]
+ while len(nodes):
+ node = nodes.pop(0)
+ value = ''
+ n=len(node[0].childNodes)-1
+ while n>=0:
+ if node[0].childNodes[n].nodeType==3:
+ value += node[0].childNodes[n].nodeValue
+ else:
+ nodes.insert( 0, (node[0].childNodes[n], node[1]+1) )
+ n-=1
+ if not node[1] in style_level:
+ style = copy.deepcopy(styles["Normal"])
+ style.leftIndent=node[1]*6*mm
+ style.firstLineIndent=-3*mm
+ style_level[node[1]] = style
+ story.append( Paragraph('<b>%s</b>: %s' % (node[0].tagName, value), style_level[node[1]]))
+ doc.build(story)
+ return self.result.getvalue()
+
+if __name__=='__main__':
+ import time
+ s = simple('''<test>
+ <author-list>
+ <author>
+ <name>Fabien Pinckaers</name>
+ <age>23</age>
+ </author>
+ <author>
+ <name>Michel Pinckaers</name>
+ <age>53</age>
+ </author>
+ No other
+ </author-list>
+ </test>''')
+ print s.render()