improved
[odoo/odoo.git] / bin / report / print_xml.py
index 2370f53..f349e10 100644 (file)
+# -*- coding: utf-8 -*-
 ##############################################################################
+#    
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
 #
-# Copyright (c) 2004-2008 TINY SPRL. (http://tiny.be) All Rights Reserved.
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
 #
-# $Id$
+#    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 Affero General Public License for more details.
 #
-# 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.
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.     
 #
 ##############################################################################
 
 import os,types
-from xml.dom import minidom
-
+from lxml import etree
 import netsvc
 import tools
+from tools.safe_eval import safe_eval
 import print_fnc
-
+import copy
 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]
+    # 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
+    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):
+    def __init__(self, cr, uid, datas, func=False):
+        # create a new document
+        self.cr = cr
+        self.pool = pooler.get_pool(cr.dbname)
+        self.func = func or {}
+        self.datas = datas
+        self.uid = uid
+        self.bin_datas = {}
+
+    def node_attrs_get(self, node):
+        if len(node.attrib):
+            return node.attrib
+        return {}
+
+    def get_value(self, browser, field_path):
+        fields = field_path.split('.')
+
+        if not len(fields):
+            return ''
+
+        value = browser
+
+        for f in fields:
+            if isinstance(value, list):
+                if len(value)==0:
+                    return ''
+                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 
+# 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
 
@@ -126,241 +110,187 @@ class document(object):
 # 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'])
+#       name = browser.name
+#       data_dict = browser._data[self.get_value(browser, 'id')]
+        return safe_eval(expr, {}, {'obj': record})
+
+    def parse_node(self, node, parent, browser, datas=None):
+            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 = etree.SubElement(parent, node.tag)
+                    el.text = tounicode(value)
 #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)
+                    for key, value in attrs.iteritems():
+                        if key not in ('type', 'name', 'default'):
+                            el.set(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
+                            from StringIO import StringIO
+                            dt = base64.decodestring(datas['datas'])
+                            fp = StringIO()
+                            fp.write(dt)
+                            i = str(len(self.bin_datas))
+                            self.bin_datas[i] = fp
+                            el = etree.SubElement(parent, node.tag)
+                            el.text = i
+
+                elif attrs['type']=='data':
 #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':
+                    txt = self.datas.get('form', {}).get(attrs['name'], '')
+                    el = etree.SubElement(parent, node.tag)
+                    el.text = txt
+
+                elif attrs['type']=='function':
+                    if attrs['name'] in self.func:
+                        txt = self.func[attrs['name']](node)
+                    else:
+                        txt = print_fnc.print_fnc(attrs['name'], node)
+                    el = etree.SubElement(parent, node.tag)
+                    el.text = txt
+
+                elif attrs['type']=='eval':
+                    value = self.eval(browser, attrs['expr'])
+                    el = etree.SubElement(parent, node.tag)
+                    el.text = str(value)
+
+                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 = etree.SubElement(parent, node.tag)
+                        for el_cld in node:
+                            self.parse_node(el_cld, el, v)
+
+                elif attrs['type']=='call':
+                    if len(attrs['args']):
 #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=None):
-               if not context:
-                       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=None):
-               if not context:
-                       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=None):
-               if not context:
-                       context={}
-               # parses the xml template to memory
-               self.dom = minidom.parseString(tools.file_open(
-                               os.path.join(tools.config['root_path'],
-                                       filename)).read())
-
-               # create the xml data from the xml template
-               self.parse_tree(ids, model, context)
-
-       def close(self):
-               self.doc = None
-               self.dom = None
+                        # 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 not node.tag == etree.Comment:
+                            el = etree.SubElement(parent, node.tag)
+                            atr = self.node_attrs_get(node)
+                            if 'value' in atr:
+                                if not isinstance(datas[atr['value']], (str, unicode)):
+                                    txt = str(datas[atr['value']])
+                                else:
+                                     txt = datas[atr['value']]
+                                el.text = txt
+                            else:
+                                for el_cld in node:
+                                    parse_result_tree(el_cld, el, datas)
+                    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 = etree.SubElement(parent, node.tag)
+                            for el_cld in node:
+                                self.parse_node(el_cld, el, v)
+            else:
+                # if there is no "type" attribute in the node, copy it to the xml data and parse its childs
+                if not node.tag == etree.Comment:
+                    if node.tag == parent.tag:
+                        el = parent
+                    else:
+                        el = etree.SubElement(parent, node.tag)
+                    for el_cld in node:
+                        self.parse_node(el_cld,el, browser)
+    def xml_get(self):
+        return etree.tostring(self.doc,encoding="utf-8",xml_declaration=True,pretty_print=True)
+
+    def parse_tree(self, ids, model, context=None):
+        if not context:
+            context={}
+        browser = self.pool.get(model).browse(self.cr, self.uid, ids, context)
+        self.parse_node(self.dom, self.doc, browser)
+
+    def parse_string(self, xml, ids, model, context=None):
+        if not context:
+            context={}
+        # parses the xml template to memory
+        self.dom = etree.XML(xml)
+        # create the xml data from the xml template
+        self.parse_tree(ids, model, context)
+
+    def parse(self, filename, ids, model, context=None):
+        if not context:
+            context={}
+        # parses the xml template to memory
+        self.dom = etree.XML(tools.file_open(filename).read())
+        self.doc = etree.Element(self.dom.tag)
+        self.parse_tree(ids, model, context)
+
+    def close(self):
+        self.doc = None
+        self.dom = None
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: