| 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) |
| 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, |