使用Python ElementTree迭代多个(父,子)节点
Python的ElementTree标准实现(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)中提取“para”的文本,结果是:
first, Content>Para
third, Content>Para
second, Table>Para
这看起来像是广度优先搜索。
因此,这引出了两个问题。
- 这是正确且预期的行为吗?
- 当子节点必须是某种特定类型,而父节点可以是任何类型时,如何提取(父,子)元组,如果必须保持文档的顺序。我认为运行两个循环,并将(3)生成的(父,子)映射到(1)生成的顺序并不是理想的做法。
1 个回答
5
考虑一下这个:
>>> 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
>>>