~serpent-consulting-services/openerp-usa/fix-shipping_api_ups_cc

« back to all changes in this revision

Viewing changes to assembly_bom/report/tiny_sxw2rml.py

  • Committer: npgllc
  • Date: 2012-08-02 17:13:27 UTC
  • Revision ID: npgllc-20120802171327-2xgyyjjb5d1kx26y
Removed all the 6.0 compatible modules

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python
2
 
# -*- encoding: utf-8 -*-
3
 
##############################################################################
4
 
#
5
 
# Copyright (c):
6
 
#
7
 
#     2005 pyopenoffice.py Martin Simon (http://www.bezirksreiter.de)
8
 
#     2005 Fabien Pinckaers, TINY SPRL. (http://tiny.be)
9
 
#
10
 
# WARNING: This program as such is intended to be used by professional
11
 
# programmers who take the whole responsability of assessing all potential
12
 
# consequences resulting from its eventual inadequacies and bugs
13
 
# End users who are looking for a ready-to-use solution with commercial
14
 
# garantees and support are strongly adviced to contact a Free Software
15
 
# Service Company
16
 
#
17
 
# This program is Free Software; you can redistribute it and/or
18
 
# modify it under the terms of the GNU General Public License
19
 
# as published by the Free Software Foundation; either version 2
20
 
# of the License, or (at your option) any later version.
21
 
#
22
 
# This program is distributed in the hope that it will be useful,
23
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25
 
# GNU General Public License for more details.
26
 
#
27
 
# You should have received a copy of the GNU General Public License
28
 
# along with this program; if not, write to the Free Software
29
 
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
30
 
#
31
 
##############################################################################
32
 
 
33
 
"""
34
 
Tiny SXW2RML - The Open ERP's report engine
35
 
 
36
 
Tiny SXW2RMLis part of the Tiny report project.
37
 
Tiny Report is a module that allows you to render high quality PDF document
38
 
from an OpenOffice template (.sxw) and any relationnal database.
39
 
 
40
 
The whole source code is distributed under the terms of the
41
 
GNU Public Licence.
42
 
 
43
 
(c) 2005 pyopenoffice.py Martin Simon (http://www.bezirksreiter.de)
44
 
(c) 2005-TODAY, Fabien Pinckaers - Tiny sprl
45
 
"""
46
 
__version__ = '0.9'
47
 
 
48
 
 
49
 
import re
50
 
import string
51
 
import os
52
 
import zipfile
53
 
import xml.dom.minidom
54
 
from reportlab.lib.units import toLength
55
 
import base64
56
 
 
57
 
class DomApiGeneral:
58
 
    """General DOM API utilities."""
59
 
    def __init__(self,content_string="",file=""):
60
 
        self.content_string = content_string
61
 
        self.re_digits = re.compile(r"(.*?\d)(pt|cm|mm|inch|in)")
62
 
 
63
 
    def _unitTuple(self,string):
64
 
        """Split values and units to a tuple."""
65
 
        temp = self.re_digits.findall(string)
66
 
        if not temp:
67
 
            return (string,"")
68
 
        else:
69
 
            return (temp[0])
70
 
 
71
 
    def stringPercentToFloat(self,string):
72
 
        temp = string.replace("""%""","")
73
 
        return float(temp)/100
74
 
 
75
 
    def findChildrenByName(self,parent,name,attr_dict={}):
76
 
        """Helper functions. Does not work recursively.
77
 
        Optional: also test for certain attribute/value pairs."""
78
 
        children = []
79
 
        for c in parent.childNodes:
80
 
            if c.nodeType == c.ELEMENT_NODE and c.nodeName == name:
81
 
                children.append(c)
82
 
        if attr_dict == {}:
83
 
            return children
84
 
        else:
85
 
            return self._selectForAttributes(nodelist=children,attr_dict=attr_dict)
86
 
 
87
 
    def _selectForAttributes(self,nodelist,attr_dict):
88
 
        "Helper function."""
89
 
        selected_nodes = []
90
 
        for n in nodelist:
91
 
            check = 1
92
 
            for a in attr_dict.keys():
93
 
                if n.getAttribute(a) != attr_dict[a]:
94
 
                    # at least one incorrect attribute value?
95
 
                    check = 0
96
 
            if check:
97
 
                selected_nodes.append(n)
98
 
        return selected_nodes
99
 
 
100
 
    def _stringToTuple(self,s):
101
 
        """Helper function."""
102
 
        try:
103
 
            temp = string.split(s,",")
104
 
            return int(temp[0]),int(temp[1])
105
 
        except:
106
 
            return None
107
 
 
108
 
    def _tupleToString(self,t):
109
 
        try:
110
 
            return self.openOfficeStringUtf8("%s,%s" % (t[0],t[1]))
111
 
        except:
112
 
            return None
113
 
 
114
 
    def _lengthToFloat(self,value):
115
 
        v = value
116
 
        if not self.re_digits.search(v):
117
 
            return v
118
 
        try:
119
 
            if v[-4:] == "inch":
120
 
                # OO files use "inch" instead of "in" in Reportlab units
121
 
                v = v[:-2]
122
 
        except:
123
 
            pass
124
 
        try:
125
 
            c = round(toLength(v))
126
 
            return c
127
 
        except:
128
 
            return v
129
 
 
130
 
    def openOfficeStringUtf8(self,string):
131
 
        if type(string) == unicode:
132
 
            return string.encode("utf-8")
133
 
        tempstring = unicode(string,"cp1252").encode("utf-8")
134
 
        return tempstring
135
 
 
136
 
class DomApi(DomApiGeneral):
137
 
    """This class provides a DOM-API for XML-Files from an SXW-Archive."""
138
 
    def __init__(self,xml_content,xml_styles):
139
 
        DomApiGeneral.__init__(self)
140
 
        self.content_dom = xml.dom.minidom.parseString(xml_content)
141
 
        self.styles_dom = xml.dom.minidom.parseString(xml_styles)
142
 
        body = self.content_dom.getElementsByTagName("office:body")
143
 
        self.body = body and body[0]
144
 
 
145
 
        # TODO:
146
 
        self.style_dict = {}
147
 
        self.style_properties_dict = {}
148
 
 
149
 
        # ******** always use the following order:
150
 
        self.buildStyleDict()
151
 
        self.buildStylePropertiesDict()
152
 
        if self.styles_dom.getElementsByTagName("style:page-master").__len__()<>0:
153
 
            self.page_master = self.styles_dom.getElementsByTagName("style:page-master")[0]
154
 
        if  self.styles_dom.getElementsByTagName("style:page-layout").__len__()<>0 :
155
 
                        self.page_master = self.styles_dom.getElementsByTagName("style:page-layout")[0]        
156
 
        self.document = self.content_dom.getElementsByTagName("office:document-content")[0]
157
 
 
158
 
    def buildStylePropertiesDict(self):
159
 
        for s in self.style_dict.keys():
160
 
            self.style_properties_dict[s] = self.getStylePropertiesDict(s)
161
 
 
162
 
    def updateWithPercents(self,dict,updatedict):
163
 
        """Sometimes you find values like "115%" in the style hierarchy."""
164
 
        if not updatedict:
165
 
            # no style hierarchies for this style? =>
166
 
            return
167
 
        new_updatedict = copy.copy(updatedict)
168
 
        for u in new_updatedict.keys():
169
 
            try:
170
 
                if new_updatedict[u].find("""%""") != -1 and dict.has_key(u):
171
 
                    number = float(self.re_digits.search(dict[u]).group(1))
172
 
                    unit = self.re_digits.search(dict[u]).group(2)
173
 
                    new_number = self.stringPercentToFloat(new_updatedict[u]) * number
174
 
                    if unit == "pt":
175
 
                        new_number = int(new_number)
176
 
                        # no floats allowed for "pt"
177
 
                        # OOo just takes the int, does not round (try it out!)
178
 
                    new_updatedict[u] = "%s%s" % (new_number,unit)
179
 
                else:
180
 
                    dict[u] = new_updatedict[u]
181
 
            except:
182
 
                dict[u] = new_updatedict[u]
183
 
        dict.update(new_updatedict)
184
 
 
185
 
    def normalizeStyleProperties(self):
186
 
        """Transfer all style:style-properties attributes from the
187
 
        self.style_properties_hierarchical dict to the automatic-styles
188
 
        from content.xml. Use this function to preprocess content.xml for
189
 
        XSLT transformations etc.Do not try to implement this function
190
 
        with XSlT - believe me, it's a terrible task..."""
191
 
        styles_styles = self.styles_dom.getElementsByTagName("style:style")
192
 
        automatic_styles = self.content_dom.getElementsByTagName("office:automatic-styles")[0]
193
 
        for s in styles_styles:
194
 
            automatic_styles.appendChild(s.cloneNode(deep=1))
195
 
        content_styles = self.content_dom.getElementsByTagName("style:style")
196
 
        # these are the content_styles with styles_styles added!!!
197
 
        for s in content_styles:
198
 
            c = self.findChildrenByName(s,"style:properties")
199
 
            if c == []:
200
 
                # some derived automatic styles do not have "style:properties":
201
 
                temp = self.content_dom.createElement("style:properties")
202
 
                s.appendChild(temp)
203
 
                c = self.findChildrenByName(s,"style:properties")
204
 
            c = c[0]
205
 
            dict = self.style_properties_dict[(s.getAttribute("style:name")).encode("utf-8")] or {}
206
 
            for attribute in dict.keys():
207
 
                c.setAttribute(self.openOfficeStringUtf8(attribute),self.openOfficeStringUtf8(dict[attribute]))
208
 
 
209
 
    def transferStylesXml(self):
210
 
        """Transfer certain sub-trees from styles.xml to the normalized content.xml
211
 
        (see above). It is not necessary to do this - for example - with paragraph styles.
212
 
        the "normalized" style properties contain all information needed for
213
 
        further processing."""
214
 
        # TODO: What about table styles etc.?
215
 
        outline_styles = self.styles_dom.getElementsByTagName("text:outline-style")
216
 
        t = self.content_dom.createElement("transferredfromstylesxml")
217
 
        self.document.insertBefore(t,self.body)
218
 
        t_new = self.body.previousSibling
219
 
        try:
220
 
            page_master = self.page_master
221
 
            t_new.appendChild(page_master.cloneNode(deep=1))
222
 
            t_new.appendChild(outline_styles[0].cloneNode(deep=1))
223
 
        except:
224
 
            pass
225
 
 
226
 
    def normalizeLength(self):
227
 
        """Normalize all lengthes to floats (i.e: 1 inch = 72).
228
 
        Always use this after "normalizeContent" and "transferStyles"!"""
229
 
        # TODO: The complex attributes of table cell styles are not transferred yet.
230
 
        #all_styles = self.content_dom.getElementsByTagName("style:properties")
231
 
        #all_styles += self.content_dom.getElementsByTagName("draw:image")
232
 
        all_styles = self.content_dom.getElementsByTagName("*")
233
 
        for s in all_styles:
234
 
            for x in s._attrs.keys():
235
 
                v = s.getAttribute(x)
236
 
                s.setAttribute(x,"%s" % self._lengthToFloat(v))
237
 
                # convert float to string first!
238
 
 
239
 
    def normalizeTableColumns(self):
240
 
        """Handle this strange table:number-columns-repeated attribute."""
241
 
        columns = self.content_dom.getElementsByTagName("table:table-column")
242
 
        for c in columns:
243
 
            if c.hasAttribute("table:number-columns-repeated"):
244
 
                number = int(c.getAttribute("table:number-columns-repeated"))
245
 
                c.removeAttribute("table:number-columns-repeated")
246
 
                for i in range(number-1):
247
 
                    (c.parentNode).insertBefore(c.cloneNode(deep=1),c)
248
 
 
249
 
    def buildStyleDict(self):
250
 
        """Store all style:style-nodes from content.xml and styles.xml in self.style_dict.
251
 
        Caution: in this dict the nodes from two dom apis are merged!"""
252
 
        for st in (self.styles_dom,self.content_dom):
253
 
            for s in st.getElementsByTagName("style:style"):
254
 
                name = s.getAttribute("style:name").encode("utf-8")
255
 
                self.style_dict[name] = s
256
 
        return True
257
 
 
258
 
    def toxml(self):
259
 
        return self.content_dom.toxml(encoding="utf-8")
260
 
 
261
 
    def getStylePropertiesDict(self,style_name):
262
 
        res = {}
263
 
 
264
 
        if self.style_dict[style_name].hasAttribute("style:parent-style-name"):
265
 
            parent = self.style_dict[style_name].getAttribute("style:parent-style-name").encode("utf-8")
266
 
            res = self.getStylePropertiesDict(parent)
267
 
 
268
 
        childs = self.style_dict[style_name].childNodes
269
 
        for c in childs:
270
 
            if c.nodeType == c.ELEMENT_NODE and c.nodeName.find("properties")>0 :
271
 
                for attr in c._attrs.keys():
272
 
                    res[attr] = c.getAttribute(attr).encode("utf-8")
273
 
        return res
274
 
 
275
 
