| 275 | | if False: |
| 276 | | #if directive == "for" and ancestor.tag == table_row_tag: |
| 277 | | print "horizontal repetition", a_val |
| 278 | | # find position of current cell in row |
| 279 | | position_xpath_expr = \ |
| 280 | | 'count(ancestor::table:table-cell/preceding-sibling::*)' |
| 281 | | opening_pos = opening.xpath(position_xpath_expr, |
| 282 | | namespaces=self.namespaces) |
| 283 | | closing_pos = closing.xpath(position_xpath_expr, |
| 284 | | namespaces=self.namespaces) |
| 285 | | print "opening_pos", opening_pos |
| 286 | | print "closing_pos", closing_pos |
| 287 | | |
| 288 | | idx = 0 |
| 289 | | table_node = opening.xpath('ancestor::table:table[1]', |
| 290 | | namespaces=self.namespaces)[0] |
| 291 | | #XXX: use getiterator('table:table-column') instead of xpath? |
| 292 | | to_split = [] |
| 293 | | to_move = [] |
| 294 | | for tag in table_node.xpath('table:table-column', |
| 295 | | namespaces=self.namespaces): |
| 296 | | if num_col_attr in tag.attrib: |
| 297 | | oldidx = idx |
| 298 | | idx += int(tag.attrib[num_col_attr]) |
| 299 | | print "oldidx", oldidx, "idx", idx |
| 300 | | if oldidx < opening_pos < idx or \ |
| 301 | | oldidx < closing_pos < idx: |
| 302 | | to_split.append(tag) |
| 303 | | print "to_split", to_split |
| 304 | | else: |
| 305 | | idx += 1 |
| 306 | | |
| 307 | | # split tags |
| 308 | | for tag in to_split: |
| 309 | | tag_pos = table_node.index(tag) |
| 310 | | print "tag_pos", tag_pos |
| 311 | | num = int(tag.attrib[num_col_attr]) |
| 312 | | tag.attrib.pop(num_col_attr) |
| 313 | | new_tags = [deepcopy(tag) for _ in range(num)] |
| 314 | | table_node[tag_pos:tag_pos] = new_tags |
| 315 | | |
| 316 | | # compute moves |
| 317 | | if False: |
| 318 | | if idx < opening_pos: |
| 319 | | pass |
| 320 | | elif opening_pos < idx < closing_pos: |
| 321 | | to_move.append(tag) |
| 322 | | else: |
| 323 | | break |
| 324 | | print idx |
| 325 | | # move tags |
| 326 | | # |
| 327 | | # add a <py:for each="%s"> % a_val |
| 328 | | # for_node = EtreeElement('{%s}%s' % (py_namespace, 'for'), |
| 329 | | # attrib={attr: a_val}, |
| 330 | | # nsmap=self.namespaces) |
| | 308 | if directive == "for" and ancestor.tag == table_row_tag: |
| | 309 | self.has_col_loop = True |
| | 310 | |
| | 311 | # table (it is not necessarily the parent of ancestor) |
| | 312 | table_node = ancestor.iterancestors(table_tag).next() |
| | 313 | table_name = table_node.attrib[table_name_attr] |
| | 314 | |
| | 315 | # add counting instructions |
| | 316 | loop_id = id(opening) |
| | 317 | |
| | 318 | # 1) add reset counter code on the row opening tag |
| | 319 | # (through a py:attrs attribute). |
| | 320 | # Note that table_name is not needed in the first two |
| | 321 | # operations, but a unique id within the table is required |
| | 322 | # to support nested column repetition |
| | 323 | attr_value = "__reset_col_count(%d)" % loop_id |
| | 324 | ancestor.attrib[py_attrs_attr] = attr_value |
| | 325 | |
| | 326 | # 2) add increment code (through a py:attrs attribute) on |
| | 327 | # the first cell node after the opening (cell node) |
| | 328 | # ancestor |
| | 329 | enclosed_cell = outermost_o_ancestor.itersiblings().next() |
| | 330 | assert enclosed_cell.tag == table_cell_tag |
| | 331 | attr_value = "__inc_col_count(%d)" % loop_id |
| | 332 | enclosed_cell.attrib[py_attrs_attr] = attr_value |
| | 333 | |
| | 334 | # 3) add "store count" code as a py:replace node, as the |
| | 335 | # last child of the row |
| | 336 | attr_value = "__store_col_count(%d, %r)" % (loop_id, |
| | 337 | table_name) |
| | 338 | replace_node = EtreeElement('{%s}replace' % py_namespace, |
| | 339 | attrib={'value': attr_value}, |
| | 340 | nsmap=self.namespaces) |
| | 341 | ancestor.append(replace_node) |
| | 342 | |
| | 343 | # find the position in the row of the cells holding the |
| | 344 | # <for> and </for> instructions |
| | 345 | position_xpath_expr = 'count(preceding-sibling::*)' |
| | 346 | opening_pos = \ |
| | 347 | int(outermost_o_ancestor.xpath(position_xpath_expr, |
| | 348 | namespaces=self.namespaces)) |
| | 349 | closing_pos = \ |
| | 350 | int(outermost_c_ancestor.xpath(position_xpath_expr, |
| | 351 | namespaces=self.namespaces)) |
| | 352 | |
| | 353 | # check if this table was already processed |
| | 354 | repeat_node = table_node.find(repeat_tag) |
| | 355 | if repeat_node is not None: |
| | 356 | prev_pos = (int(repeat_node.attrib['opening']), |
| | 357 | int(repeat_node.attrib['closing'])) |
| | 358 | if (opening_pos, closing_pos) != prev_pos: |
| | 359 | raise Exception( |
| | 360 | 'Incoherent column repetition found! ' |
| | 361 | 'If a table has several lines with repeated ' |
| | 362 | 'columns, the repetition need to be on the ' |
| | 363 | 'same columns across all lines.') |
| | 364 | else: |
| | 365 | # compute splits: we need to split any column header |
| | 366 | # which is repeated so many times that it encompass |
| | 367 | # any of the column headers that we need to repeat |
| | 368 | to_split = [] |
| | 369 | idx = 0 |
| | 370 | childs = list(table_node.iterchildren(table_col_tag)) |
| | 371 | assert closing_pos < len(childs) |
| | 372 | for tag in childs: |
| | 373 | if table_num_col_attr in tag.attrib: |
| | 374 | oldidx = idx |
| | 375 | idx += int(tag.attrib[table_num_col_attr]) |
| | 376 | if oldidx < opening_pos < idx or \ |
| | 377 | oldidx < closing_pos < idx: |
| | 378 | to_split.append(tag) |
| | 379 | else: |
| | 380 | idx += 1 |
| | 381 | |
| | 382 | # split tags |
| | 383 | for tag in to_split: |
| | 384 | tag_pos = table_node.index(tag) |
| | 385 | num = int(tag.attrib.pop(table_num_col_attr)) |
| | 386 | new_tags = [deepcopy(tag) for _ in range(num)] |
| | 387 | table_node[tag_pos:tag_pos+1] = new_tags |
| | 388 | |
| | 389 | # compute moves and deletes: the column headers |
| | 390 | # corresponding to the opening and closing tags |
| | 391 | # need to be deleted. The column headers between them |
| | 392 | # need to be moved to inside the "repeat" tag (which |
| | 393 | # is to be created later). |
| | 394 | to_remove = [] |
| | 395 | to_move = [] |
| | 396 | coldefs = table_node.iterchildren(table_col_tag) |
| | 397 | for idx, tag in enumerate(coldefs): |
| | 398 | if idx in (opening_pos, closing_pos): |
| | 399 | to_remove.append(tag) |
| | 400 | if opening_pos < idx < closing_pos: |
| | 401 | to_move.append(tag) |
| | 402 | elif idx > closing_pos: |
| | 403 | break |
| | 404 | assert len(to_remove) == 2, "failed %d %d" \ |
| | 405 | % (opening_pos, closing_pos) |
| | 406 | assert to_move |
| | 407 | |
| | 408 | # execute deletes |
| | 409 | for node in to_remove: |
| | 410 | table_node.remove(node) |
| | 411 | |
| | 412 | # execute moves (add a <relatorio:repeat> node around |
| | 413 | # the column definitions nodes) |
| | 414 | o_pos, c_pos = str(opening_pos), str(closing_pos) |
| | 415 | repeat_node = EtreeElement(repeat_tag, |
| | 416 | attrib={ |
| | 417 | "opening": o_pos, |
| | 418 | "closing": c_pos, |
| | 419 | "table": table_name}, |
| | 420 | nsmap=self.namespaces) |
| | 421 | |
| | 422 | for node in to_move[1:]: |
| | 423 | table_node.remove(node) |
| | 424 | repeat_node.append(node) |
| | 425 | table_node.replace(to_move[0], repeat_node) |
| | 426 | repeat_node.append(to_move[0]) |
| 394 | | generate_all = super(Template, self).generate(*args, **kwargs) |
| 395 | | |
| 396 | | return RelatorioStream(generate_all, serializer) |
| | 488 | |
| | 489 | counter = ColumnCounter() |
| | 490 | kwargs['__reset_col_count'] = counter.reset |
| | 491 | kwargs['__inc_col_count'] = counter.inc |
| | 492 | kwargs['__store_col_count'] = counter.store |
| | 493 | |
| | 494 | stream = super(Template, self).generate(*args, **kwargs) |
| | 495 | if self.has_col_loop: |
| | 496 | transformation = DuplicateColumnHeaders(counter) |
| | 497 | col_filter = Transformer('//repeat[namespace-uri()="%s"]' |
| | 498 | % RELATORIO_URI) |
| | 499 | col_filter = col_filter.apply(transformation) |
| | 500 | stream = Stream(list(stream), self.serializer) | col_filter |
| | 501 | return RelatorioStream(stream, serializer) |
| | 502 | |
| | 503 | |
| | 504 | class DuplicateColumnHeaders(object): |
| | 505 | def __init__(self, counter): |
| | 506 | self.counter = counter |
| | 507 | |
| | 508 | def __call__(self, stream): |
| | 509 | for mark, (kind, data, pos) in stream: |
| | 510 | # for each repeat tag found |
| | 511 | if mark is ENTER: |
| | 512 | # get the number of columns for that table |
| | 513 | attrs = data[1] |
| | 514 | table = attrs.get('table') |
| | 515 | col_count = self.counter.counters[table] |
| | 516 | |
| | 517 | # collect events (column header tags) to repeat |
| | 518 | events = [] |
| | 519 | for submark, event in stream: |
| | 520 | if submark is EXIT: |
| | 521 | break |
| | 522 | events.append(event) |
| | 523 | |
| | 524 | # repeat them |
| | 525 | for _ in range(col_count): |
| | 526 | for event in events: |
| | 527 | yield None, event |
| | 528 | else: |
| | 529 | yield mark, (kind, data, pos) |