Changeset 89:757b097c61a5

Show
Ignore:
Timestamp:
01/15/09 07:38:25 (19 months ago)
Author:
Ga?tan de Menten <ged@…>
Branch:
default
Message:

- simplified and optimized opening/closing tags matching code, and moved it

into the _relatorio_statements method

- optimized genshi tags replacement loop (common ancestor block)

Location:
relatorio
Files:
3 modified

Legend:

Unmodified
Added
Removed
  • relatorio/templates/opendocument.py

    r84 r89  
    1111# This program is distributed in the hope that it will be useful, but WITHOUT 
    1212# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
    13 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more  
     13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 
    1414# details. 
    1515# 
     
    4242    ChartTemplate = type(None) 
    4343 
    44 GENSHI_EXPR = re.compile(r'''((/)?(for|choose|otherwise|when|if|with)\s*(\s(\w+)=["'](.*)["']|$)|.*)''') 
     44GENSHI_EXPR = re.compile(r''' 
     45        (/)?                                 # is this a closing tag? 
     46        (for|choose|otherwise|when|if|with)  # tag directive 
     47        \s* 
     48        (?:\s(\w+)=["'](.*)["']|$)           # match a single attr & its value 
     49        | 
     50        .*                                   # or anything else 
     51        ''', re.VERBOSE) 
     52 
    4553EXTENSIONS = {'image/png': 'png', 
    4654              'image/jpeg': 'jpg', 
     
    6573 
    6674class ImageHref: 
    67     "A class used to add image in the odf zipfile" 
    68      
     75    "A class used to add images in the odf zipfile" 
     76 
    6977    def __init__(self, zfile, context): 
    7078        self.zip = zfile 
     
    7280 
    7381    def __call__(self, expr, name): 
     82        #FIXME: name argument is unused 
    7483        bitstream, mimetype = expr 
    7584        if isinstance(bitstream, Report): 
     
    97106    def _parse(self, source, encoding): 
    98107        """parses the odf file. 
    99          
     108 
    100109        It adds genshi directives and finds the inner docs. 
    101110        """ 
     
    115124            content = zf.read(c_path) 
    116125            styles = zf.read(s_path) 
    117              
     126 
    118127            c_parsed = template._parse(self.insert_directives(zf.read(c_path)), 
    119128                                       encoding) 
     
    147156    def _invert_style(self, tree): 
    148157        "inverts the text:a and text:span" 
    149         xpath_expr = "//text:a[starts-with(@xlink:href, 'relatorio://')]"\ 
     158        xpath_expr = "//text:a[starts-with(@xlink:href, 'relatorio://')]" \ 
    150159                     "/text:span" 
    151160        for span in tree.xpath(xpath_expr, namespaces=self.namespaces): 
     
    163172        # If this node href matches a genshi directive it is kept for further 
    164173        # processing. 
    165         r_statements, genshi_dir = [], [] 
    166174        xlink_href_attrib = '{%s}href' % self.namespaces['xlink'] 
    167175        text_a = '{%s}a' % self.namespaces['text'] 
    168176        placeholder = '{%s}placeholder' % self.namespaces['text'] 
    169  
    170177        s_xpath = "//text:a[starts-with(@xlink:href, 'relatorio://')]" \ 
    171178                  "| //text:placeholder" 
     179 
     180        r_statements = [] 
     181        opened_tags = [] 
     182        # We map each opening tag with its closing tag 
     183        closing_tags = {} 
    172184        for statement in tree.xpath(s_xpath, namespaces=self.namespaces): 
    173185            if statement.tag == placeholder: 
     
    182194                warnings.warn('No statement text in %s' % self.filepath) 
    183195            elif expr != statement.text and statement.tag == text_a: 
    184                 warnings.warn('url and text do not match in %s: %s != %s'  
     196                warnings.warn('url and text do not match in %s: %s != %s' 
    185197                              % (self.filepath, expr, 
    186198                                 statement.text.encode('utf-8'))) 
    187199 
    188             expr, closing, directive, _, attr, attr_val = \ 
     200            closing, directive, attr, attr_val = \ 
    189201                    GENSHI_EXPR.match(expr).groups() 
     202            is_opening = closing != '/' 
    190203            if directive is not None: 
    191                 genshi_dir.append((statement, closing)) 
    192             r_statements.append((statement,  
    193                                  (expr, closing, directive, attr, attr_val))) 
    194  
    195         return r_statements, genshi_dir 
     204                # map closing tags with their opening tag 
     205                if is_opening: 
     206                    opened_tags.append(statement) 
     207                else: 
     208                    closing_tags[id(opened_tags.pop())] = statement 
     209            # - we operate only on opening statements 
     210            if is_opening: 
     211                r_statements.append((statement, 
     212                                     (expr, directive, attr, attr_val)) 
     213                                   ) 
     214        assert not opened_tags 
     215 
     216        return r_statements, closing_tags 
    196217 
    197218    def _handle_relatorio_tags(self, tree): 
     
    207228        genshi_replace = '{%s}replace' % self.namespaces['py'] 
    208229 
    209         r_statements, genshi_directives = self._relatorio_statements(tree) 
    210         # We match the opening and closing directives together 
    211         idx = 0 
    212         genshi_pairs, inserted = [], [] 
    213         for statement, closing in genshi_directives: 
    214             if closing is None: 
    215                 genshi_pairs.append([statement, None]) 
    216                 inserted.append(idx) 
    217                 idx += 1 
    218             else: 
    219                 genshi_pairs[inserted.pop()][1] = statement 
     230        r_statements, closing_tags = self._relatorio_statements(tree) 
    220231 
    221232        for r_node, parsed in r_statements: 
    222             expr, c_dir, directive, attr, a_val = parsed 
    223  
     233            expr, directive, attr, a_val = parsed 
     234 
     235            # If the node is a genshi directive statement: 
    224236            if directive is not None: 
    225                 # If the node is a genshi directive statement: 
    226                 #    - we operate only on opening statement 
    227                 #    - we find the nearest ancestor of the closing and opening 
    228                 #      statement 
    229                 #    - we create a <py:xxx> node 
    230                 #    - we add all the node between the opening and closing 
    231                 #      statements to this new node 
    232                 #    - we replace the opening statement by the <py:for> node 
    233                 #    - we delete the closing statement 
    234  
    235                 if c_dir is not None: 
    236                     # pass the closing statements 
    237                     continue 
    238                 for pair in genshi_pairs: 
    239                     if pair[0] == r_node: 
    240                         opening, closing = pair 
    241                         break 
    242  
    243                 o_ancestors = list(opening.iterancestors()) 
     237 
     238                opening = r_node 
     239                closing = closing_tags[id(r_node)] 
     240 
     241                # - we find the nearest common ancestor of the closing and 
     242                #   opening statements 
     243                o_ancestors = [] 
    244244                c_ancestors = list(closing.iterancestors()) 
    245                 for node in o_ancestors: 
    246                     if node in c_ancestors: 
     245                ancestor = None 
     246                for node in opening.iterancestors(): 
     247                    try: 
     248                        idx = c_ancestors.index(node) 
     249                        assert c_ancestors[idx] == node 
     250                        del c_ancestors[idx:] 
    247251                        ancestor = node 
    248252                        break 
    249  
     253                    except ValueError: 
     254                        pass 
     255                    o_ancestors.append(node) 
     256                assert ancestor is not None, \ 
     257                       "No common ancestor found for opening and closing tag" 
     258 
     259                # - we create a <py:xxx> node 
    250260                genshi_node = EtreeElement('{%s}%s' % (self.namespaces['py'], 
    251                                                        directive),  
     261                                                       directive), 
    252262                                           attrib={attr: a_val}, 
    253263                                           nsmap=self.namespaces) 
     264 
     265                # - we add all the nodes between the opening and closing 
     266                #   statements to this new node 
    254267                can_append = False 
    255268                for node in ancestor.iterchildren(): 
    256269                    if node in o_ancestors: 
    257270                        outermost_o_ancestor = node 
     271                        assert outermost_o_ancestor == o_ancestors[-1] 
    258272                        can_append = True 
    259273                        continue 
    260274                    if node in c_ancestors: 
    261275                        outermost_c_ancestor = node 
     276                        assert outermost_c_ancestor == c_ancestors[-1] 
    262277                        break 
    263278                    if can_append: 
     279                        # we are between the opening and closing node 
    264280                        genshi_node.append(node) 
     281 
     282                # - we replace the opening statement by the <py:xxx> node 
    265283                ancestor.replace(outermost_o_ancestor, genshi_node) 
     284 
     285                # - we delete the closing statement (and its ancestors) 
    266286                ancestor.remove(outermost_c_ancestor) 
    267287            else: 
     
    281301 
    282302    def _handle_images(self, tree): 
    283         "replaces all draw:frame named 'image: ...' by a draw:image node"  
     303        "replaces all draw:frame named 'image: ...' by a draw:image node" 
    284304        draw_name = '{%s}name' % self.namespaces['draw'] 
    285305        draw_image = '{%s}image' % self.namespaces['draw'] 
     
    289309            d_name = draw.attrib[draw_name] 
    290310            attr_expr = "make_href(%s, %r)" % (d_name[7:], d_name[7:]) 
    291             image_node = EtreeElement(draw_image,  
     311            image_node = EtreeElement(draw_image, 
    292312                                      attrib={python_attrs: attr_expr}, 
    293313                                      nsmap=self.namespaces) 
     
    333353            elif f_info.filename in files: 
    334354                stream = files[f_info.filename] 
    335                 self.outzip.writestr(f_info.filename,  
     355                self.outzip.writestr(f_info.filename, 
    336356                                     output_encode(self.xml_serializer(stream))) 
    337357            else: 
  • relatorio/tests/test_api.py

    r80 r89  
    1111# This program is distributed in the hope that it will be useful, but WITHOUT 
    1212# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
    13 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more  
     13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 
    1414# details. 
    1515# 
     
    3939        "Testing the registration" 
    4040        reporting = ReportRepository() 
    41         reporting.add_report(StubObject, 'text/plain',  
     41        reporting.add_report(StubObject, 'text/plain', 
    4242                             os.path.join('templates', 'test.tmpl')) 
    4343 
     
    4949        eq_(mime, 'text/plain') 
    5050        eq_(report.mimetype, 'text/plain') 
    51         assert_true(report.fpath.endswith(os.path.join('templates',  
     51        assert_true(report.fpath.endswith(os.path.join('templates', 
    5252                                                       'test.tmpl'))) 
    5353 
     
    105105 
    106106        a = StubObject(name='Foo') 
    107         eq_(report(o=a, time="One o'clock").render(),  
     107        eq_(report(o=a, time="One o'clock").render(), 
    108108            "Hi Foo,\nIt's One o'clock to 2 !\n") 
    109         eq_(report(o=a, time="One o'clock", y=4).render(),  
     109        eq_(report(o=a, time="One o'clock", y=4).render(), 
    110110            "Hi Foo,\nIt's One o'clock to 5 !\n") 
    111         assert_raises(TypeError, report, a)  
     111        assert_raises(TypeError, report, a) 
    112112 
  • relatorio/tests/test_odt.py

    r81 r89  
    1212# This program is distributed in the hope that it will be useful, but WITHOUT 
    1313# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
    14 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more  
     14# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 
    1515# details. 
    1616# 
     
    3737    catalog = {'Mes collègues sont:': 'My collegues are:', 
    3838               'Bonjour,': 'Hello,', 
    39                'Je suis un test de templating en odt.':  
     39               'Je suis un test de templating en odt.': 
    4040                'I am an odt templating test', 
    4141               'Felix da housecat': unicode('Félix le chat de la maison', 
     
    5555                     'last_name': unicode('Møller', 'utf8'), 
    5656                     'ville': unicode('Liège', 'utf8'), 
    57                      'friends': [{'first_name': u'Camille',  
     57                     'friends': [{'first_name': u'Camille', 
    5858                                  'last_name': u'Salauhpe'}, 
    5959                                 {'first_name': u'Mathias', 
     
    8181        <text:a xlink:href="relatorio://foo">foo</text:a> 
    8282        </b:a>''' % 'urn:text' 
    83         parsed = self.oot.insert_directives(xml)  
     83        parsed = self.oot.insert_directives(xml) 
    8484        root = lxml.etree.parse(StringIO(xml)).getroot() 
    8585        root_parsed = lxml.etree.parse(parsed).getroot() 
    86         eq_(root_parsed[0].attrib['{http://genshi.edgewall.org/}replace'],  
     86        eq_(root_parsed[0].attrib['{http://genshi.edgewall.org/}replace'], 
    8787            'foo') 
    8888 
     
    144144    def test_regexp(self): 
    145145        "Testing the regexp used to find relatorio tags" 
    146         regexp = re.compile(GENSHI_EXPR) 
    147         group = regexp.match('for each="foo in bar"').groups() 
    148         eq_(group, ('for each="foo in bar"', None, 'for', ' each="foo in bar"', 
    149                     'each', 'foo in bar')) 
    150         group = regexp.match('foreach="foo in bar"').groups() 
    151         eq_(group, ('foreach="foo in bar"', None, None, None, None, None)) 
    152         group = regexp.match('/for').groups() 
    153         eq_(group, ('/for', '/', 'for', '', None, None)) 
    154         group = regexp.match('/for ').groups() 
    155         eq_(group, ('/for ', '/', 'for', '', None, None)) 
    156         group = regexp.match('formatLang("en")').groups() 
    157         eq_(group, ('formatLang("en")', None, None, None, None, None)) 
     146        # a valid expression 
     147        group = GENSHI_EXPR.match('for each="foo in bar"').groups() 
     148        eq_(group, (None, 'for', 'each', 'foo in bar')) 
     149 
     150        # invalid expr 
     151        group = GENSHI_EXPR.match('foreach="foo in bar"').groups() 
     152        eq_(group, (None, None, None, None)) 
     153 
     154        # valid closing tags 
     155        group = GENSHI_EXPR.match('/for').groups() 
     156        eq_(group, ('/', 'for', None, None)) 
     157        group = GENSHI_EXPR.match('/for ').groups() 
     158        eq_(group, ('/', 'for', None, None)) 
     159 
     160        # another non matching expr 
     161        group = GENSHI_EXPR.match('formatLang("en")').groups() 
     162        eq_(group, (None, None, None, None)) 
    158163 
    159164    def test_str(self):