Итерация нескольких (родительских, дочерних) узлов с помощью Python ElementTree - PullRequest
4 голосов
/ 18 января 2011

Стандартная реализация ElementTree для Python (2.6) не предоставляет указателей родителям из дочерних узлов.Поэтому, если нужны родители, рекомендуется перебирать родителей, а не детей.

Учтите, что мой xml имеет форму:

<Content>
  <Para>first</Para>
  <Table><Para>second</Para></Table>
  <Para>third</Para>
</Content>

Следующее находит все узлы "Para" безс учетом родителей:

(1) paras = [p for p in page.getiterator("Para")]

Это (адаптировано из effbot) сохраняет родителя путем циклического обхода их вместо дочерних узлов:

(2) paras = [(c,p) for p in page.getiterator() for c in p]

Это имеет смысл и может быть расширено с помощьюусловие для достижения (предположительно) того же результата, что и (1), но с добавленной родительской информацией:

(3) paras = [(c,p) for p in page.getiterator() for c in p if c.tag == "Para"]

Документация ElementTree предполагает, что метод getiterator () выполняетПервый поиск.Запуск его без поиска родителя (1) приводит к:

first
second
third

Однако, извлекая текст из параграфов (3), получается:

first, Content>Para
third, Content>Para
second, Table>Para

Это выглядит как ширинаfirst.

Поэтому возникает два вопроса.

  1. Это правильное и ожидаемое поведение?
  2. Как вы извлекаете (родитель, ребенок) кортежи, когда ребенок долженбыть определенного типа, но родитель может быть любым, , если порядок документа должен поддерживаться .Я не думаю, что выполнение двух циклов и отображение (родительского, дочернего), генерируемого (3), в порядки, генерируемые (1), является идеальным.

1 Ответ

5 голосов
/ 18 января 2011

Учтите это:

>>> xml = """<Content>
...   <Para>first</Para>
...   <Table><Para>second</Para></Table>
...   <Para>third</Para>
... </Content>"""
>>> import xml.etree.cElementTree as et
>>> page = et.fromstring(xml)
>>> for p in page.getiterator():
...     print "ppp", p.tag, repr(p.text)
...     for c in p:
...         print "ccc", c.tag, repr(c.text), p.tag
...
ppp Content '\n  '
ccc Para 'first' Content
ccc Table None Content
ccc Para 'third' Content
ppp Para 'first'
ppp Table None
ccc Para 'second' Table
ppp Para 'second'
ppp Para 'third'
>>> 

В сторону: списочные представления великолепны до тех пор, пока вы не захотите увидеть, что именно повторяется: -)

getiterator - это , создающий элементы "ppp" в объявленном заказе. Однако вы отбираете интересующие вас элементы из вспомогательных элементов "ccc", которые находятся не в нужном вам порядке.

Одним из решений является создание собственной итерации:

>>> def process(elem, parent):
...    print elem.tag, repr(elem.text), parent.tag if parent is not None else None
...    for child in elem:
...       process(child, elem)
...
>>> process(page, None)
Content '\n  ' None
Para 'first' Content
Table None Content
Para 'second' Table
Para 'third' Content
>>>

Теперь вы можете перехватывать элементы "Para", каждый со ссылкой на своего родителя (если есть), когда они проходят мимо.

Это можно красиво обернуть в гаджет генератора:

>>> def iterate_with_parent(elem):
...     stack = []
...     while 1:
...         for child in reversed(elem):
...             stack.append((child, elem))
...         if not stack: return
...         elem, parent = stack.pop()
...         yield elem, parent
...
>>>
>>> showtag = lambda e: e.tag if e is not None else None
>>> showtext = lambda e: repr((e.text or '').rstrip())
>>> for e, p in iterate_with_parent(page):
...     print e.tag, showtext(e), showtag(p)
...
Para 'first' Content
Table '' Content
Para 'second' Table
Para 'third' Content
>>>
...