05f7e70378d089492e7c2629f787d7922de11788
[odoo/odoo.git] / bin / report / print_xml.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #    
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.     
19 #
20 ##############################################################################
21
22 import os,types
23 from lxml import etree
24 import netsvc
25 import tools
26 import print_fnc
27 import copy
28 from osv.orm import browse_null, browse_record
29 import pooler
30
31 class InheritDict(dict):
32     # Might be usefull when we're doing name lookup for call or eval.
33
34     def __init__(self, parent=None):
35         self.parent = parent
36
37     def __getitem__(self, name):
38         if name in self:
39             return super(InheritDict, self).__getitem__(name)
40         else:
41             if not self.parent:
42                 raise KeyError
43             else:
44                 return self.parent[name]
45
46 def tounicode(val):
47     if isinstance(val, str):
48         unicode_val = unicode(val, 'utf-8')
49     elif isinstance(val, unicode):
50         unicode_val = val
51     else:
52         unicode_val = unicode(val)
53     return unicode_val
54
55 class document(object):
56     def __init__(self, cr, uid, datas, func=False):
57         # create a new document
58         self.cr = cr
59         self.pool = pooler.get_pool(cr.dbname)
60         self.func = func or {}
61         self.datas = datas
62         self.uid = uid
63         self.bin_datas = {}
64
65     def node_attrs_get(self, node):
66         if len(node.attrib):
67             return node.attrib
68         return {}
69
70     def get_value(self, browser, field_path):
71         fields = field_path.split('.')
72
73         if not len(fields):
74             return ''
75
76         value = browser
77
78         for f in fields:
79             if isinstance(value, list):
80                 if len(value)==0:
81                     return ''
82                 value = value[0]
83             if isinstance(value, browse_null):
84                 return ''
85             else:
86                 value = value[f]
87
88         if isinstance(value, browse_null) or (type(value)==bool and not value):
89             return ''
90         else:
91             return value
92
93     def get_value2(self, browser, field_path):
94         value = self.get_value(browser, field_path)
95         if isinstance(value, browse_record):
96             return value.id
97         elif isinstance(value, browse_null):
98             return False
99         else:
100             return value
101
102     def eval(self, record, expr):
103 #TODO: support remote variables (eg address.title) in expr
104 # how to do that: parse the string, find dots, replace those dotted variables by temporary
105 # "simple ones", fetch the value of those variables and add them (temporarily) to the _data
106 # dictionary passed to eval
107
108 #FIXME: it wont work if the data hasn't been fetched yet... this could
109 # happen if the eval node is the first one using this browse_record
110 # the next line is a workaround for the problem: it causes the resource to be loaded
111 #Pinky: Why not this ? eval(expr, browser) ?
112 #       name = browser.name
113 #       data_dict = browser._data[self.get_value(browser, 'id')]
114         return eval(expr, {}, {'obj': record})
115
116     def parse_node(self, node, parent, browser, datas=None):
117             attrs = self.node_attrs_get(node)
118             if 'type' in attrs:
119                 if attrs['type']=='field':
120                     value = self.get_value(browser, attrs['name'])
121 #TODO: test this
122                     if value == '' and 'default' in attrs:
123                         value = attrs['default']
124                     el = etree.Element(node.tag)
125                     parent.append(el)
126                     el.text = tounicode(value)
127 #TODO: test this
128                     for key, value in attrs.iteritems():
129                         if key not in ('type', 'name', 'default'):
130                             el.set(key, value)
131
132                 elif attrs['type']=='attachment':
133                     if isinstance(browser, list):
134                         model = browser[0]._table_name
135                     else:
136                         model = browser._table_name
137
138                     value = self.get_value(browser, attrs['name'])
139
140                     service = netsvc.LocalService("object_proxy")
141                     ids = service.execute(self.cr.dbname, self.uid, 'ir.attachment', 'search', [('res_model','=',model),('res_id','=',int(value))])
142                     datas = service.execute(self.cr.dbname, self.uid, 'ir.attachment', 'read', ids)
143
144                     if len(datas):
145                         # if there are several, pick first
146                         datas = datas[0]
147                         fname = str(datas['datas_fname'])
148                         ext = fname.split('.')[-1].lower()
149                         if ext in ('jpg','jpeg', 'png'):
150                             import base64
151                             from StringIO import StringIO
152                             dt = base64.decodestring(datas['datas'])
153                             fp = StringIO()
154                             fp.write(dt)
155                             i = str(len(self.bin_datas))
156                             self.bin_datas[i] = fp
157                             el = etree.Element(node.tag)
158                             el.text = i
159                             parent.append(el)
160
161                 elif attrs['type']=='data':
162 #TODO: test this
163                     txt = self.datas.get('form', {}).get(attrs['name'], '')
164                     el = etree.Element(node.tag)
165                     el.text = txt
166                     parent.append(el)
167
168                 elif attrs['type']=='function':
169                     if attrs['name'] in self.func:
170                         txt = self.func[attrs['name']](node)
171                     else:
172                         txt = print_fnc.print_fnc(attrs['name'], node)
173                     el = etree.Element(node.tag)
174                     el.text = txt
175                     parent.append(el)
176
177                 elif attrs['type']=='eval':
178                     value = self.eval(browser, attrs['expr'])
179                     el = etree.Element(node.tag)
180                     el.text = str(value)
181                     parent.append(el)
182
183                 elif attrs['type']=='fields':
184                     fields = attrs['name'].split(',')
185                     vals = {}
186                     for b in browser:
187                         value = tuple([self.get_value2(b, f) for f in fields])
188                         if not value in vals:
189                             vals[value]=[]
190                         vals[value].append(b)
191                     keys = vals.keys()
192                     keys.sort()
193
194                     if 'order' in attrs and attrs['order']=='desc':
195                         keys.reverse()
196
197                     v_list = [vals[k] for k in keys]
198                     for v in v_list:
199                         el = etree.Element(node.tag)
200                         parent.append(el)
201                         for el_cld in node:
202                             self.parse_node(el_cld, el, v)
203
204                 elif attrs['type']=='call':
205                     if len(attrs['args']):
206 #TODO: test this
207                         # fetches the values of the variables which names where passed in the args attribute
208                         args = [self.eval(browser, arg) for arg in attrs['args'].split(',')]
209                     else:
210                         args = []
211                     # get the object
212                     if attrs.has_key('model'):
213                         obj = self.pool.get(attrs['model'])
214                     else:
215                         if isinstance(browser, list):
216                             obj = browser[0]._table
217                         else:
218                             obj = browser._table
219
220                     # get the ids
221                     if attrs.has_key('ids'):
222                         ids = self.eval(browser, attrs['ids'])
223                     else:
224                         if isinstance(browser, list):
225                             ids = [b.id for b in browser]
226                         else:
227                             ids = [browser.id]
228
229                     # call the method itself
230                     newdatas = getattr(obj, attrs['name'])(self.cr, self.uid, ids, *args)
231
232                     def parse_result_tree(node, parent, datas):
233                         if not node.tag == etree.Comment:
234                             el = etree.Element(node.tag)
235                             parent.append(el)
236                             atr = self.node_attrs_get(node)
237                             if 'value' in atr:
238                                 if not isinstance(datas[atr['value']], (str, unicode)):
239                                     txt = str(datas[atr['value']])
240                                 else:
241                                      txt = datas[atr['value']]
242                                 el.text = txt
243                             else:
244                                 for el_cld in node:
245                                     parse_result_tree(el_cld, el, datas)
246                     if not isinstance(newdatas, list):
247                         newdatas = [newdatas]
248                     for newdata in newdatas:
249                         parse_result_tree(node, parent, newdata)
250
251                 elif attrs['type']=='zoom':
252                     value = self.get_value(browser, attrs['name'])
253                     if value:
254                         if not isinstance(value, list):
255                             v_list = [value]
256                         else:
257                             v_list = value
258                         for v in v_list:
259                             el = etree.Element(node.tag)
260                             parent.append(el)
261                             for el_cld in node:
262                                 self.parse_node(el_cld, el, v)
263             else:
264                 # if there is no "type" attribute in the node, copy it to the xml data and parse its childs
265                 if not node.tag == etree.Comment:
266                     if node.tag == parent.tag:
267                         el = parent
268                     else:
269                         el = etree.Element(node.tag)
270                         parent.append(el)
271                     for el_cld in node:
272                         self.parse_node(el_cld,el, browser)
273     def xml_get(self):
274         return etree.tostring(self.doc,encoding="utf-8",xml_declaration=True,pretty_print=True)
275
276     def parse_tree(self, ids, model, context=None):
277         if not context:
278             context={}
279         browser = self.pool.get(model).browse(self.cr, self.uid, ids, context)
280         self.parse_node(self.dom, self.doc, browser)
281
282     def parse_string(self, xml, ids, model, context=None):
283         if not context:
284             context={}
285         # parses the xml template to memory
286         self.dom = etree.XML(xml)
287         # create the xml data from the xml template
288         self.parse_tree(ids, model, context)
289
290     def parse(self, filename, ids, model, context=None):
291         if not context:
292             context={}
293         # parses the xml template to memory
294         self.dom = etree.XML(tools.file_open(filename).read())
295         self.doc = etree.Element(self.dom.tag)
296         self.parse_tree(ids, model, context)
297
298     def close(self):
299         self.doc = None
300         self.dom = None
301
302
303 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
304