不使用递归搜索解析XML的Python方法
这让我快疯了,我可能已经纠结太久了,所以希望能得到一些帮助,帮我恢复理智!这里的食物相关的xml只是我想实现的一个例子。
我有一个文件,想把它转成一个图形结构,像是小麦和水果是父节点,深度为0。印度是小麦的子节点,深度为1,依此类推。
每一层都有一些关键词。所以我想要得到的是:
layer, depth, parent, keywords
wheat, 1, ROOT, [bread, pita, narn, loaf]
indian, 2, wheat [chapati]
mumbai, 3, indian, puri
fruit, 1,ROOT, [apple, orange, pear, lemon]
这是一个示例文件 -
<keywords>
<layer id="wheat">
<layer id="indian">
<keyword>chapati</keyword>
<layer id="mumbai">
<keyword>puri</keyword>
</layer>
</layer>
<keyword>bread</keyword>
<keyword>pita</keyword>
<keyword>narn</keyword>
<keyword>loaf</keyword>
</layer>
<layer id="fruit">
<keyword>apple</keyword>
<keyword>orange</keyword>
<keyword>pear</keyword>
<keyword>lemon</keyword>
</layer>
</keywords>
所以这不是一个图形问题,我能处理那部分,挺简单的。让我困扰的是如何解析这个XML。
如果我执行一个操作:
xmldoc = minidom.parse(self.filename)
layers = xmldoc.getElementsByTagName('layer')
只返回所有层的元素,这太多了,而且我理解的深度/层级概念并没有体现,因为它是递归搜索。
下面的帖子不错,但没有提供我需要的概念。使用Python和minidom解析XML。有没有人能帮我一下,我该怎么做?我可以发我的代码,但它实在是拼凑得太乱/根本就有问题,我觉得对任何人都没用!
谢谢
戴夫
4 个回答
你可以选择递归地遍历DOM树(可以参考kelloti的回答),或者从找到的节点中获取信息:
xmldoc = minidom.parse(filename)
layers = xmldoc.getElementsByTagName("layer")
def _getText(node):
rc = []
for n in node.childNodes:
if n.nodeType == n.TEXT_NODE:
rc.append(n.data)
return ''.join(rc)
def _depth(n):
res = -1
while isinstance(n, minidom.Element):
n = n.parentNode
res += 1
return res
for l in layers:
keywords = [_getText(k) for k in l.childNodes
if k.nodeType == k.ELEMENT_NODE and k.tagName == 'keyword']
print("%s %s %s" % (l.getAttribute("id"), _depth(l), keywords))
这里有一个使用 ElementTree 的解决方案:
from xml.etree import ElementTree as ET
from io import StringIO
from collections import defaultdict
data = '''\
<keywords>
<layer id="wheat">
<layer id="indian">
<keyword>chapati</keyword>
<layer id="mumbai">
<keyword>puri</keyword>
</layer>
</layer>
<keyword>bread</keyword>
<keyword>pita</keyword>
<keyword>narn</keyword>
<keyword>loaf</keyword>
</layer>
<layer id="fruit">
<keyword>apple</keyword>
<keyword>orange</keyword>
<keyword>pear</keyword>
<keyword>lemon</keyword>
</layer>
</keywords>
'''
path = ['ROOT'] # stack for layer names
items = defaultdict(list) # key=layer, value=list of items @ layer
f = StringIO(data)
for evt,e in ET.iterparse(f,('start','end')):
if evt == 'start':
if e.tag == 'layer':
path.append(e.attrib['id']) # new layer added to path
elif e.tag == 'keyword':
items[path[-1]].append(e.text) # add item to last layer in path
elif evt == 'end':
if e.tag == 'layer':
layer = path.pop()
parent = path[-1]
print layer,len(path),parent,items[layer]
输出结果
mumbai 3 indian ['puri']
indian 2 wheat ['chapati']
wheat 1 ROOT ['bread', 'pita', 'narn', 'loaf']
fruit 1 ROOT ['apple', 'orange', 'pear', 'lemon']
可以使用 lxml 库,特别是 XPath 这个功能。你可以通过 "//layer"
来获取所有的 layer
元素,不管它们在文档中的层级有多深。而如果你想获取某个特定 id 的 layer
,可以用 "//layer[id='{}'][0]".format(id)
这个方式。要获取某个元素下直接包含的 keyword
元素,可以用 ".../keyword"
(这里的 ...
是一个查询,用来找到需要搜索的节点)。
获取某个节点的深度就没那么简单,但也不算难。我没有找到现成的函数(据我所知,这个功能不在 XPath 的范围内——虽然你可以在查询中检查深度,但返回的只是元素本身,也就是说你可以返回特定深度的节点,但不能直接得到深度),所以我写了一个简单的函数(没有使用递归,因为这不是必要的——不过一般来说,处理 XML 时常常需要用到递归,不管你喜不喜欢!):
def depth(node):
depth = 0
while node.getparent() is not None:
node = node.getParent()
depth += 1
return depth
如果你不使用最好的 Python XML 库,使用 DOM 也可以做到类似的事情,虽然这样做有点傻;)