summaryrefslogtreecommitdiff
path: root/bin/report
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
New trunk
bzr revid: pinky-3f10ee12cea3c4c75cef44ab04ad33ef47432907
Diffstat (limited to 'bin/report')
-rw-r--r--bin/report/__init__.py38
-rw-r--r--bin/report/common.py32
-rw-r--r--bin/report/custom.py661
-rw-r--r--bin/report/int_to_text.py81
-rw-r--r--bin/report/interface.py195
-rw-r--r--bin/report/misc.py14
-rw-r--r--bin/report/print_fnc.py41
-rw-r--r--bin/report/print_xml.py358
-rw-r--r--bin/report/printscreen/__init__.py31
-rw-r--r--bin/report/printscreen/ps_form.py154
-rw-r--r--bin/report/printscreen/ps_list.py152
-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
-rw-r--r--bin/report/report_sxw.py309
23 files changed, 3588 insertions, 0 deletions
diff --git a/bin/report/__init__.py b/bin/report/__init__.py
new file mode 100644
index 00000000000..3993499e86c
--- /dev/null
+++ b/bin/report/__init__.py
@@ -0,0 +1,38 @@
+##############################################################################
+#
+# 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 interface
+import print_xml
+import print_fnc
+import custom
+import render
+import pychart
+
+import report_sxw
+
+import printscreen
diff --git a/bin/report/common.py b/bin/report/common.py
new file mode 100644
index 00000000000..3f4f372d67e
--- /dev/null
+++ b/bin/report/common.py
@@ -0,0 +1,32 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+
+pageSize = {
+ 'A4': (210,297),
+ 'A5': (148.5,105)
+}
diff --git a/bin/report/custom.py b/bin/report/custom.py
new file mode 100644
index 00000000000..2d4aef9c137
--- /dev/null
+++ b/bin/report/custom.py
@@ -0,0 +1,661 @@
+##############################################################################
+#
+# Copyright (c) 2004-2005 TINY SPRL. (http://tiny.be) All Rights Reserved.
+#
+# $Id: custom.py 1305 2005-09-08 14:39:51Z kayhman $
+#
+# 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 os, time
+import netsvc
+
+import tools
+import print_xml
+import render
+from interface import report_int
+import common
+from osv.orm import browse_null
+from osv.orm import browse_record_list
+import pooler
+from xml.dom import minidom
+import libxml2
+import libxslt
+from pychart import *
+import misc
+import StringIO
+
+class external_pdf(render.render):
+ def __init__(self, pdf):
+ render.render.__init__(self)
+ self.pdf = pdf
+ self.output_type='pdf'
+ def _render(self):
+ return self.pdf
+
+theme.use_color = 1
+
+
+#TODO: devrait heriter de report_rml a la place de report_int
+# -> pourrait overrider que create_xml a la place de tout create
+# heuu, ca marche pas ds tous les cas car graphs sont generes en pdf directment
+# par pychart, et on passe donc pas par du rml
+class report_custom(report_int):
+ def __init__(self, name):
+ report_int.__init__(self, name)
+ #
+ # PRE:
+ # fields = [['address','city'],['name'], ['zip']]
+ # conditions = [[('zip','==','3'),(,)],(,),(,)] #same structure as fields
+ # row_canvas = ['Rue', None, None]
+ # POST:
+ # [ ['ville','name','zip'] ]
+ #
+ def _row_get(self, cr, uid, objs, fields, conditions, row_canvas=None, group_by=None):
+ result = []
+ tmp = []
+ for obj in objs:
+ tobreak = False
+ for cond in conditions:
+ if cond and cond[0]:
+ c = cond[0]
+ temp = c[0](eval('obj.'+c[1]))
+ if not eval('\''+temp+'\''+' '+c[2]+' '+'\''+str(c[3])+'\''):
+ tobreak = True
+ if tobreak:
+ break
+ levels = {}
+ row = []
+ for i in range(len(fields)):
+ if not fields[i]:
+ row.append(row_canvas and row_canvas[i])
+ if row_canvas[i]:
+ row_canvas[i]=False
+ elif len(fields[i])==1:
+ if not isinstance(obj, browse_null):
+ row.append(str(eval('obj.'+fields[i][0])))
+ else:
+ row.append(None)
+ else:
+ row.append(None)
+ levels[fields[i][0]]=True
+ if not levels:
+ result.append(row)
+ else:
+ # Process group_by data first
+ key = []
+ if group_by != None and fields[group_by] != None:
+ if fields[group_by][0] in levels.keys():
+ key.append(fields[group_by][0])
+ for l in levels.keys():
+ if l != fields[group_by][0]:
+ key.append(l)
+ else:
+ key = levels.keys()
+ for l in key:
+ objs = eval('obj.'+l)
+ if not isinstance(objs, browse_record_list) and type(objs) <> type([]):
+ objs = [objs]
+ field_new = []
+ cond_new = []
+ for f in range(len(fields)):
+ if (fields[f] and fields[f][0])==l:
+ field_new.append(fields[f][1:])
+ cond_new.append(conditions[f][1:])
+ else:
+ field_new.append(None)
+ cond_new.append(None)
+ if len(objs):
+ result += self._row_get(cr, uid, objs, field_new, cond_new, row, group_by)
+ else:
+ result.append(row)
+ return result
+
+
+ def create(self, cr, uid, ids, datas, context={}):
+ self.pool = pooler.get_pool(cr.dbname)
+ report = self.pool.get('ir.report.custom').browse(cr, uid, [datas['report_id']])[0]
+ datas['model'] = report.model_id.model
+ if report.menu_id:
+ ids = self.pool.get(report.model_id.model).search(cr, uid, [])
+ datas['ids'] = ids
+
+ service = netsvc.LocalService("object_proxy")
+ report_id = datas['report_id']
+ report = service.execute(cr.dbname, uid, 'ir.report.custom', 'read', [report_id], context=context)[0]
+ fields = service.execute(cr.dbname, uid, 'ir.report.custom.fields', 'read', report['fields_child0'], context=context)
+
+ fields.sort(lambda x,y : x['sequence'] - y['sequence'])
+
+ if report['field_parent']:
+ parent_field = service.execute(cr.dbname, uid, 'ir.model.fields', 'read', [report['field_parent'][0]],['model'])
+ model_name = service.execute(cr.dbname, uid, 'ir.model', 'read', [report['model_id'][0]], ['model'],context=context)[0]['model']
+
+ fct = {}
+ fct['id'] = lambda x : x
+ fct['gety'] = lambda x: x.split('-')[0]
+ fct['in'] = lambda x: x.split(',')
+ new_fields = []
+ new_cond = []
+ for f in fields:
+ row = []
+ cond = []
+ for i in range(4):
+ if f['field_child'+str(i)]:
+ row.append(f['field_child'+str(i)][1])
+ if f['fc'+str(i)+'_operande']:
+ fct_name = 'id'
+ cond_op = f['fc'+str(i)+'_op']
+ if len(f['fc'+str(i)+'_op'].split(',')) == 2:
+ cond_op = f['fc'+str(i)+'_op'].split(',')[1]
+ fct_name = f['fc'+str(i)+'_op'].split(',')[0]
+ cond.append((fct[fct_name], f['fc'+str(i)+'_operande'][1], cond_op, f['fc'+str(i)+'_condition']))
+ else:
+ cond.append(None)
+ new_fields.append(row)
+ new_cond.append(cond)
+ objs = self.pool.get(model_name).browse(cr, uid, ids)
+
+ # Group by
+ groupby = None
+ idx = 0
+ for f in fields:
+ if f['groupby']:
+ groupby = idx
+ idx += 1
+
+
+ results = []
+ if report['field_parent']:
+ level = []
+ def build_tree(obj, level, depth):
+ res = self._row_get(cr, uid,[obj], new_fields, new_cond)
+ level.append(depth)
+ new_obj = eval('obj.'+report['field_parent'][1])
+ if not isinstance(new_obj, list) :
+ new_obj = [new_obj]
+ for o in new_obj:
+ if not isinstance(o, browse_null):
+ res += build_tree(o, level, depth+1)
+ return res
+
+ for obj in objs:
+ results += build_tree(obj, level, 0)
+ else:
+ results = self._row_get(cr, uid,objs, new_fields, new_cond, group_by=groupby)
+
+ fct = {
+ 'calc_sum': lambda l: reduce(lambda x,y: float(x)+float(y), filter(None, l), 0),
+ 'calc_avg': lambda l: reduce(lambda x,y: float(x)+float(y), filter(None, l), 0) / (len(filter(None, l)) or 1.0),
+ 'calc_max': lambda l: reduce(lambda x,y: max(x,y), [(i or 0.0) for i in l], 0),
+ 'calc_min': lambda l: reduce(lambda x,y: min(x,y), [(i or 0.0) for i in l], 0),
+ 'calc_count': lambda l: len(filter(None, l)),
+ 'False': lambda l: '\r\n'.join(filter(None, l)),
+ 'groupby': lambda l: reduce(lambda x,y: x or y, l)
+ }
+ new_res = []
+
+ prev = None
+ if groupby != None:
+ res_dic = {}
+ for line in results:
+ if not line[groupby] and prev in res_dic:
+ res_dic[prev].append(line)
+ else:
+ prev = line[groupby]
+ if res_dic.has_key(line[groupby]):
+ res_dic[line[groupby]].append(line)
+ else:
+ res_dic[line[groupby]] = []
+ res_dic[line[groupby]].append(line)
+ #we use the keys in results since they are ordered, whereas in res_dic.heys() they aren't
+ for key in filter(None, [x[groupby] for x in results]):
+ row = []
+ for col in range(len(fields)):
+ if col == groupby:
+ row.append(fct['groupby'](map(lambda x: x[col], res_dic[key])))
+ else:
+ row.append(fct[str(fields[col]['operation'])](map(lambda x: x[col], res_dic[key])))
+ new_res.append(row)
+ results = new_res
+
+ if report['type']=='table':
+ if report['field_parent']:
+ res = self._create_tree(uid, ids, report, fields, level, results, context)
+ else:
+ sort_idx = 0
+ for idx in range(len(fields)):
+ if fields[idx]['name'] == report['sortby']:
+ sort_idx = idx
+ break
+ try :
+ results.sort(lambda x,y : cmp(float(x[sort_idx]),float(y[sort_idx])))
+ except :
+ results.sort(lambda x,y : cmp(x[sort_idx],y[sort_idx]))
+ if report['limitt']:
+ results = results[:int(report['limitt'])]
+ res = self._create_table(uid, ids, report, fields, None, results, context)
+ elif report['type'] in ('pie','bar', 'line'):
+ results2 = []
+ prev = False
+ for r in results:
+ row = []
+ for j in range(len(r)):
+ if j == 0 and not r[j]:
+ row.append(prev)
+ elif j == 0 and r[j]:
+ prev = r[j]
+ row.append(r[j])
+ else:
+ try:
+ row.append(float(r[j]))
+ except:
+ row.append(r[j])
+ results2.append(row)
+ if report['type']=='pie':
+ res = self._create_pie(cr,uid, ids, report, fields, results2, context)
+ elif report['type']=='bar':
+ res = self._create_bars(cr,uid, ids, report, fields, results2, context)
+ elif report['type']=='line':
+ res = self._create_lines(cr,uid, ids, report, fields, results2, context)
+ return (self.obj.get(), 'pdf')
+
+ def _create_tree(self, uid, ids, report, fields, level, results, context):
+ pageSize=common.pageSize.get(report['print_format'], [210.0,297.0])
+ if report['print_orientation']=='landscape':
+ pageSize=[pageSize[1],pageSize[0]]
+
+ impl = minidom.getDOMImplementation()
+ new_doc = impl.createDocument(None, "report", None)
+
+ # build header
+ config = new_doc.createElement("config")
+
+ def _append_node(name, text):
+ n = new_doc.createElement(name)
+ t = new_doc.createTextNode(text)
+ n.appendChild(t)
+ config.appendChild(n)
+
+ _append_node('date', time.strftime('%d/%m/%Y'))
+ _append_node('PageFormat', '%s' % report['print_format'])
+ _append_node('PageSize', '%.2fmm,%.2fmm' % tuple(pageSize))
+ _append_node('PageWidth', '%.2f' % (pageSize[0] * 2.8346,))
+ _append_node('PageHeight', '%.2f' %(pageSize[1] * 2.8346,))
+
+ length = pageSize[0]-30-reduce(lambda x,y:x+(y['width'] or 0), fields, 0)
+ count = 0
+ for f in fields:
+ if not f['width']: count+=1
+ for f in fields:
+ if not f['width']:
+ f['width']=round((float(length)/count)-0.5)
+
+ _append_node('tableSize', '%s' % ','.join(map(lambda x: '%.2fmm' % (x['width'],), fields)))
+ _append_node('report-header', '%s' % (report['title'],))
+ _append_node('report-footer', '%s' % (report['footer'],))
+
+ new_doc.childNodes[0].appendChild(config)
+ header = new_doc.createElement("header")
+
+ for f in fields:
+ field = new_doc.createElement("field")
+ field_txt = new_doc.createTextNode('%s' % (f['name'],))
+ field.appendChild(field_txt)
+ header.appendChild(field)
+
+ new_doc.childNodes[0].appendChild(header)
+
+ lines = new_doc.createElement("lines")
+ level.reverse()
+ for line in results:
+ shift = level.pop()
+ node_line = new_doc.createElement("row")
+ prefix = '+'
+ for f in range(len(fields)):
+ col = new_doc.createElement("col")
+ if f == 0:
+ col.setAttribute('para','yes')
+ col.setAttribute('tree','yes')
+ col.setAttribute('space',str(3*shift)+'mm')
+ if line[f] != None:
+ txt = new_doc.createTextNode(prefix+str(line[f]) or '')
+ else:
+ txt = new_doc.createTextNode('/')
+ col.appendChild(txt)
+ node_line.appendChild(col)
+ prefix = ''
+ lines.appendChild(node_line)
+
+ new_doc.childNodes[0].appendChild(lines)
+
+ styledoc = libxml2.parseFile(os.path.join(tools.config['root_path'],'addons/base/report/custom_new.xsl'))
+ style = libxslt.parseStylesheetDoc(styledoc)
+ doc = libxml2.parseDoc(new_doc.toxml())
+ rml_obj = style.applyStylesheet(doc, None)
+ rml = style.saveResultToString(rml_obj)
+
+ self.obj = render.rml(rml)
+ self.obj.render()
+ return True
+
+
+ def _create_lines(self, cr, uid, ids, report, fields, results, context):
+ service = netsvc.LocalService("object_proxy")
+ pdf_string = StringIO.StringIO()
+ can = canvas.init(fname=pdf_string, format='pdf')
+
+ can.show(80,380,'/16/H'+report['title'])
+
+ ar = area.T(size=(350,350),
+ #x_coord = category_coord.T(['2005-09-01','2005-10-22'],0),
+ x_axis = axis.X(label = fields[0]['name'], format="/a-30{}%s"),
+ y_axis = axis.Y(label = ', '.join(map(lambda x : x['name'], fields[1:]))))
+
+ process_date = {}
+ process_date['D'] = lambda x : reduce(lambda xx,yy : xx+'-'+yy,x.split('-')[1:3])
+ process_date['M'] = lambda x : x.split('-')[1]
+ process_date['Y'] = lambda x : x.split('-')[0]
+
+ order_date = {}
+ order_date['D'] = lambda x : time.mktime((2005,int(x.split('-')[0]), int(x.split('-')[1]),0,0,0,0,0,0))
+ order_date['M'] = lambda x : x
+ order_date['Y'] = lambda x : x
+
+ abscissa = []
+ tmp = {}
+
+ idx = 0
+ date_idx = None
+ fct = {}
+ for f in fields:
+ field_id = (f['field_child3'] and f['field_child3'][0]) or (f['field_child2'] and f['field_child2'][0]) or (f['field_child1'] and f['field_child1'][0]) or (f['field_child0'] and f['field_child0'][0])
+ if field_id:
+ type = service.execute(cr.dbname, uid, 'ir.model.fields', 'read', [field_id],['ttype'])
+ if type[0]['ttype'] == 'date':
+ date_idx = idx
+ fct[idx] = process_date[report['frequency']]
+ else:
+ fct[idx] = lambda x : x
+ else:
+ fct[idx] = lambda x : x
+ idx+=1
+
+ # plots are usually displayed year by year
+ # so we do so if the first field is a date
+ data_by_year = {}
+ if date_idx != None:
+ for r in results:
+ key = process_date['Y'](r[date_idx])
+ if not data_by_year.has_key(key):
+ data_by_year[key] = []
+ for i in range(len(r)):
+ r[i] = fct[i](r[i])
+ data_by_year[key].append(r)
+ else:
+ data_by_year[''] = results
+
+ idx0 = 0
+ nb_bar = len(data_by_year)*(len(fields)-1)
+ colors = map(lambda x:line_style.T(color=x), misc.choice_colors(nb_bar))
+ abscissa = {}
+ for line in data_by_year.keys():
+ fields_bar = []
+ # sum data and save it in a list. An item for a fields
+ for d in data_by_year[line]:
+ for idx in range(len(fields)-1):
+ fields_bar.append({})
+ if fields_bar[idx].has_key(d[0]):
+ fields_bar[idx][d[0]] += d[idx+1]
+ else:
+ fields_bar[idx][d[0]] = d[idx+1]
+ for idx in range(len(fields)-1):
+ data = {}
+ for k in fields_bar[idx].keys():
+ if data.has_key(k):
+ data[k] += fields_bar[idx][k]
+ else:
+ data[k] = fields_bar[idx][k]
+ data_cum = []
+ prev = 0.0
+ keys = data.keys()
+ keys.sort()
+ # cumulate if necessary
+ for k in keys:
+ data_cum.append([k, float(data[k])+float(prev)])
+ if fields[idx+1]['cumulate']:
+ prev += data[k]
+ idx0 = 0
+ plot = line_plot.T(label=fields[idx+1]['name']+' '+str(line), data = data_cum, line_style=colors[idx0*(len(fields)-1)+idx])
+ ar.add_plot(plot)
+ abscissa.update(fields_bar[idx])
+ idx0 += 1
+
+ abscissa = map(lambda x : [x, None], abscissa)
+ ar.x_coord = category_coord.T(abscissa,0)
+ ar.draw(can)
+
+ can.close()
+ self.obj = external_pdf(pdf_string.getvalue())
+ self.obj.render()
+ pdf_string.close()
+ return True
+
+
+
+ def _create_bars(self, cr, uid, ids, report, fields, results, context):
+ service = netsvc.LocalService("object_proxy")
+ pdf_string = StringIO.StringIO()
+ can = canvas.init(fname=pdf_string, format='pdf')
+
+ can.show(80,380,'/16/H'+report['title'])
+
+ process_date = {}
+ process_date['D'] = lambda x : reduce(lambda xx,yy : xx+'-'+yy,x.split('-')[1:3])
+ process_date['M'] = lambda x : x.split('-')[1]
+ process_date['Y'] = lambda x : x.split('-')[0]
+
+ order_date = {}
+ order_date['D'] = lambda x : time.mktime((2005,int(x.split('-')[0]), int(x.split('-')[1]),0,0,0,0,0,0))
+ order_date['M'] = lambda x : x
+ order_date['Y'] = lambda x : x
+
+ ar = area.T(size=(350,350),
+ x_axis = axis.X(label = fields[0]['name'], format="/a-30{}%s"),
+ y_axis = axis.Y(label = ', '.join(map(lambda x : x['name'], fields[1:]))))
+
+ idx = 0
+ date_idx = None
+ fct = {}
+ for f in fields:
+ field_id = (f['field_child3'] and f['field_child3'][0]) or (f['field_child2'] and f['field_child2'][0]) or (f['field_child1'] and f['field_child1'][0]) or (f['field_child0'] and f['field_child0'][0])
+ if field_id:
+ type = service.execute(cr.dbname, uid, 'ir.model.fields', 'read', [field_id],['ttype'])
+ if type[0]['ttype'] == 'date':
+ date_idx = idx
+ fct[idx] = process_date[report['frequency']]
+ else:
+ fct[idx] = lambda x : x
+ else:
+ fct[idx] = lambda x : x
+ idx+=1
+
+ # plot are usually displayed year by year
+ # so we do so if the first field is a date
+ data_by_year = {}
+ if date_idx != None:
+ for r in results:
+ key = process_date['Y'](r[date_idx])
+ if not data_by_year.has_key(key):
+ data_by_year[key] = []
+ for i in range(len(r)):
+ r[i] = fct[i](r[i])
+ data_by_year[key].append(r)
+ else:
+ data_by_year[''] = results
+
+
+ nb_bar = len(data_by_year)*(len(fields)-1)
+ colors = map(lambda x:fill_style.Plain(bgcolor=x), misc.choice_colors(nb_bar))
+
+ abscissa = {}
+ for line in data_by_year.keys():
+ fields_bar = []
+ # sum data and save it in a list. An item for a fields
+ for d in data_by_year[line]:
+ for idx in range(len(fields)-1):
+ fields_bar.append({})
+ if fields_bar[idx].has_key(d[0]):
+ fields_bar[idx][d[0]] += d[idx+1]
+ else:
+ fields_bar[idx][d[0]] = d[idx+1]
+ for idx in range(len(fields)-1):
+ data = {}
+ for k in fields_bar[idx].keys():
+ if data.has_key(k):
+ data[k] += fields_bar[idx][k]
+ else:
+ data[k] = fields_bar[idx][k]
+ data_cum = []
+ prev = 0.0
+ keys = data.keys()
+ keys.sort()
+ # cumulate if necessary
+ for k in keys:
+ data_cum.append([k, float(data[k])+float(prev)])
+ if fields[idx+1]['cumulate']:
+ prev += data[k]
+
+ idx0 = 0
+ plot = bar_plot.T(label=fields[idx+1]['name']+' '+str(line), data = data_cum, cluster=(idx0*(len(fields)-1)+idx,nb_bar), fill_style=colors[idx0*(len(fields)-1)+idx])
+ ar.add_plot(plot)
+ abscissa.update(fields_bar[idx])
+ idx0 += 1
+ abscissa = map(lambda x : [x, None], abscissa)
+ ar.x_coord = category_coord.T(abscissa,0)
+ ar.draw(can)
+
+ can.close()
+ self.obj = external_pdf(pdf_string.getvalue())
+ self.obj.render()
+ pdf_string.close()
+ return True
+
+ def _create_pie(self, cr, uid, ids, report, fields, results, context):
+ pdf_string = StringIO.StringIO()
+ can = canvas.init(fname=pdf_string, format='pdf')
+ ar = area.T(size=(350,350), legend=legend.T(),
+ x_grid_style = None, y_grid_style = None)
+ colors = map(lambda x:fill_style.Plain(bgcolor=x), misc.choice_colors(len(results)))
+
+ if reduce(lambda x,y : x+y, map(lambda x : x[1],results)) == 0.0:
+ raise('The sum of the data (2nd field) is null. \nWe can draw a pie chart !')
+
+ plot = pie_plot.T(data=results, arc_offsets=[0,10,0,10],
+ shadow = (2, -2, fill_style.gray50),
+ label_offset = 25,
+ arrow_style = arrow.a3,
+ fill_styles=colors)
+ ar.add_plot(plot)
+ ar.draw(can)
+ can.close()
+ self.obj = external_pdf(pdf_string.getvalue())
+ self.obj.render()
+ pdf_string.close()
+ return True
+
+ def _create_table(self, uid, ids, report, fields, tree, results, context):
+ pageSize=common.pageSize.get(report['print_format'], [210.0,297.0])
+ if report['print_orientation']=='landscape':
+ pageSize=[pageSize[1],pageSize[0]]
+
+ impl = minidom.getDOMImplementation()
+ new_doc = impl.createDocument(None, "report", None)
+
+ # build header
+ config = new_doc.createElement("config")
+
+ def _append_node(name, text):
+ n = new_doc.createElement(name)
+ t = new_doc.createTextNode(text)
+ n.appendChild(t)
+ config.appendChild(n)
+
+ _append_node('date', time.strftime('%d/%m/%Y'))
+ _append_node('PageSize', '%.2fmm,%.2fmm' % tuple(pageSize))
+ _append_node('PageFormat', '%s' % report['print_format'])
+ _append_node('PageWidth', '%.2f' % (pageSize[0] * 2.8346,))
+ _append_node('PageHeight', '%.2f' %(pageSize[1] * 2.8346,))
+
+ length = pageSize[0]-30-reduce(lambda x,y:x+(y['width'] or 0), fields, 0)
+ count = 0
+ for f in fields:
+ if not f['width']: count+=1
+ for f in fields:
+ if not f['width']:
+ f['width']=round((float(length)/count)-0.5)
+
+ _append_node('tableSize', '%s' % ','.join(map(lambda x: '%.2fmm' % (x['width'],), fields)))
+ _append_node('report-header', '%s' % (report['title'],))
+ _append_node('report-footer', '%s' % (report['footer'],))
+
+ new_doc.childNodes[0].appendChild(config)
+ header = new_doc.createElement("header")
+
+ for f in fields:
+ field = new_doc.createElement("field")
+ field_txt = new_doc.createTextNode('%s' % (f['name'],))
+ field.appendChild(field_txt)
+ header.appendChild(field)
+
+ new_doc.childNodes[0].appendChild(header)
+
+ lines = new_doc.createElement("lines")
+ for line in results:
+ node_line = new_doc.createElement("row")
+ for f in range(len(fields)):
+ col = new_doc.createElement("col")
+ col.setAttribute('tree','no')
+ if line[f] != None:
+ txt = new_doc.createTextNode(str(line[f] or ''))
+ else:
+ txt = new_doc.createTextNode('/')
+ col.appendChild(txt)
+ node_line.appendChild(col)
+ lines.appendChild(node_line)
+
+ new_doc.childNodes[0].appendChild(lines)
+
+# file('/tmp/terp.xml','w+').write(new_doc.toxml())
+
+ styledoc = libxml2.parseFile(os.path.join(tools.config['root_path'],'addons/base/report/custom_new.xsl'))
+ style = libxslt.parseStylesheetDoc(styledoc)
+ doc = libxml2.parseDoc(new_doc.toxml())
+ rml_obj = style.applyStylesheet(doc, None)
+ rml = style.saveResultToString(rml_obj)
+
+ self.obj = render.rml(rml)
+ self.obj.render()
+ return True
+report_custom('report.custom')
+
diff --git a/bin/report/int_to_text.py b/bin/report/int_to_text.py
new file mode 100644
index 00000000000..d0df86bb83a
--- /dev/null
+++ b/bin/report/int_to_text.py
@@ -0,0 +1,81 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+
+unites = {
+ 0: '', 1:'un', 2:'deux', 3:'trois', 4:'quatre', 5:'cinq', 6:'six', 7:'sept', 8:'huit', 9:'neuf',
+ 10:'dix', 11:'onze', 12:'douze', 13:'treize', 14:'quatorze', 15:'quinze', 16:'seize',
+ 21:'vingt et un', 31:'trente et un', 41:'quarante et un', 51:'cinquante et un', 61:'soixante et un',
+ 71:'septante et un', 91:'nonante et un', 80:'quatre-vingts'
+}
+
+dizaine = {
+ 1: 'dix', 2:'vingt', 3:'trente',4:'quarante', 5:'cinquante', 6:'soixante', 7:'septante', 8:'quatre-vingt', 9:'nonante'
+}
+
+centaine = {
+ 0:'', 1: 'cent', 2:'deux cent', 3:'trois cent',4:'quatre cent', 5:'cinq cent', 6:'six cent', 7:'sept cent', 8:'huit cent', 9:'neuf cent'
+}
+
+mille = {
+ 0:'', 1:'mille'
+}
+
+def _100_to_text(chiffre):
+ if chiffre in unites:
+ return unites[chiffre]
+ else:
+ if chiffre%10>0:
+ return dizaine[chiffre / 10]+'-'+unites[chiffre % 10]
+ else:
+ return dizaine[chiffre / 10]
+
+def _1000_to_text(chiffre):
+ d = _100_to_text(chiffre % 100)
+ d2 = chiffre/100
+ if d2>0 and d:
+ return centaine[d2]+' '+d
+ elif d2>1 and not(d):
+ return centaine[d2]+'s'
+ else:
+ return centaine[d2] or d
+
+def _10000_to_text(chiffre):
+ if chiffre==0:
+ return 'zero'
+ part1 = _1000_to_text(chiffre % 1000)
+ part2 = mille.get(chiffre / 1000, _1000_to_text(chiffre / 1000)+' mille')
+ if part2 and part1:
+ part1 = ' '+part1
+ return part2+part1
+
+def int_to_text(i):
+ return _10000_to_text(i)
+
+if __name__=='__main__':
+ for i in range(1,999999,139):
+ print int_to_text(i)
diff --git a/bin/report/interface.py b/bin/report/interface.py
new file mode 100644
index 00000000000..53983771be0
--- /dev/null
+++ b/bin/report/interface.py
@@ -0,0 +1,195 @@
+##############################################################################
+#
+# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+#
+# $Id: interface.py 1304 2005-09-08 14:35:42Z nicoe $
+#
+# 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 os,re
+
+#Ged> Why do we use libxml2 here instead of xml.dom like in other places of the code?
+import libxml2
+import libxslt
+
+import netsvc
+import pooler
+
+import tools
+import print_xml
+import render
+
+#
+# encode a value to a string in utf8 and converts XML entities
+#
+def toxml(val):
+ if isinstance(val, str):
+ str_utf8 = val
+ elif isinstance(val, unicode):
+ str_utf8 = val.encode('utf-8')
+ else:
+ str_utf8 = str(val)
+ return str_utf8.replace('&', '&amp;').replace('<','&lt;').replace('>','&gt;')
+
+class report_int(netsvc.Service):
+ def __init__(self, name, audience='*'):
+ super(report_int, self).__init__(name, audience)
+ if name[0:7]<>'report.':
+ raise 'ConceptionError, bad report name'
+ self.name = name
+ self.id = 0
+ self.name2 = '.'.join(name.split('.')[1:])
+ self.joinGroup('report')
+ self.exportMethod(self.create)
+
+ def create(self, cr, uid, ids, datas, context={}):
+ return False
+
+"""
+ Class to automatically build a document using the transformation process:
+ XML -> DATAS -> RML -> PDF
+ -> HTML
+ using a XSL:RML transformation
+"""
+class report_rml(report_int):
+ def __init__(self, name, table, tmpl, xsl):
+ super(report_rml, self).__init__(name)
+ self.table = table
+ self.tmpl = tmpl
+ self.xsl = xsl
+ self.bin_datas = {}
+ self.generators = {'pdf': self.create_pdf, 'html': self.create_html, 'raw': self.create_raw}
+
+ def create(self, cr, uid, ids, datas, context):
+ xml = self.create_xml(cr, uid, ids, datas, context)
+# file('/tmp/terp.xml','wb+').write(xml)
+ if datas.get('report_type', 'pdf') == 'raw':
+ return xml
+ rml = self.create_rml(cr, xml, uid, context)
+# file('/tmp/terp.rml','wb+').write(rml)
+ report_type = datas.get('report_type', 'pdf')
+ create_doc = self.generators[report_type]
+ pdf = create_doc(rml)
+ return (pdf, report_type)
+
+ def create_xml(self, cr, uid, ids, datas, context={}):
+ doc = print_xml.document(cr, uid, datas, {})
+ self.bin_datas = doc.bin_datas
+ doc.parse(self.tmpl, ids, self.table, context)
+ xml = doc.xml_get()
+ doc.close()
+ return self.post_process_xml_data(cr, uid, xml, context)
+
+ def post_process_xml_data(self, cr, uid, xml, context={}):
+ # find the position of the 3rd tag
+ # (skip the <?xml ...?> and the "root" tag)
+ iter = re.finditer('<[^>]*>', xml)
+ i = iter.next()
+ i = iter.next()
+ pos_xml = i.end()
+
+ doc = print_xml.document(cr, uid, {}, {})
+ tmpl_path = os.path.join(tools.config['root_path'], 'addons/custom/corporate_defaults.xml')
+ doc.parse(tmpl_path, [uid], 'res.users', context)
+ corporate_header = doc.xml_get()
+ doc.close()
+
+ # find the position of the tag after the <?xml ...?> tag
+ iter = re.finditer('<[^>]*>', corporate_header)
+ i = iter.next()
+ pos_header = i.end()
+
+ return xml[:pos_xml] + corporate_header[pos_header:] + xml[pos_xml:]
+
+ #
+ # TODO: The translation doesn't work for "<tag t="1">textext<tag> tex</tag>text</tag>"
+ #
+ def create_rml(self, cr, xml, uid, context={}):
+ service = netsvc.LocalService("object_proxy")
+
+ # In some case we might not use xsl ...
+ if not self.xsl:
+ return xml
+
+ # load XSL (parse it to the XML level)
+ styledoc = libxml2.parseFile(os.path.join(tools.config['root_path'],self.xsl))
+
+ #TODO: get all the translation in one query. That means we have to:
+ # * build a list of items to translate,
+ # * issue the query to translate them,
+ # * (re)build/update the stylesheet with the translated items
+
+ # translate the XSL stylesheet
+ def look_down(child, lang):
+ while child is not None:
+ if (child.type == "element") and child.hasProp('t'):
+ #FIXME: use cursor
+ res = service.execute(cr.dbname, uid, 'ir.translation', '_get_source', self.name2, 'xsl', lang, child.content)
+ if res:
+ child.setContent(res)
+ look_down(child.children, lang)
+ child = child.next
+
+ if context.get('lang', False):
+ look_down(styledoc.children, context['lang'])
+
+ style = libxslt.parseStylesheetDoc(styledoc) # parse XSL
+
+ doc = libxml2.parseMemory(xml,len(xml)) # load XML (data)
+ result = style.applyStylesheet(doc, None) # create RML (apply XSL to XML data)
+ xml = style.saveResultToString(result) # save result to string
+
+ style.freeStylesheet()
+ doc.freeDoc()
+ result.freeDoc()
+ return xml
+
+ def create_pdf(self, xml):
+ obj = render.rml(xml, self.bin_datas, os.path.dirname(self.tmpl))
+ obj.render()
+ return obj.get()
+
+ def create_html(self, xml):
+ obj = render.rml2html(xml, self.bin_datas)
+ obj.render()
+ return obj.get()
+
+ def create_raw(self, xml):
+ return xml
+
+from report_sxw import report_sxw
+
+def register_all(db):
+ opj = os.path.join
+ #FIXME: multi-db, quoique... ca init le code donc ok. Enfin, du moins si les modules sont les memes.
+ cr = db.cursor()
+ cr.execute("SELECT * FROM ir_act_report_xml WHERE auto ORDER BY id")
+ result = cr.dictfetchall()
+ cr.close()
+ for r in result:
+ if r['report_rml']:
+ report_sxw('report.'+r['report_name'], r['model'], opj('addons',r['report_rml']))
+ if r['report_xsl']:
+ report_rml('report.'+r['report_name'], r['model'], opj('addons',r['report_xml']), r['report_xsl'] and opj('addons',r['report_xsl']))
+
diff --git a/bin/report/misc.py b/bin/report/misc.py
new file mode 100644
index 00000000000..21601c5a11c
--- /dev/null
+++ b/bin/report/misc.py
@@ -0,0 +1,14 @@
+from pychart import *
+
+colorline = [color.T(r=((r+3) % 11)/10.0,
+ g=((g+6) % 11)/10.0,
+ b=((b+9) % 11)/10.0)
+ for r in range(11) for g in range(11) for b in range(11)]
+
+def choice_colors(n):
+ if n:
+ return colorline[0:-1:len(colorline)/n]
+ return []
+
+if __name__=='__main__':
+ print choice_colors(10)
diff --git a/bin/report/print_fnc.py b/bin/report/print_fnc.py
new file mode 100644
index 00000000000..7dc2251c6d1
--- /dev/null
+++ b/bin/report/print_fnc.py
@@ -0,0 +1,41 @@
+##############################################################################
+#
+# 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 time
+
+functions = {
+ 'today': lambda x: time.strftime('%d/%m/%Y', time.localtime()).decode('latin1')
+}
+
+#
+# TODO: call an object internal function too
+#
+def print_fnc(fnc, arg):
+ if fnc in functions:
+ return functions[fnc](arg)
+ return ''
diff --git a/bin/report/print_xml.py b/bin/report/print_xml.py
new file mode 100644
index 00000000000..671ec9806d1
--- /dev/null
+++ b/bin/report/print_xml.py
@@ -0,0 +1,358 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+#
+# $Id: print_xml.py 1005 2005-07-25 08:41:42Z nicoe $
+#
+# 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 os,types
+from xml.dom import minidom
+
+import netsvc
+import tools
+import print_fnc
+
+from osv.orm import browse_null, browse_record
+import pooler
+
+class InheritDict(dict):
+ # Might be usefull when we're doing name lookup for call or eval.
+
+ def __init__(self, parent=None):
+ self.parent = parent
+
+ def __getitem__(self, name):
+ if name in self:
+ return super(InheritDict, self).__getitem__(name)
+ else:
+ if not self.parent:
+ raise KeyError
+ else:
+ return self.parent[name]
+
+def tounicode(val):
+ if isinstance(val, str):
+ unicode_val = unicode(val, 'utf-8')
+ elif isinstance(val, unicode):
+ unicode_val = val
+ else:
+ unicode_val = unicode(val)
+ return unicode_val
+
+class document(object):
+ def __init__(self, cr, uid, datas, func=False):
+ # create a new document
+ self.cr = cr
+ self.pool = pooler.get_pool(cr.dbname)
+ self.doc = minidom.Document()
+ self.func = func or {}
+ self.datas = datas
+ self.uid = uid
+ self.bin_datas = {}
+
+ def node_attrs_get(self, node):
+ attrs = {}
+ nattr = node.attributes
+ for i in range(nattr.length):
+ attr = nattr.item(i)
+ attrs[attr.localName] = attr.nodeValue
+# attrs[attr.name] = attr.nodeValue
+ return attrs
+
+ def get_value(self, browser, field_path):
+ fields = field_path.split('.')
+
+ if not len(fields):
+ print "WARNING: field name is empty!"
+ return ''
+
+ value = browser
+ for f in fields:
+ if isinstance(value, list):
+ if len(value)==0:
+ print "WARNING: empty list found!"
+ return ''
+# elif len(value)>1:
+# print "WARNING:", len(value), "possibilities for", value[0]._table_name , "picking first..."
+ value = value[0]
+ if isinstance(value, browse_null):
+ return ''
+ else:
+ value = value[f]
+
+ if isinstance(value, browse_null) or (type(value)==bool and not value):
+ return ''
+ else:
+ return value
+
+ def get_value2(self, browser, field_path):
+ value = self.get_value(browser, field_path)
+ if isinstance(value, browse_record):
+ return value.id
+ elif isinstance(value, browse_null):
+ return False
+ else:
+ return value
+
+ def eval(self, record, expr):
+#TODO: support remote variables (eg address.title) in expr
+# how to do that: parse the string, find dots, replace those dotted variables by temporary
+# "simple ones", fetch the value of those variables and add them (temporarily) to the _data
+# dictionary passed to eval
+
+#FIXME: it wont work if the data hasn't been fetched yet... this could
+# happen if the eval node is the first one using this browse_record
+# the next line is a workaround for the problem: it causes the resource to be loaded
+#Pinky: Why not this ? eval(expr, browser) ?
+# name = browser.name
+# data_dict = browser._data[self.get_value(browser, 'id')]
+ return eval(expr)
+
+ def parse_node(self, node, parent, browser, datas=None):
+ # node is the node of the xml template to be parsed
+ # parent = the parent node in the xml data tree we are creating
+
+ if node.nodeType == node.ELEMENT_NODE:
+# print '-'*60
+# print "parse_node", node
+# print "parent: ", parent
+# print "ids:", ids
+# print "model:", model
+# print "datas:", datas
+
+ # convert the attributes of the node to a dictionary
+
+ attrs = self.node_attrs_get(node)
+ if 'type' in attrs:
+ if attrs['type']=='field':
+ value = self.get_value(browser, attrs['name'])
+#TODO: test this
+ if value == '' and 'default' in attrs:
+ value = attrs['default']
+ el = self.doc.createElement(node.localName)
+ parent.appendChild(el)
+ el_txt = self.doc.createTextNode(tounicode(value))
+ el.appendChild(el_txt)
+
+#TODO: test this
+ for key, value in attrs.iteritems():
+ if key not in ('type', 'name', 'default'):
+ el.setAttribute(key, value)
+
+ elif attrs['type']=='attachment':
+ if isinstance(browser, list):
+ model = browser[0]._table_name
+ else:
+ model = browser._table_name
+
+ value = self.get_value(browser, attrs['name'])
+
+ service = netsvc.LocalService("object_proxy")
+ ids = service.execute(self.cr.dbname, self.uid, 'ir.attachment', 'search', [('res_model','=',model),('res_id','=',int(value))])
+ datas = service.execute(self.cr.dbname, self.uid, 'ir.attachment', 'read', ids)
+
+ if len(datas):
+ # if there are several, pick first
+ datas = datas[0]
+ fname = str(datas['datas_fname'])
+ ext = fname.split('.')[-1].lower()
+ if ext in ('jpg','jpeg', 'png'):
+ import base64, StringIO
+ dt = base64.decodestring(datas['datas'])
+ fp = StringIO.StringIO(dt)
+ i = str(len(self.bin_datas))
+ self.bin_datas[i] = fp
+
+ el = self.doc.createElement(node.localName)
+ parent.appendChild(el)
+ # node content is the length of the image
+ el_txt = self.doc.createTextNode(i)
+ el.appendChild(el_txt)
+
+ elif attrs['type']=='data':
+#TODO: test this
+ el = self.doc.createElement(node.localName)
+ parent.appendChild(el)
+ txt = self.datas.get('form', {}).get(attrs['name'], '')
+ el_txt = self.doc.createTextNode(tounicode(txt))
+ el.appendChild(el_txt)
+
+ elif attrs['type']=='function':
+ el = self.doc.createElement(node.localName)
+ parent.appendChild(el)
+ if attrs['name'] in self.func:
+ txt = self.func[attrs['name']](node)
+ else:
+ txt = print_fnc.print_fnc(attrs['name'], node)
+ el_txt = self.doc.createTextNode(txt)
+ el.appendChild(el_txt)
+
+ elif attrs['type']=='eval':
+#TODO: faire ca plus proprement
+ if isinstance(browser, list):
+ print "ERROR: EVAL!"
+ el = self.doc.createElement(node.localName)
+ parent.appendChild(el)
+ value = self.eval(browser, attrs['expr'])
+ el_txt = self.doc.createTextNode(str(value))
+ el.appendChild(el_txt)
+
+ elif attrs['type']=='fields':
+ fields = attrs['name'].split(',')
+ vals = {}
+ for b in browser:
+ value = tuple([self.get_value2(b, f) for f in fields])
+ if not value in vals:
+ vals[value]=[]
+ vals[value].append(b)
+ keys = vals.keys()
+ keys.sort()
+
+ if 'order' in attrs and attrs['order']=='desc':
+ keys.reverse()
+
+ v_list = [vals[k] for k in keys]
+ for v in v_list:
+ el = self.doc.createElement(node.localName)
+ parent.appendChild(el)
+ el_cld = node.firstChild
+ while el_cld:
+ self.parse_node(el_cld, el, v)
+ el_cld = el_cld.nextSibling
+
+ elif attrs['type']=='call':
+ if len(attrs['args']):
+#TODO: test this
+ # fetches the values of the variables which names where passed in the args attribute
+ args = [self.eval(browser, arg) for arg in attrs['args'].split(',')]
+ else:
+ args = []
+
+ # get the object
+ if attrs.has_key('model'):
+ obj = self.pool.get(attrs['model'])
+ else:
+ if isinstance(browser, list):
+ obj = browser[0]._table
+ else:
+ obj = browser._table
+
+ # get the ids
+ if attrs.has_key('ids'):
+ ids = self.eval(browser, attrs['ids'])
+ else:
+ if isinstance(browser, list):
+ ids = [b.id for b in browser]
+ else:
+ ids = [browser.id]
+
+ # call the method itself
+ newdatas = getattr(obj, attrs['name'])(self.cr, self.uid, ids, *args)
+
+ def parse_result_tree(node, parent, datas):
+ if node.nodeType == node.ELEMENT_NODE:
+ el = self.doc.createElement(node.localName)
+ parent.appendChild(el)
+ atr = self.node_attrs_get(node)
+ if 'value' in atr:
+ #print "type=>",type(datas[atr['value']])
+ #print "value=>",datas[atr['value']]
+ if not isinstance(datas[atr['value']], (str, unicode)):
+ txt = self.doc.createTextNode(str(datas[atr['value']]))
+ else:
+ txt = self.doc.createTextNode(datas[atr['value']].decode('utf-8'))
+ el.appendChild(txt)
+ else:
+ el_cld = node.firstChild
+ while el_cld:
+ parse_result_tree(el_cld, el, datas)
+ el_cld = el_cld.nextSibling
+ elif node.nodeType==node.TEXT_NODE:
+ el = self.doc.createTextNode(node.nodeValue)
+ parent.appendChild(el)
+ else:
+ pass
+
+ if not isinstance(newdatas, list):
+ newdatas = [newdatas]
+ for newdata in newdatas:
+ parse_result_tree(node, parent, newdata)
+
+ elif attrs['type']=='zoom':
+ value = self.get_value(browser, attrs['name'])
+
+ if value:
+ if not isinstance(value, list):
+ v_list = [value]
+ else:
+ v_list = value
+ for v in v_list:
+ el = self.doc.createElement(node.localName)
+ parent.appendChild(el)
+ el_cld = node.firstChild
+ while el_cld:
+ self.parse_node(el_cld, el, v)
+ el_cld = el_cld.nextSibling
+ else:
+ # if there is no "type" attribute in the node, copy it to the xml data and parse its childs
+ el = self.doc.createElement(node.localName)
+ parent.appendChild(el)
+ el_cld = node.firstChild
+ while el_cld:
+ self.parse_node(el_cld, el, browser)
+ el_cld = el_cld.nextSibling
+
+ elif node.nodeType==node.TEXT_NODE:
+ # if it's a text node, copy it to the xml data
+ el = self.doc.createTextNode(node.nodeValue)
+ parent.appendChild(el)
+ else:
+ pass
+
+ def xml_get(self):
+ return self.doc.toxml('utf-8')
+
+ def parse_tree(self, ids, model, context={}):
+ browser = self.pool.get(model).browse(self.cr, self.uid, ids, context)
+ self.parse_node(self.dom.documentElement, self.doc, browser)
+
+ def parse_string(self, xml, ids, model, context={}):
+ # parses the xml template to memory
+ self.dom = minidom.parseString(xml)
+
+ # create the xml data from the xml template
+ self.parse_tree(ids, model, context)
+
+ def parse(self, filename, ids, model, context={}):
+ # parses the xml template to memory
+ self.dom = minidom.parse(os.path.join(tools.config['root_path'],filename))
+
+ # create the xml data from the xml template
+ self.parse_tree(ids, model, context)
+
+ def close(self):
+ self.doc = None
+ self.dom = None
+
diff --git a/bin/report/printscreen/__init__.py b/bin/report/printscreen/__init__.py
new file mode 100644
index 00000000000..1ee255bbfb4
--- /dev/null
+++ b/bin/report/printscreen/__init__.py
@@ -0,0 +1,31 @@
+##############################################################################
+#
+# 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 ps_list
+import ps_form
+
diff --git a/bin/report/printscreen/ps_form.py b/bin/report/printscreen/ps_form.py
new file mode 100644
index 00000000000..8968ab55821
--- /dev/null
+++ b/bin/report/printscreen/ps_form.py
@@ -0,0 +1,154 @@
+##############################################################################
+#
+# 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 report.interface import report_int
+import pooler
+import tools
+
+from report import render
+
+from xml.dom import minidom
+import libxml2
+import libxslt
+
+import time, os
+
+class report_printscreen_list(report_int):
+ def __init__(self, name):
+ report_int.__init__(self, name)
+
+ def _parse_node(self, root_node):
+ result = []
+ for node in root_node.childNodes:
+ if node.localName == 'field':
+ attrsa = node.attributes
+ attrs = {}
+ if not attrsa is None:
+ for i in range(attrsa.length):
+ attrs[attrsa.item(i).localName] = attrsa.item(i).nodeValue
+ result.append(attrs['name'])
+ else:
+ result.extend(self._parse_node(node))
+ return result
+
+ def _parse_string(self, view):
+ dom = minidom.parseString(view)
+ return self._parse_node(dom)
+
+ def create(self, cr, uid, ids, datas, context={}):
+ datas['ids'] = ids
+ model = pooler.get_pool(cr.dbname).get(datas['model'])
+ result = model.fields_view_get(cr, uid, view_type='tree', context=context)
+
+ fields_order = self._parse_string(result['arch'])
+ rows = model.read(cr, uid, datas['ids'], result['fields'].keys() )
+ res = self._create_table(uid, datas['ids'], result['fields'], fields_order, rows, context, model._description)
+ return (self.obj.get(), 'pdf')
+
+
+ def _create_table(self, uid, ids, fields, fields_order, results, context, title=''):
+ pageSize=[297.0,210.0]
+
+ impl = minidom.getDOMImplementation()
+ new_doc = impl.createDocument(None, "report", None)
+
+ # build header
+ config = new_doc.createElement("config")
+
+ def _append_node(name, text):
+ n = new_doc.createElement(name)
+ t = new_doc.createTextNode(text)
+ n.appendChild(t)
+ config.appendChild(n)
+
+ _append_node('date', time.strftime('%d/%m/%Y'))
+ _append_node('PageSize', '%.2fmm,%.2fmm' % tuple(pageSize))
+ _append_node('PageWidth', '%.2f' % (pageSize[0] * 2.8346,))
+ _append_node('PageHeight', '%.2f' %(pageSize[1] * 2.8346,))
+
+ _append_node('report-header', title)
+
+ l = []
+ t = 0
+ strmax = (pageSize[0]-40) * 2.8346
+ for f in fields_order:
+ s = 0
+ if fields[f]['type'] in ('date','time','float','integer'):
+ s = 60
+ strmax -= s
+ else:
+ t += fields[f].get('size', 56) / 28 + 1
+ l.append(s)
+ for pos in range(len(l)):
+ if not l[pos]:
+ s = fields[fields_order[pos]].get('size', 56) / 28 + 1
+ l[pos] = strmax * s / t
+ _append_node('tableSize', ','.join(map(str,l)) )
+ new_doc.childNodes[0].appendChild(config)
+ header = new_doc.createElement("header")
+
+ for f in fields_order:
+ field = new_doc.createElement("field")
+ field_txt = new_doc.createTextNode('%s' % (fields[f]['string'],))
+ field.appendChild(field_txt)
+ header.appendChild(field)
+
+ new_doc.childNodes[0].appendChild(header)
+
+ lines = new_doc.createElement("lines")
+ for line in results:
+ node_line = new_doc.createElement("row")
+ for f in fields_order:
+ if fields[f]['type']=='many2one' and line[f]:
+ line[f] = line[f][1]
+ if fields[f]['type'] in ('one2many','many2many') and line[f]:
+ line[f] = '( '+str(len(line[f])) + ' )'
+ col = new_doc.createElement("col")
+ col.setAttribute('tree','no')
+ if line[f] != None:
+ txt = new_doc.createTextNode(str(line[f] or ''))
+ else:
+ txt = new_doc.createTextNode('/')
+ col.appendChild(txt)
+ node_line.appendChild(col)
+ lines.appendChild(node_line)
+ new_doc.childNodes[0].appendChild(lines)
+
+ file('/tmp/terp.xml','w+').write(new_doc.toxml())
+
+ styledoc = libxml2.parseFile(os.path.join(tools.config['root_path'],'addons/base/report/custom_new.xsl'))
+ style = libxslt.parseStylesheetDoc(styledoc)
+ doc = libxml2.parseDoc(new_doc.toxml())
+ rml_obj = style.applyStylesheet(doc, None)
+ rml = style.saveResultToString(rml_obj)
+
+ self.obj = render.rml(rml)
+ self.obj.render()
+ return True
+report_printscreen_list('report.printscreen.form')
+
diff --git a/bin/report/printscreen/ps_list.py b/bin/report/printscreen/ps_list.py
new file mode 100644
index 00000000000..f247eaa3589
--- /dev/null
+++ b/bin/report/printscreen/ps_list.py
@@ -0,0 +1,152 @@
+##############################################################################
+#
+# 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 report.interface import report_int
+import pooler
+import tools
+
+from report import render
+
+from xml.dom import minidom
+import libxml2
+import libxslt
+
+import time, os
+
+class report_printscreen_list(report_int):
+ def __init__(self, name):
+ report_int.__init__(self, name)
+
+ def _parse_node(self, root_node):
+ result = []
+ for node in root_node.childNodes:
+ if node.localName == 'field':
+ attrsa = node.attributes
+ attrs = {}
+ if not attrsa is None:
+ for i in range(attrsa.length):
+ attrs[attrsa.item(i).localName] = attrsa.item(i).nodeValue
+ result.append(attrs['name'])
+ else:
+ result.extend(self._parse_node(node))
+ return result
+
+ def _parse_string(self, view):
+ dom = minidom.parseString(view)
+ return self._parse_node(dom)
+
+ def create(self, cr, uid, ids, datas, context={}):
+ datas['ids'] = ids
+ model = pooler.get_pool(cr.dbname).get(datas['model'])
+ result = model.fields_view_get(cr, uid, view_type='tree', context=context)
+
+ fields_order = self._parse_string(result['arch'])
+ rows = model.read(cr, uid, datas['ids'], result['fields'].keys() )
+ res = self._create_table(uid, datas['ids'], result['fields'], fields_order, rows, context, model._description)
+ return (self.obj.get(), 'pdf')
+
+
+ def _create_table(self, uid, ids, fields, fields_order, results, context, title=''):
+ pageSize=[297.0,210.0]
+
+ impl = minidom.getDOMImplementation()
+ new_doc = impl.createDocument(None, "report", None)
+
+ # build header
+ config = new_doc.createElement("config")
+
+ def _append_node(name, text):
+ n = new_doc.createElement(name)
+ t = new_doc.createTextNode(text)
+ n.appendChild(t)
+ config.appendChild(n)
+
+ _append_node('date', time.strftime('%d/%m/%Y'))
+ _append_node('PageSize', '%.2fmm,%.2fmm' % tuple(pageSize))
+ _append_node('PageWidth', '%.2f' % (pageSize[0] * 2.8346,))
+ _append_node('PageHeight', '%.2f' %(pageSize[1] * 2.8346,))
+
+ _append_node('report-header', title)
+
+ l = []
+ t = 0
+ strmax = (pageSize[0]-40) * 2.8346
+ for f in fields_order:
+ s = 0
+ if fields[f]['type'] in ('date','time','float','integer'):
+ s = 60
+ strmax -= s
+ else:
+ t += fields[f].get('size', 56) / 28 + 1
+ l.append(s)
+ for pos in range(len(l)):
+ if not l[pos]:
+ s = fields[fields_order[pos]].get('size', 56) / 28 + 1
+ l[pos] = strmax * s / t
+ _append_node('tableSize', ','.join(map(str,l)) )
+ new_doc.childNodes[0].appendChild(config)
+ header = new_doc.createElement("header")
+
+ for f in fields_order:
+ field = new_doc.createElement("field")
+ field_txt = new_doc.createTextNode('%s' % (fields[f]['string'],))
+ field.appendChild(field_txt)
+ header.appendChild(field)
+
+ new_doc.childNodes[0].appendChild(header)
+
+ lines = new_doc.createElement("lines")
+ for line in results:
+ node_line = new_doc.createElement("row")
+ for f in fields_order:
+ if fields[f]['type']=='many2one' and line[f]:
+ line[f] = line[f][1]
+ if fields[f]['type'] in ('one2many','many2many') and line[f]:
+ line[f] = '( '+str(len(line[f])) + ' )'
+ col = new_doc.createElement("col")
+ col.setAttribute('tree','no')
+ if line[f] != None:
+ txt = new_doc.createTextNode(str(line[f] or ''))
+ else:
+ txt = new_doc.createTextNode('/')
+ col.appendChild(txt)
+ node_line.appendChild(col)
+ lines.appendChild(node_line)
+ new_doc.childNodes[0].appendChild(lines)
+
+ styledoc = libxml2.parseFile(os.path.join(tools.config['root_path'],'addons/base/report/custom_new.xsl'))
+ style = libxslt.parseStylesheetDoc(styledoc)
+ doc = libxml2.parseDoc(new_doc.toxml())
+ rml_obj = style.applyStylesheet(doc, None)
+ rml = style.saveResultToString(rml_obj)
+
+ self.obj = render.rml(rml)
+ self.obj.render()
+ return True
+report_printscreen_list('report.printscreen.list')
+
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()
diff --git a/bin/report/report_sxw.py b/bin/report/report_sxw.py
new file mode 100644
index 00000000000..2b4c648b365
--- /dev/null
+++ b/bin/report/report_sxw.py
@@ -0,0 +1,309 @@
+##############################################################################
+#
+# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
+#
+# 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 xml.dom.minidom
+import os, time
+import ir, netsvc
+import osv
+from interface import report_rml
+import re
+import tools
+import pooler
+
+import copy
+
+parents = {
+ 'tr':1,
+ 'li':1,
+ 'story': 0,
+ 'section': 0
+}
+
+#
+# Context: {'node': node.dom}
+#
+class browse_record_list(list):
+ def __init__(self, lst, context):
+ super(browse_record_list, self).__init__(lst)
+ self.parents = copy.copy(parents)
+ self.context = context
+
+ def __getattr__(self, name):
+ res = browse_record_list([getattr(x,name) for x in self], self.context)
+ return res
+
+ def __str__(self):
+ return "browse_record_list("+str(len(self))+")"
+
+ def repeatIn(self, name):
+ print 'Decrecated ! Use repeatIn(object_list, \'variable\')'
+ node = self.context['_node']
+ node.data = ''
+ while True:
+ if not node.parentNode:
+ break
+ node = node.parentNode
+ if node.nodeType == node.ELEMENT_NODE and node.localName in parents:
+ break
+ parent_node = node
+ if not len(self):
+ return None
+ nodes = [(0,node)]
+ for i in range(1,len(self)):
+ newnode = parent_node.cloneNode(1)
+ n = parent_node.parentNode
+ n.insertBefore(newnode, parent_node)
+ nodes.append((i,newnode))
+ for i,node in nodes:
+ self.context[name] = self[i]
+ self.context['_self']._parse_node(node)
+ return None
+
+class rml_parse(object):
+ def __init__(self, cr, uid, name, context={}):
+ self.cr = cr
+ self.uid = uid
+ self.pool = pooler.get_pool(cr.dbname)
+ user = self.pool.get('res.users').browse(cr, uid, uid)
+ self.localcontext = {
+ 'user': user,
+ 'company': user.company_id,
+ 'repeatIn': self.repeatIn,
+ 'setLang': self.setLang,
+ 'setTag': self.setTag,
+ 'removeParentNode': self.removeParentNode,
+ }
+ self.localcontext.update(context)
+ self.name = name
+ self._regex = re.compile('\[\[(.+?)\]\]')
+ self._transl_regex = re.compile('(\[\[.+?\]\])')
+ self._node = None
+# self.already = {}
+
+ def setTag(self, oldtag, newtag):
+ node = self._find_parent(self._node, [oldtag])
+ if node:
+ node.tagName = newtag
+ return None
+
+ def removeParentNode(self, tag):
+ node = self._find_parent(self._node, [tag])
+ if node:
+ parentNode = node.parentNode
+ parentNode.removeChild(node)
+ self._node = parentNode
+
+ def setLang(self, lang):
+ self.localcontext['lang'] = lang
+
+ def repeatIn(self, lst, name, nodes_parent=False):
+ self._node.data = ''
+ node = self._find_parent(self._node, nodes_parent or parents)
+
+ pp = node.parentNode
+ ns = node.nextSibling
+ pp.removeChild(node)
+ self._node = pp
+
+ if not len(lst):
+ return None
+ nodes = []
+ for i in range(len(lst)):
+ newnode = node.cloneNode(1)
+ if ns:
+ pp.insertBefore(newnode, ns)
+ else:
+ pp.appendChild(newnode)
+ nodes.append((i, newnode))
+ for i, node in nodes:
+ self.node_context[node] = {name: lst[i]}
+ return None
+
+ def _eval(self, expr):
+ try:
+ res = eval(expr, self.localcontext)
+ if res is False or res is None:
+ res = ''
+ except Exception,e:
+ print e.args
+ print e
+ print dir(e)
+ res = 'Error'
+ return res
+
+ def _find_parent(self, node, parents):
+ while True:
+ if not node.parentNode:
+ return False
+ node = node.parentNode
+ if node.nodeType == node.ELEMENT_NODE and node.localName in parents:
+ break
+ return node
+
+ def _parse_text(self, text, level=[]):
+ res = self._regex.findall(text)
+ todo = []
+ for key in res:
+ newtext = self._eval(key)
+ for i in range(len(level)):
+ if isinstance(newtext, list):
+ newtext = newtext[level[i]]
+ if isinstance(newtext, list):
+ todo.append((key, newtext))
+ else:
+ if not isinstance(newtext, basestring):
+ newtext = str(newtext)
+ # if there are two [[]] blocks the same, it will replace both
+ # but it's ok because it should evaluate to the same thing
+ # anyway
+ text = text.replace('[['+key+']]', newtext.decode('utf8'))
+ # translate the text
+ # the "split [[]] if not match [[]]" is not very nice, but I
+ # don't see how I could do it better...
+ # what I'd like to do is a re.sub(NOT pattern, func, string)
+ # but I don't know how to do that...
+ # translate the RML file
+ if 'lang' in self.localcontext:
+ lang = self.localcontext['lang']
+ if lang not in (False, 'en') and text and not text.isspace():
+ transl_obj = self.pool.get('ir.translation')
+ piece_list = self._transl_regex.split(text)
+ for pn in range(len(piece_list)):
+ if not self._transl_regex.match(piece_list[pn]):
+ source_string = piece_list[pn].replace('\n', ' ').strip()
+ if len(source_string):
+ translated_string = transl_obj._get_source(self.cr, self.uid, self.name, 'rml', lang, source_string)
+ if translated_string:
+ piece_list[pn] = piece_list[pn].replace(source_string, translated_string.decode('utf8'))
+ text = ''.join(piece_list)
+ self._node.data = text
+ if len(todo):
+ for key, newtext in todo:
+ parent_node = self._find_parent(self._node, parents)
+ assert parents.get(parent_node.localName, False), 'No parent node found !'
+ nodes = [parent_node]
+ for i in range(len(newtext) - 1):
+ newnode = parent_node.cloneNode(1)
+ if parents.get(parent_node.localName, False):
+ n = parent_node.parentNode
+ parent_node.parentNode.insertAfter(newnode, parent_node)
+ nodes.append(newnode)
+ return False
+ return text
+
+ def _parse_node(self):
+ level = []
+ while True:
+ if self._node.hasChildNodes():
+ self._node = self._node.firstChild
+ elif self._node.nextSibling:
+ self._node = self._node.nextSibling
+ else:
+ while self._node and not self._node.nextSibling:
+ self._node = self._node.parentNode
+ if not self._node:
+ break
+ self._node = self._node.nextSibling
+ if self._node in self.node_context:
+ self.localcontext.update(self.node_context[self._node])
+ if self._node.nodeType in (self._node.CDATA_SECTION_NODE, self._node.TEXT_NODE):
+# if self._node in self.already:
+# self.already[self._node] += 1
+# print "second pass!", self.already[self._node], '---%s---' % self._node.data
+# else:
+# self.already[self._node] = 0
+ self._parse_text(self._node.data, level)
+
+ return True
+
+ def _find_node(self, node, localname):
+ if node.localName==localname:
+ return node
+ for tag in node.childNodes:
+ if tag.nodeType==tag.ELEMENT_NODE:
+ found = self._find_node(tag, localname)
+ if found:
+ return found
+ return False
+
+ def _add_header(self, node):
+ rml_head = tools.file_open('custom/corporate_rml_header.rml').read()
+ head_dom = xml.dom.minidom.parseString(rml_head)
+ node2 = head_dom.documentElement
+ for tag in node2.childNodes:
+ if tag.nodeType==tag.ELEMENT_NODE:
+ found = self._find_node(node, tag.localName)
+ if found:
+ found.parentNode.replaceChild(tag, found)
+ return True
+
+ def preprocess(self, objects, data, ids):
+ self.localcontext['data'] = data
+ self.localcontext['objects'] = objects
+ self.datas = data
+ self.ids = ids
+ self.objects = objects
+
+ def _parse(self, rml_dom, objects, data):
+ self.node_context = {}
+ self.dom = rml_dom
+ self._node = self.dom.documentElement
+ self._add_header(self._node)
+ self._parse_node()
+ res = self.dom.documentElement.toxml('utf-8')
+ return res
+
+class report_sxw(report_rml):
+ def __init__(self, name, table, rml, parser=rml_parse):
+ report_rml.__init__(self, name, table, rml, '')
+ self.name = name
+ self.parser = parser
+
+ def getObjects(self, cr, uid, ids, context):
+ table_obj = pooler.get_pool(cr.dbname).get(self.table)
+ return table_obj.browse(cr, uid, ids, list_class=browse_record_list, context=context)
+
+ def create(self, cr, uid, ids, data, context={}):
+ rml = file(os.path.join(tools.config['root_path'], self.tmpl)).read()
+
+ rml_parser = self.parser(cr, uid, self.name2, context)
+ objs = self.getObjects(cr, uid, ids, context)
+ rml_parser.preprocess(objs, data, ids)
+
+ rml_dom = xml.dom.minidom.parseString(rml)
+
+ rml2 = rml_parser._parse(rml_dom, objs, data)
+ #f = file("/tmp/debug.rml", "w")
+ #f.write(rml2)
+ #f.close()
+ report_type= data.get('report_type','pdf')
+ create_doc = self.generators[report_type]
+ pdf = create_doc(rml2)
+ return (pdf, report_type)
+
+