root/relatorio/templates/opendocument.py @ 38:6b2dfffd96d8

Revision 38:6b2dfffd96d8, 10.0 kB (checked in by Nicolas ?vrard <nicoe@…>, 5 years ago)

renamed variables to be more explicit

Line 
1###############################################################################
2#
3# Copyright (c) 2007, 2008 OpenHex SPRL. (http://openhex.com) All Rights
4# Reserved.
5#
6# This program is free software; you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation; either version 2 of the License, or (at your option) any later
9# version.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14# details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program.  If not, see <http://www.gnu.org/licenses/>.
18#
19###############################################################################
20
21__metaclass__ = type
22
23import os
24import re
25import md5
26import urllib
27import zipfile
28from cStringIO import StringIO
29
30import lxml.etree
31import genshi
32import genshi.output
33from genshi.template import MarkupTemplate
34
35GENSHI_TAGS = re.compile(r'''relatorio://((/)?(for|choose|otherwise|when|if|with)( (\w+)=["'](.*)["']|)|.*)''')
36EXTENSIONS = {'image/png': 'png',
37              'image/jpeg': 'jpg',
38              'image/bmp': 'bmp',
39              'image/gif': 'gif',
40              'image/tiff': 'tif',
41              'image/xbm': 'xbm',
42             }
43
44_encode = genshi.output.encode
45ETElement = lxml.etree.Element
46
47
48class ImageHref:
49   
50    def __init__(self, zipfile):
51        self.zip = zipfile
52
53    def __call__(self, expr, name):
54        bitstream, mimetype = expr
55        bitstream.seek(0)
56        file_content = bitstream.read()
57        name = md5.new(file_content).hexdigest()
58        path = 'Pictures/%s.%s' % (name, EXTENSIONS[mimetype])
59        if path not in self.zip.namelist():
60            self.zip.writestr(path, file_content)
61        return {'{http://www.w3.org/1999/xlink}href': path}
62
63
64class Template(MarkupTemplate):
65
66    def __init__(self, source, filepath=None, filename=None, loader=None,
67                 encoding=None, lookup='strict', allow_exec=True):
68        self.namespaces = {}
69        super(Template, self).__init__(source, filepath, filename, loader,
70                                       encoding, lookup, allow_exec)
71
72    def _parse(self, source, encoding):
73        inzip = zipfile.ZipFile(self.filepath)
74        content = inzip.read('content.xml')
75        styles = inzip.read('styles.xml')
76        inzip.close()
77
78        content = super(Template, self)._parse(self.add_directives(content),
79                                               encoding)
80        styles = super(Template, self)._parse(self.add_directives(styles),
81                                              encoding)
82        return [(genshi.core.PI, ('relatorio', 'styles.xml'), None)] +\
83                styles +\
84                [(genshi.core.PI, ('relatorio', 'content.xml'), None)] +\
85                content
86
87    def add_directives(self, content):
88        tree = lxml.etree.parse(StringIO(content))
89        root = tree.getroot()
90        self.namespaces = root.nsmap.copy()
91        self.namespaces['py'] = 'http://genshi.edgewall.org/'
92
93        self._handle_text_a(tree)
94        self._handle_images(tree)
95        return StringIO(lxml.etree.tostring(tree))
96
97    def _handle_text_a(self, tree):
98        """
99        Will treat all text:a tag (py:if/for/choose/when/otherwise)
100        tags
101        """
102        # Some tag name constants
103        table_cell_tag = '{%s}table-cell' % self.namespaces['table']
104        attrib_name = '{%s}attrs' % self.namespaces['py']
105        office_name = '{%s}value' % self.namespaces['office']
106        office_valuetype = '{%s}value-type' % self.namespaces['office']
107        genshi_name = '{%s}replace' % self.namespaces['py']
108        xlink_href_attrib = '{%s}href' % self.namespaces['xlink']
109
110        # First we create the list of all the text:a nodes.
111        # If this node href matches the relatorio URL it is kept.
112        # If this node href matches a genshi directive it is kept for further
113        # processing.
114        genshi_directives, text_a = [], []
115        for statement in tree.xpath('//text:a', namespaces=self.namespaces):
116            href = urllib.unquote(statement.attrib[xlink_href_attrib])
117            match_obj = GENSHI_TAGS.match(href)
118            if match_obj is None:
119                continue
120            expr, closing, directive, _, attr, attr_val = match_obj.groups()
121            if directive is not None:
122                genshi_directives.append((statement, href))
123            text_a.append((statement, 
124                           (expr, closing, directive, attr, attr_val)))
125
126        # Then we match the opening and closing directives together
127        idx = 0
128        genshi_pairs, inserted = [], []
129        for statement, href in genshi_directives:
130            if not href.startswith('relatorio:///'):
131                genshi_pairs.append([statement, None])
132                inserted.append(idx)
133                idx += 1
134            else:
135                genshi_pairs[inserted.pop()][1] = statement
136
137        for a_node, parsed in text_a:
138            expr, c_dir, directive, attr, a_val = parsed
139
140            if directive is not None:
141                # If the text:a is a genshi directive statement:
142                #    - we operate only on opening statement
143                #    - we find the nearest ancestor of the closing and opening
144                #      statement
145                #    - we create a <py:xxx> node
146                #    - we add all the node between the opening and closing
147                #      statements to this new node
148                #    - we replace the opening statement by the <py:for> node
149                #    - we delete the closing statement
150
151                if c_dir is not None:
152                    # pass the closing statements
153                    continue
154                for pair in genshi_pairs:
155                    if pair[0] == a_node:
156                        break
157                opening, closing = pair
158
159                o_ancestors = list(opening.iterancestors())
160                c_ancestors = list(closing.iterancestors())
161                for n in o_ancestors:
162                    if n in c_ancestors:
163                        ancestor = n
164                        break
165
166                genshi_node = ETElement('{%s}%s' % (self.namespaces['py'],
167                                                    directive), 
168                                        attrib={attr: a_val},
169                                        nsmap=self.namespaces)
170                can_append = False
171                for node in ancestor.iterchildren():
172                    if node in o_ancestors:
173                        outermost_o_ancestor = node
174                        can_append = True
175                        continue
176                    if node in c_ancestors:
177                        outermost_c_ancestor = node
178                        break
179                    if can_append:
180                        genshi_node.append(node)
181                ancestor.replace(outermost_o_ancestor, genshi_node)
182                ancestor.remove(outermost_c_ancestor)
183            else:
184                # It's not a genshi statement it's a python expression
185                a_node.attrib['{%s}replace' % self.namespaces['py']] = expr
186                parent = a_node.getparent().getparent()
187                if parent is None or parent.tag != table_cell_tag:
188                    continue
189                if parent.attrib.get(office_valuetype, 'string') != 'string':
190                    # The grand-parent tag is a table cell we set the
191                    # office:value attribute of this cell
192                    dico = "{'%s': %s}" % (office_name, expr)
193                    parent.attrib[attrib_name] = dico
194                    parent.attrib.pop(office_name, None)
195
196    def _handle_images(self, tree):
197        for draw in tree.xpath('//draw:frame', namespaces=self.namespaces):
198            d_name = draw.attrib['{%s}name' % self.namespaces['draw']]
199            if d_name.startswith('image: '):
200                attr_expr = "make_href(%s, %r)" % (d_name[7:], d_name[7:])
201                attributes = {}
202                attributes['{%s}attrs' % self.namespaces['py']] = attr_expr
203                image_node = ETElement('{%s}image' % self.namespaces['draw'],
204                                       attrib=attributes,
205                                       nsmap=self.namespaces)
206                draw.replace(draw[0], image_node)
207
208
209    def generate(self, *args, **kwargs):
210        serializer = OOSerializer(self.filepath)
211        kwargs['make_href'] = ImageHref(serializer.outzip)
212        generate_all = super(Template, self).generate(*args, **kwargs)
213
214        return OOStream(generate_all, serializer)
215
216
217class OOStream(genshi.core.Stream):
218
219    def __init__(self, content_stream, serializer):
220        self.events = content_stream
221        self.serializer = serializer
222
223    def render(self, method=None, encoding='utf-8', out=None, **kwargs):
224        return self.serializer(self.events)
225
226    def serialize(self, method, **kwargs):
227        return self.render(method, **kwargs)
228
229    def __or__(self, function):
230        return OOStream(self.events | function, self.serializer)
231
232
233class OOSerializer:
234
235    def __init__(self, oo_path):
236        self.inzip = zipfile.ZipFile(oo_path)
237        self.new_oo = StringIO()
238        self.outzip = zipfile.ZipFile(self.new_oo, 'w')
239        self.xml_serializer = genshi.output.XMLSerializer()
240
241    def __call__(self, stream):
242        files = {'styles.xml': [], 'content.xml': []}
243        for kind, data, pos in stream:
244            if kind == genshi.core.PI and data[0] == 'relatorio':
245                stream_for = data[1]
246                continue
247            files[stream_for].append((kind, data, pos))
248
249        for f in self.inzip.infolist():
250            if f.filename in files:
251                stream = files[f.filename]
252                self.outzip.writestr(f.filename, 
253                                     _encode(self.xml_serializer(stream)))
254            else:
255                self.outzip.writestr(f, self.inzip.read(f.filename))
256        self.inzip.close()
257        self.outzip.close()
258
259        return self.new_oo
Note: See TracBrowser for help on using the browser.