Show
Ignore:
Timestamp:
01/15/09 13:38:25 (4 years 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)

Files:
1 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: