Changeset 99:1efd21aa821f

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

- move column loop support code to its own method
- fix typos in tests

Location:
relatorio
Files:
2 modified

Legend:

Unmodified
Added
Removed
  • relatorio/templates/opendocument.py

    r98 r99  
    273273        # Some tag/attribute name constants 
    274274        table_namespace = self.namespaces['table'] 
    275         table_tag = '{%s}table' % table_namespace 
    276         table_col_tag = '{%s}table-column' % table_namespace 
    277275        table_row_tag = '{%s}table-row' % table_namespace 
    278276        table_cell_tag = '{%s}table-cell' % table_namespace 
    279         table_num_col_attr = '{%s}number-columns-repeated' % table_namespace 
    280         table_name_attr = '{%s}name' % table_namespace 
    281277 
    282278        office_name = '{%s}value' % self.namespaces['office'] 
     
    286282        py_attrs_attr = '{%s}attrs' % py_namespace 
    287283        py_replace = '{%s}replace' % py_namespace 
    288  
    289         repeat_tag = '{%s}repeat' % self.namespaces['relatorio'] 
    290284 
    291285        r_statements, closing_tags = self._relatorio_statements(tree) 
     
    324318                # handle horizontal repetitions (over columns) 
    325319                if directive == "for" and ancestor.tag == table_row_tag: 
    326                     self.has_col_loop = True 
    327  
    328                     # table (it is not necessarily the parent of ancestor) 
    329                     table_node = ancestor.iterancestors(table_tag).next() 
    330                     table_name = table_node.attrib[table_name_attr] 
    331  
    332                     # add counting instructions 
    333                     loop_id = id(opening) 
    334  
    335                     # 1) add reset counter code on the row opening tag 
    336                     #    (through a py:attrs attribute). 
    337                     # Note that table_name is not needed in the first two 
    338                     # operations, but a unique id within the table is required 
    339                     # to support nested column repetition 
    340                     ancestor.attrib[py_attrs_attr] = \ 
    341                         "__relatorio_reset_col_count(%d)" % loop_id 
    342  
    343                     # 2) add increment code (through a py:attrs attribute) on 
    344                     #    the first cell node after the opening (cell node) 
    345                     #    ancestor 
    346                     enclosed_cell = outermost_o_ancestor.getnext() 
    347                     assert enclosed_cell.tag == table_cell_tag 
    348                     enclosed_cell.attrib[py_attrs_attr] = \ 
    349                         "__relatorio_inc_col_count(%d)" % loop_id 
    350  
    351                     # 3) add "store count" code as a py:replace node, as the 
    352                     #    last child of the row 
    353                     attr_value = "__relatorio_store_col_count(%d, %r)" \ 
    354                                  % (loop_id, table_name) 
    355                     replace_node = EtreeElement(py_replace, 
    356                                                 attrib={'value': attr_value}, 
    357                                                 nsmap=self.namespaces) 
    358                     ancestor.append(replace_node) 
    359  
    360                     # find the position in the row of the cells holding the 
    361                     # <for> and </for> instructions 
    362                     #XXX: count cells only instead of * 
    363                     position_xpath_expr = 'count(preceding-sibling::*)' 
    364                     opening_pos = \ 
    365                         int(outermost_o_ancestor.xpath(position_xpath_expr, 
    366                                                    namespaces=self.namespaces)) 
    367                     closing_pos = \ 
    368                         int(outermost_c_ancestor.xpath(position_xpath_expr, 
    369                                                    namespaces=self.namespaces)) 
    370  
    371                     # check whether or not the opening tag spans several rows 
    372                     a_val = self._handle_row_spanned_column_loops(parsed, 
    373                                 outermost_o_ancestor, opening_pos, closing_pos) 
    374  
    375                     # check if this table's headers were already processed 
    376                     repeat_node = table_node.find(repeat_tag) 
    377                     if repeat_node is not None: 
    378                         prev_pos = (int(repeat_node.attrib['opening']), 
    379                                     int(repeat_node.attrib['closing'])) 
    380                         if (opening_pos, closing_pos) != prev_pos: 
    381                             raise Exception( 
    382                                 'Incoherent column repetition found! ' 
    383                                 'If a table has several lines with repeated ' 
    384                                 'columns, the repetition need to be on the ' 
    385                                 'same columns across all lines.') 
    386                     else: 
    387                         # compute splits: oo collapses the headers of adjacent 
    388                         # columns which use the same style. We need to split 
    389                         # any column header which is repeated so many times 
    390                         # that it encompasses any of the column headers that 
    391                         # we need to repeat 
    392                         to_split = [] 
    393                         idx = 0 
    394                         childs = list(table_node.iterchildren(table_col_tag)) 
    395                         for tag in childs: 
    396                             if table_num_col_attr in tag.attrib: 
    397                                 oldidx = idx 
    398                                 idx += int(tag.attrib[table_num_col_attr]) 
    399                                 if oldidx < opening_pos < idx or \ 
    400                                    oldidx < closing_pos < idx: 
    401                                     to_split.append(tag) 
    402                             else: 
    403                                 idx += 1 
    404  
    405                         # split tags 
    406                         for tag in to_split: 
    407                             tag_pos = table_node.index(tag) 
    408                             num = int(tag.attrib.pop(table_num_col_attr)) 
    409                             new_tags = [deepcopy(tag) for _ in range(num)] 
    410                             table_node[tag_pos:tag_pos+1] = new_tags 
    411  
    412                         # recompute the list of column headers as it could 
    413                         # have changed. 
    414                         coldefs = list(table_node.iterchildren(table_col_tag)) 
    415  
    416                         # compute the column header nodes corresponding to 
    417                         # the opening and closing tags. 
    418                         first = table_node[opening_pos] 
    419                         last = table_node[closing_pos] 
    420  
    421                         # add a <relatorio:repeat> node around the column 
    422                         # definitions nodes 
    423                         attribs = { 
    424                            "opening": str(opening_pos), 
    425                            "closing": str(closing_pos), 
    426                            "table": table_name 
    427                         } 
    428                         repeat_node = EtreeElement(repeat_tag, attrib=attribs, 
    429                                                    nsmap=self.namespaces) 
    430                         wrap_nodes_between(first, last, repeat_node) 
     320                    a_val = self._handle_column_loops(parsed, ancestor, 
     321                                                      opening, 
     322                                                      outermost_o_ancestor, 
     323                                                      outermost_c_ancestor) 
    431324 
    432325                # - we create a <py:xxx> node 
     
    458351                parent.attrib.pop(office_name, None) 
    459352 
    460     def _handle_row_spanned_column_loops(self, parsed, outer_o_node, 
     353    def _handle_column_loops(self, statement, ancestor, opening, 
     354                             outer_o_node, outer_c_node): 
     355        _, directive, attr, a_val = statement 
     356 
     357        self.has_col_loop = True 
     358 
     359        table_namespace = self.namespaces['table'] 
     360        table_col_tag = '{%s}table-column' % table_namespace 
     361        table_num_col_attr = '{%s}number-columns-repeated' % table_namespace 
     362 
     363        py_namespace = self.namespaces['py'] 
     364        py_attrs_attr = '{%s}attrs' % py_namespace 
     365 
     366        repeat_tag = '{%s}repeat' % self.namespaces['relatorio'] 
     367 
     368        # table node (it is not necessarily the direct parent of ancestor) 
     369        table_node = ancestor.iterancestors('{%s}table' % table_namespace) \ 
     370                             .next() 
     371        table_name = table_node.attrib['{%s}name' % table_namespace] 
     372 
     373        # add counting instructions 
     374        loop_id = id(opening) 
     375 
     376        # 1) add reset counter code on the row opening tag 
     377        #    (through a py:attrs attribute). 
     378        # Note that table_name is not needed in the first two 
     379        # operations, but a unique id within the table is required 
     380        # to support nested column repetition 
     381        ancestor.attrib[py_attrs_attr] = \ 
     382            "__relatorio_reset_col_count(%d)" % loop_id 
     383 
     384        # 2) add increment code (through a py:attrs attribute) on 
     385        #    the first cell node after the opening (cell node) 
     386        #    ancestor 
     387        enclosed_cell = outer_o_node.getnext() 
     388        assert enclosed_cell.tag == '{%s}table-cell' % table_namespace 
     389        enclosed_cell.attrib[py_attrs_attr] = \ 
     390            "__relatorio_inc_col_count(%d)" % loop_id 
     391 
     392        # 3) add "store count" code as a py:replace node, as the 
     393        #    last child of the row 
     394        attr_value = "__relatorio_store_col_count(%d, %r)" \ 
     395                     % (loop_id, table_name) 
     396        replace_node = EtreeElement('{%s}replace' % py_namespace, 
     397                                    attrib={'value': attr_value}, 
     398                                    nsmap=self.namespaces) 
     399        ancestor.append(replace_node) 
     400 
     401        # find the position in the row of the cells holding the 
     402        # <for> and </for> instructions 
     403        #XXX: count cells only instead of * ? 
     404        position_xpath_expr = 'count(preceding-sibling::*)' 
     405        opening_pos = \ 
     406            int(outer_o_node.xpath(position_xpath_expr, 
     407                                   namespaces=self.namespaces)) 
     408        closing_pos = \ 
     409            int(outer_c_node.xpath(position_xpath_expr, 
     410                                   namespaces=self.namespaces)) 
     411 
     412        # check whether or not the opening tag spans several rows 
     413        a_val = self._handle_row_spanned_column_loops( 
     414                    statement, outer_o_node, opening_pos, closing_pos) 
     415 
     416        # check if this table's headers were already processed 
     417        repeat_node = table_node.find(repeat_tag) 
     418        if repeat_node is not None: 
     419            prev_pos = (int(repeat_node.attrib['opening']), 
     420                        int(repeat_node.attrib['closing'])) 
     421            if (opening_pos, closing_pos) != prev_pos: 
     422                raise Exception( 
     423                    'Incoherent column repetition found! ' 
     424                    'If a table has several lines with repeated ' 
     425                    'columns, the repetition need to be on the ' 
     426                    'same columns across all lines.') 
     427        else: 
     428            # compute splits: oo collapses the headers of adjacent 
     429            # columns which use the same style. We need to split 
     430            # any column header which is repeated so many times 
     431            # that it encompasses any of the column headers that 
     432            # we need to repeat 
     433            to_split = [] 
     434            idx = 0 
     435            childs = list(table_node.iterchildren(table_col_tag)) 
     436            for tag in childs: 
     437                inc = int(tag.attrib.get(table_num_col_attr, 1)) 
     438                oldidx = idx 
     439                idx += inc 
     440                if oldidx < opening_pos < idx or \ 
     441                   oldidx < closing_pos < idx: 
     442                    to_split.append(tag) 
     443 
     444            # split tags 
     445            for tag in to_split: 
     446                tag_pos = table_node.index(tag) 
     447                num = int(tag.attrib.pop(table_num_col_attr)) 
     448                new_tags = [deepcopy(tag) for _ in range(num)] 
     449                table_node[tag_pos:tag_pos+1] = new_tags 
     450 
     451            # recompute the list of column headers as it could 
     452            # have changed. 
     453            coldefs = list(table_node.iterchildren(table_col_tag)) 
     454 
     455            # compute the column header nodes corresponding to 
     456            # the opening and closing tags. 
     457            first = table_node[opening_pos] 
     458            last = table_node[closing_pos] 
     459 
     460            # add a <relatorio:repeat> node around the column 
     461            # definitions nodes 
     462            attribs = { 
     463               "opening": str(opening_pos), 
     464               "closing": str(closing_pos), 
     465               "table": table_name 
     466            } 
     467            repeat_node = EtreeElement(repeat_tag, attrib=attribs, 
     468                                       nsmap=self.namespaces) 
     469            wrap_nodes_between(first, last, repeat_node) 
     470        return a_val 
     471 
     472    def _handle_row_spanned_column_loops(self, statement, outer_o_node, 
    461473                                         opening_pos, closing_pos): 
    462474        """handles column repetitions which span several rows, by duplicating 
    463475        the py:for node for each row, and make the loops work on a copy of the 
    464476        original iterable as to not exhaust generators.""" 
    465         _, directive, attr, a_val = parsed 
     477 
     478        _, directive, attr, a_val = statement 
    466479        table_rowspan_attr = '{%s}number-rows-spanned' \ 
    467480                             % self.namespaces['table'] 
     481 
     482        # checks wether there is a (meaningful) rowspan 
    468483        rows_spanned = int(outer_o_node.attrib.get(table_rowspan_attr, 1)) 
    469  
    470         # checks wether there is a (meaningful) rowspan 
    471484        if rows_spanned == 1: 
    472485            return a_val 
  • relatorio/tests/test_odt.py

    r89 r99  
    3535 
    3636def pseudo_gettext(string): 
    37     catalog = {'Mes collègues sont:': 'My collegues are:', 
     37    catalog = {'Mes collègues sont:': 'My colleagues are:', 
    3838               'Bonjour,': 'Hello,', 
    3939               'Je suis un test de templating en odt.': 
     
    4141               'Felix da housecat': unicode('Félix le chat de la maison', 
    4242                                            'utf8'), 
    43                'We sell stuffs': u'On vend des brols', 
     43               'We sell stuff': u'On vend des choses', 
    4444              } 
    4545    return catalog.get(string, string) 
     
    6666                                 'image/png')], 
    6767                     'oeuf': file(os.path.join(thisdir, 'egg.jpg')), 
    68                      'footer': u'We sell stuffs'} 
     68                     'footer': u'We sell stuff'} 
    6969 
    7070    def test_init(self): 
     
    8181        <text:a xlink:href="relatorio://foo">foo</text:a> 
    8282        </b:a>''' % 'urn:text' 
    83         parsed = self.oot.insert_directives(xml) 
    84         root = lxml.etree.parse(StringIO(xml)).getroot() 
    85         root_parsed = lxml.etree.parse(parsed).getroot() 
    86         eq_(root_parsed[0].attrib['{http://genshi.edgewall.org/}replace'], 
    87             'foo') 
     83        interpolated = self.oot.insert_directives(xml) 
     84        root_interpolated = lxml.etree.parse(interpolated).getroot() 
     85        root_attrs = root_interpolated[0].attrib 
     86        eq_(root_attrs['{http://genshi.edgewall.org/}replace'], 'foo') 
    8887 
    8988    def test_styles(self): 
     
    9190        stream = self.oot.generate(**self.data) 
    9291        rendered = stream.events.render() 
    93         ok_('We sell stuffs' in rendered) 
     92        ok_('We sell stuff' in rendered) 
    9493 
    9594        dico = self.data.copy() 
     
    117116        ok_('Felix da housecat' not in translated_xml) 
    118117        ok_('Félix le chat de la maison' in translated_xml) 
    119         ok_('We sell stuffs' not in translated_xml) 
    120         ok_('On vend des brols' in translated_xml) 
     118        ok_('We sell stuff' not in translated_xml) 
     119        ok_('On vend des choses' in translated_xml) 
    121120 
    122121    def test_images(self):