diff options
| author | pinky <> | 2006-12-07 13:41:40 +0000 |
|---|---|---|
| committer | pinky <> | 2006-12-07 13:41:40 +0000 |
| commit | 004a0b996ff8f269451e07346f71a129a1f3fbaf (patch) | |
| tree | 48f43c1fc2ca4c1f4d5648e7e73553d3f4ccae06 /bin/report/render | |
New trunk
bzr revid: pinky-3f10ee12cea3c4c75cef44ab04ad33ef47432907
Diffstat (limited to 'bin/report/render')
| -rw-r--r-- | bin/report/render/__init__.py | 37 | ||||
| -rw-r--r-- | bin/report/render/render.py | 80 | ||||
| -rw-r--r-- | bin/report/render/rml.py | 50 | ||||
| -rw-r--r-- | bin/report/render/rml2html/__init__.py | 18 | ||||
| -rwxr-xr-x | bin/report/render/rml2html/rml2html.py | 365 | ||||
| -rw-r--r-- | bin/report/render/rml2html/utils.py | 67 | ||||
| -rw-r--r-- | bin/report/render/rml2pdf/__init__.py | 46 | ||||
| -rw-r--r-- | bin/report/render/rml2pdf/color.py | 36 | ||||
| -rwxr-xr-x | bin/report/render/rml2pdf/trml2pdf.py | 640 | ||||
| -rw-r--r-- | bin/report/render/rml2pdf/utils.py | 93 | ||||
| -rw-r--r-- | bin/report/render/simple.py | 90 |
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"> </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('&', '&').replace('<', '<').replace('>', '>') + +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() |