class PyOpenOffice(object):
276
 
    """This is the main class which provides all functionality."""
277
 
    def __init__(self, path='.', save_pict=False):
278
 
        self.path = path
279
 
        self.save_pict = save_pict
280
 
        self.images = {}
281
 
 
282
 
    def oo_read(self,fname):
283
 
        z = zipfile.ZipFile(fname,"r")
284
 
        content = z.read('content.xml')
285
 
        style = z.read('styles.xml')
286
 
        all = z.namelist()
287
 
        for a in all:
288
 
            if a[:9]=='Pictures/' and len(a)>10:
289
 
                pic_content = z.read(a)
290
 
                self.images[a[9:]] = pic_content
291
 
                if self.save_pict:
292
 
                    f=open(os.path.join(self.path, os.path.basename(a)),"wb")
293
 
                    f.write(pic_content)
294
 
                    f.close()
295
 
        z.close()
296
 
        return content,style
297
 
 
298
 
    def oo_replace(self,content):
299
 
        regex = [
300
 
            (r"<para[^>]*/>", ""),
301
 
            #(r"<text:ordered-list.*?>(.*?)</text:ordered-list>", "$1"),
302
 
            #(r"<text:unordered-list.*?>(.*?)</text:unordered-list>", "$1"),
303
 
            (r"<para(.*)>(.*?)<text:line-break[^>]*/>", "<para$1>$2</para><para$1>"),
304
 
        ]
305
 
        for key,val in regex:
306
 
            content = re.sub(key, val, content)
307
 
        return content
308
 
 
309
 
    def unpackNormalize(self,sourcefile):
310
 
        c,s = self.oo_read(sourcefile)
311
 
        c = self.oo_replace(c)
312
 
        dom = DomApi(c,s)
313
 
        dom.normalizeStyleProperties()
314
 
        dom.transferStylesXml()
315
 
        dom.normalizeLength()
316
 
        dom.normalizeTableColumns()
317
 
        new_c = dom.toxml()
318
 
        return new_c
319
 
 
320
 
def sxw2rml(sxw_file, xsl, output='.', save_pict=False):
321
 
    from lxml import etree
322
 
    from StringIO import StringIO
323
 
 
324
 
    tool = PyOpenOffice(output, save_pict = save_pict)
325
 
    res = tool.unpackNormalize(sxw_file)
326
 
    
327
 
    f = StringIO(xsl)
328
 
    styledoc = etree.parse(f)
329
 
    style = etree.XSLT(styledoc)
330
 
    
331
 
    f = StringIO(res)
332
 
    doc = etree.parse(f)
333
 
    result = style(doc)
334
 
    root = etree.XPathEvaluator(result)("/document/stylesheet")
335
 
    
336
 
    if root:
337
 
        root=root[0]
338
 
        images = etree.Element("images")
339
 
        for img in tool.images:
340
 
            node = etree.Element('image', name=img)
341
 
            node.text = base64.encodestring(tool.images[img])
342
 
            images.append(node)
343
 
        root.append(images)
344
 
 
345
 
    try:
346
 
        xml = str(result)
347
 
        return xml
348
 
    except:
349
 
        return result
350
 
 
351
 
if __name__ == "__main__":
352
 
    import optparse
353
 
    parser = optparse.OptionParser(
354
 
        version="Tiny Report v%s" % __version__,
355
 
        usage = 'tiny_sxw2rml.py [options] file.sxw')
356
 
    parser.add_option("-v", "--verbose", default=False, dest="verbose", help="enable basic debugging")
357
 
    parser.add_option("-o", "--output", dest="output", default='.', help="directory of image output")
358
 
    (opt, args) = parser.parse_args()
359
 
    if len(args) != 1:
360
 
        parser.error("incorrect number of arguments")
361
 
 
362
 
    import sys
363
 
    import StringIO
364
 
 
365
 
    fname = sys.argv[1]
366
 
    f = fname
367
 
    xsl_file = 'normalized_oo2rml.xsl'
368
 
    z = zipfile.ZipFile(fname,"r")
369
 
    mimetype = z.read('mimetype')
370
 
    if mimetype.split('/')[-1] == 'vnd.oasis.opendocument.text' :
371
 
                xsl_file = 'normalized_odt2rml.xsl'
372
 
    xsl = file(os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]), xsl_file)).read()
373
 
    result = sxw2rml(f, xsl, output=opt.output, save_pict=False)
374
 
 
375
 
    print result
376
 
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
377