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