1
# -*- encoding: utf-8 -*-
3
#########################################################################
5
# Copyright (C) 2009 Domsense s.r.l. #
6
# @authors: Simone Orsi, KN dati Ltd (www.kndati.lv) #
8
#This program is free software: you can redistribute it and/or modify #
9
#it under the terms of the GNU General Public License as published by #
10
#the Free Software Foundation, either version 3 of the License, or #
11
#(at your option) any later version. #
13
#This program is distributed in the hope that it will be useful, #
14
#but WITHOUT ANY WARRANTY; without even the implied warranty of #
15
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
16
#GNU General Public License for more details. #
18
#You should have received a copy of the GNU General Public License #
19
#along with this program. If not, see <http://www.gnu.org/licenses/>. #
20
#########################################################################
25
from report.report_sxw import *
28
from xml.dom import minidom
31
from tools.translate import _
34
from relatorio.templates.opendocument import Template
35
#from genshi.filters import Translator
39
logger = netsvc.Logger()
42
from report_openoffice_pdf.DocumentConverter import DocumentConverter
44
DocumentConverter = False
46
from ExtraFunctions import ExtraFunctions
48
from tools import debug
50
class OpenOffice_parser(report_sxw):
52
def __init__(self, name, table, rml=False, parser=False, header=True, store=False):
53
report_sxw.__init__(self, name, table, rml, parser, header, store)
55
def _doc_optimized(self, cr, uid, data, document):
56
pool = pooler.get_pool(cr.dbname)
57
out_oo = StringIO.StringIO()
58
in_oo = StringIO.StringIO(document)
59
inzip = zipfile.ZipFile(in_oo, mode='r')
60
content_data = inzip.read('content.xml')
61
styles_data = inzip.read('styles.xml')
62
meta_data = inzip.read('meta.xml')
63
manifest_data = inzip.read('META-INF/manifest.xml')
64
##### manifest.xml editing #####
65
manifestdoc = minidom.parseString(manifest_data)
66
manifest = [x.getAttribute("manifest:full-path") for x in manifestdoc.getElementsByTagName('manifest:file-entry') \
67
if x.getAttribute("manifest:media-type")!="\\"]
68
namelist = filter(lambda a: a not in ('META-INF/manifest.xml','mimetype'), inzip.namelist())
69
thumbnails_nodes = filter(lambda a: a.getAttribute("manifest:full-path").startswith("Thumbnails"), \
70
manifestdoc.getElementsByTagName('manifest:file-entry'))
71
for thmb in thumbnails_nodes:
72
manifestdoc.firstChild.removeChild(thmb)
73
################################
75
if name not in manifest:
76
dirname = os.path.dirname(name)
77
if dirname not in manifest:
78
newNode = manifestdoc.createElement("manifest:file-entry")
79
newNode.setAttribute("manifest:media-type", "")
80
newNode.setAttribute("manifest:full-path", dirname+"/")
81
manifestdoc.firstChild.appendChild(newNode)
82
manifest.append(dirname)
83
newNode = manifestdoc.createElement("manifest:file-entry")
84
newNode.setAttribute("manifest:media-type", "")
85
newNode.setAttribute("manifest:full-path", name)
86
manifestdoc.firstChild.appendChild(newNode)
87
################################
89
####### meta.xml editing #######
90
metadoc = minidom.parseString(meta_data)
91
user_name = pool.get('res.users').browse(cr, uid, uid, {}).name
92
model_id = pool.get('ir.model').search(cr, uid, [('model','=',data['model'])])[0]
93
model_name = pool.get('ir.model').browse(cr, uid, model_id).name
94
if metadoc.getElementsByTagName('meta:creation-date'):
95
cdate_node = metadoc.getElementsByTagName('meta:creation-date')[0].childNodes[0]
96
cdate_node.nodeValue=time.strftime('%Y-%m-%dT%H:%M:%S')
98
newNode=metadoc.createElement("meta:creation-date")
99
newText=metadoc.createTextNode(time.strftime('%Y-%m-%dT%H:%M:%S'))
100
newNode.appendChild(newText)
101
if metadoc.getElementsByTagName('dc:date'):
102
date_node = metadoc.getElementsByTagName('dc:date')[0].childNodes[0]
103
date_node.nodeValue=time.strftime('%Y-%m-%dT%H:%M:%S')
105
newNode=metadoc.createElement("dc:date")
106
newText=metadoc.createTextNode(time.strftime('%Y-%m-%dT%H:%M:%S'))
107
newNode.appendChild(newText)
108
if metadoc.getElementsByTagName('meta:editing-cycles'):
109
date_node = metadoc.getElementsByTagName('meta:editing-cycles')[0].childNodes[0]
110
date_node.nodeValue=1
112
newNode=metadoc.createElement("meta:editing-cycles")
113
newText=metadoc.createTextNode(1)
114
newNode.appendChild(newText)
115
if metadoc.getElementsByTagName('dc:creator'):
116
date_node = metadoc.getElementsByTagName('dc:creator')[0].childNodes[0]
117
date_node.nodeValue=user_name
119
newNode=metadoc.createElement("dc:creator")
120
newText=metadoc.createTextNode(user_name)
121
newNode.appendChild(newText)
122
if metadoc.getElementsByTagName('dc:title'):
123
date_node = metadoc.getElementsByTagName('dc:title')[0].childNodes[0]
124
date_node.nodeValue=model_name
126
newNode=metadoc.createElement("dc:title")
127
newText=metadoc.createTextNode(model_name)
128
newNode.appendChild(newText)
129
################################
130
outzip = zipfile.ZipFile(out_oo, mode='w')
131
outzip.compression = 8
132
for file_inf in inzip.infolist():
133
if file_inf.filename=='META-INF/manifest.xml':
134
outzip.writestr(file_inf.filename, manifestdoc.toxml('utf-8'))
135
elif file_inf.filename=='meta.xml':
136
outzip.writestr(file_inf.filename, metadoc.toxml('utf-8'))
137
elif file_inf.filename=='content.xml' and not content_data.startswith('<?xml'):
138
outzip.writestr(file_inf.filename, '<?xml version="1.0" encoding="UTF-8"?>\n'+content_data)
139
elif file_inf.filename=='styles.xml' and not styles_data.startswith('<?xml'):
140
outzip.writestr(file_inf.filename, '<?xml version="1.0" encoding="UTF-8"?>\n'+styles_data)
141
elif file_inf.filename=='Thumbnails/thumbnail.png':
144
outzip.writestr(file_inf.filename, inzip.read(file_inf.filename))
148
return out_oo.getvalue()
150
def create_relatorio_report(self, cr, uid, ids, data, report_xml, context=None, output='odt'):
151
""" Returns an odt or a pdf generated with relatorio
153
#####################################################################################################
154
def replaceStyles(node_list1, node_list2, name):
156
while(o_index<len(node_list1)):
158
while(n_index<len(node_list2)):
159
if node_list1[o_index].getAttribute(name)==node_list2[n_index].getAttribute(name):
160
node_list1[o_index].parentNode.replaceChild(node_list2[n_index], node_list1[o_index])
164
######################################################################################################
168
context = context.copy()
170
objects = self.getObjects(cr, uid, ids, context)
171
oo_parser = self.parser(cr, uid, self.name2, context=context)
172
oo_parser.objects = objects
174
############# Get XML ############
175
xml_data_fields = oo_parser.localcontext.get('xml_data_fields', False)
177
for field in xml_data_fields:
179
if getattr(o, field):
180
xml_data = base64.decodestring(getattr(o, field))
181
xmldoc = minidom.parseString(xml_data)
182
setattr(o, field, xmldoc.firstChild)
183
############## Get other Tamplate ##############
184
file_data = report_xml.report_sxw_content
185
if hasattr(oo_parser, 'get_template'):
186
pool = pooler.get_pool(cr.dbname)
187
record = pool.get(data['model']).browse(cr, uid, data['id'], {})
188
new_template = oo_parser.get_template(cr, uid, record)
189
file_data = new_template or file_data
190
################################################
193
in_oo = StringIO.StringIO(file_data)
194
inzip = zipfile.ZipFile(in_oo, mode='r')
196
############### Styles usage #################
197
if report_xml.styles_mode!='default':
198
out_oo = StringIO.StringIO()
199
outzip = zipfile.ZipFile(out_oo, mode='a')
200
if report_xml.styles_mode=='global':
201
pool = pooler.get_pool(cr.dbname)
202
company_id = pool.get('res.users')._get_company(cr, uid, context=context)
203
style_content = pool.get('res.company').browse(cr, uid, company_id, context=context).report_styles
204
elif report_xml.styles_mode=='specified':
205
style_content = report_xml.report_styles
209
for f in inzip.infolist():
210
if f.filename == 'styles.xml':
212
style_xml = inzip.read(f.filename)
214
outzip.writestr(f.filename, inzip.read(f.filename))
217
dom_style = minidom.parseString(style_xml)
218
node_style = dom_style.documentElement
220
style2_io = StringIO.StringIO()
221
style2_io.write(base64.decodestring(style_content))
222
style2_z = zipfile.ZipFile(style2_io, mode='r')
223
style2_xml = style2_z.read('styles.xml')
224
for file_name in style2_z.namelist():
225
if file_name.startswith('Pictures'):
226
picture = style2_z.read(file_name)
227
pictures.append((file_name, picture))
230
dom_style2 = minidom.parseString(style2_xml)
231
node_style2 = dom_style2.documentElement
232
font_face_new = node_style2.getElementsByTagName('office:font-face-decls')
233
font_face_orig = node_style.getElementsByTagName('office:font-face-decls')
234
orig_styles = replaceStyles(font_face_orig, font_face_new, 'style:name')
235
orig_styles = node_style.getElementsByTagName('style:style')
236
new_styles = node_style2.getElementsByTagName('style:style')
237
orig_styles = replaceStyles(orig_styles, new_styles, 'style:name')
239
outzip.writestr(style_info,
240
'<?xml version="1.0" encoding="UTF-8"?>' + \
241
dom_style.documentElement.toxml('utf-8'))
243
for ffile, picture in pictures:
244
outzip.writestr(ffile, picture)
245
##############################################
246
if not outzip or not outzip.namelist():
247
out_oo = StringIO.StringIO(file_data)
248
outzip = zipfile.ZipFile(out_oo, mode='a')
249
############### Replace tags 'text:text-input' on 'text:placeholder' #################
251
for file_name in ('content.xml','styles.xml'):
252
content = outzip.read(file_name)
253
xmldoc = minidom.parseString(content)
254
for oldNode in xmldoc.getElementsByTagName('text:text-input'):
255
input_val = oldNode.getAttribute("text:description")
256
if input_val.startswith('<') and input_val.endswith('>'):
257
if input_val=='<_()>':
258
input_val = "<_('%s')>" % oldNode.firstChild.nodeValue
259
newNode = xmldoc.createElement("text:placeholder")
260
newNode.setAttribute("text:placeholder-type", "text")
261
textNode=xmldoc.createTextNode(input_val)
262
newNode.appendChild(textNode)
263
oldNode.parentNode.replaceChild(newNode,oldNode)
265
output_data[file_name] = xmldoc.toxml('utf-8')
267
outzip = zipfile.ZipFile(out_oo, mode='w')
268
for f_info in inzip.infolist():
269
if f_info.filename in output_data.keys():
270
outzip.writestr(f_info.filename, output_data[f_info.filename])
272
outzip.writestr(f_info.filename, inzip.read(f_info.filename))
273
######################################################################################
277
file_data = out_oo.getvalue()
278
##############################################
280
oo_parser.localcontext['objects'] = objects
281
oo_parser.localcontext['data'] = data
283
oo_parser.localcontext['o'] = objects[0]
284
xfunc = ExtraFunctions(cr, uid, report_xml.id, oo_parser.localcontext)
285
oo_parser.localcontext.update(xfunc.functions)
287
toremove = [] # for temp objects to be removed
288
toclose = [] # for StringIO objects to be closed
289
odt_fd, odt_path = tempfile.mkstemp(suffix='.odt', prefix='openoffice-report-')
290
toremove.append( (odt_fd,odt_path) )
291
fd = file(odt_path, 'wb')
292
fd.write( file_data )
295
basic = Template(source=None, filepath=odt_path)
297
data = self._doc_optimized(cr, uid, data, basic.generate(**oo_parser.localcontext).render().getvalue())
299
if output!='odt' and DocumentConverter:
301
pool = pooler.get_pool(cr.dbname)
302
module_id = pool.get('ir.module.module').search(cr, uid, [('name','=','report_openoffice_pdf')])
304
if pool.get('ir.module.module').browse(cr, uid, module_id[0], context=context).state=='installed':
310
cr.execute("SELECT host, port FROM oo_config")
311
host, port = cr.fetchone()
312
DC = DocumentConverter(host, port)
313
data = DC.convertByStream(data)
324
for item in toremove:
330
# override needed to keep the attachments' storing procedure
331
def create_single_pdf(self, cr, uid, ids, data, report_xml, context=None):
334
if report_xml.report_type =='oo-pdf':
335
return self.create_relatorio_report(cr, uid, ids, data, report_xml, context=context, output='pdf')
336
elif report_xml.report_type =='oo-odt':
337
return self.create_relatorio_report(cr, uid, ids, data, report_xml, context=context, output='odt')
339
context = context.copy()
340
title = report_xml.name
341
rml = report_xml.report_rml_content
342
oo_parser = self.parser(cr, uid, self.name2, context=context)
343
objs = self.getObjects(cr, uid, ids, context)
344
oo_parser.set_context(objs, data, ids, report_xml.report_type)
345
processed_rml = self.preprocess_rml(etree.XML(rml),report_xml.report_type)
346
if report_xml.header:
347
oo_parser._add_header(processed_rml)
349
logo = base64.decodestring(oo_parser.logo)
350
create_doc = self.generators[report_xml.report_type]
351
pdf = create_doc(etree.tostring(processed_rml),oo_parser.localcontext,logo,title.encode('utf8'))
352
return (pdf, report_xml.report_type)
354
def create_source_odt(self, cr, uid, ids, data, report_xml, context=None):
357
pool = pooler.get_pool(cr.dbname)
358
attach = report_xml.attachment
360
objs = self.getObjects(cr, uid, ids, context)
363
aname = eval(attach, {'object':obj, 'time':time})
365
if report_xml.attachment_use and aname and context.get('attachment_use', True):
366
aids = pool.get('ir.attachment').search(cr, uid, [('datas_fname','=',aname+'.odt'),('res_model','=',self.table),('res_id','=',obj.id)])
368
brow_rec = pool.get('ir.attachment').browse(cr, uid, aids[0])
369
if not brow_rec.datas:
371
d = base64.decodestring(brow_rec.datas)
372
results.append((d,'odt'))
374
result = self.create_single_pdf(cr, uid, [obj.id], data, report_xml, context)
377
name = aname+'.'+result[1]
378
pool.get('ir.attachment').create(cr, uid, {
380
'datas': base64.encodestring(result[0]),
382
'res_model': self.table,
388
import traceback, sys
389
tb_s = reduce(lambda x, y: x+y, traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback))
390
netsvc.Logger().notifyChannel('report', netsvc.LOG_ERROR,str(e))
391
results.append(result)
393
return self.create_single_pdf(cr, uid, ids, data, report_xml, context)
395
# override needed to intercept the call to the proper 'create' method
396
def create(self, cr, uid, ids, data, context=None):
397
pool = pooler.get_pool(cr.dbname)
398
ir_obj = pool.get('ir.actions.report.xml')
399
report_xml_ids = ir_obj.search(cr, uid,
400
[('report_name', '=', self.name[7:])], context=context)
402
report_xml = ir_obj.browse(cr, uid, report_xml_ids[0], context=context)
403
report_xml.report_rml = None
404
report_xml.report_rml_content = None
405
report_xml.report_sxw_content_data = None
406
report_rml.report_sxw_content = None
407
report_rml.report_sxw = None
410
rml = tools.file_open(self.tmpl, subdir=None).read()
411
report_type= data.get('report_type', 'pdf')
413
def __init__(self, *args, **argv):
414
for key,arg in argv.items():
415
setattr(self, key, arg)
416
report_xml = a(title=title, report_type=report_type, report_rml_content=rml, name=title, attachment=False, header=self.header)
418
report_type = report_xml.report_type
419
if report_type in ['sxw','odt']:
420
fnct = self.create_source_odt
421
elif report_type in ['pdf','raw','html','oo-pdf']:
422
fnct = self.create_source_pdf
423
elif report_type=='html2html':
424
fnct = self.create_source_html2html
425
elif report_type in ['oo-odt']:
426
#fnct = self.create_relatorio_report
427
fnct = self.create_source_odt
429
raise ReportTypeException(_('Unknown report type: %s') % report_type)
430
return fnct(cr, uid, ids, data, report_xml, context)
432
class ReportTypeException(Exception):
433
def __init__(self, value):
434
self.parameter = value
436
return repr(self.parameter)
438
#########################################################################
442
def load_from_file(filepath, dbname, key):
444
expected_class = 'Parser'
447
if filepath.find(os.getcwd())==-1:
448
filepath=os.getcwd()+filepath
449
mod_name,file_ext = os.path.splitext(os.path.split(filepath)[-1])
450
mod_name = '%s_%s_%s' % (dbname,mod_name,key)
452
if file_ext.lower() == '.py':
453
py_mod = imp.load_source(mod_name, filepath)
455
elif file_ext.lower() == '.pyc':
456
py_mod = imp.load_compiled(mod_name, filepath)
458
if expected_class in dir(py_mod):
459
class_inst = py_mod.Parser
464
def load_from_source(source):
465
expected_class = 'Parser'
466
context = {'Parser':None}
468
exec source in context
469
return context['Parser']
473
def register_report(name, model, tmpl_path, parser):
474
name = 'report.%s' % name
475
if netsvc.service_exist( name ):
476
#service = netsvc.SERVICES[name].parser
477
if isinstance( netsvc.SERVICES[name], OpenOffice_parser ):
479
del netsvc.SERVICES[name]
480
OpenOffice_parser(name, model, tmpl_path, parser=parser)
482
old_register_all = report.interface.register_all
483
def new_register_all(db):
484
value = old_register_all(db)
488
cr.execute("SELECT * FROM ir_act_report_xml")
489
records = cr.dictfetchall()
491
for record in records:
492
if record['report_type'] in ('oo-odt', 'oo-pdf'):
494
if record['parser_state']=='loc' and record['parser_loc']:
495
parser=load_from_file(record['parser_loc'], db.dbname, record['id']) or parser
496
elif record['parser_state']=='def' and record['parser_def']:
497
parser=load_from_source("from report import report_sxw\n"+record['parser_def']) or parser
498
register_report( record['report_name'], record['model'], record['report_rml'], parser)
501
report.interface.register_all = new_register_all