不使用递归搜索解析XML的Python方法

3 投票
4 回答
3163 浏览
提问于 2025-04-16 11:17

这让我快疯了,我可能已经纠结太久了,所以希望能得到一些帮助,帮我恢复理智!这里的食物相关的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 个回答

1

你可以选择递归地遍历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))
2

这里有一个使用 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']
4

可以使用 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 也可以做到类似的事情,虽然这样做有点傻;)

撰写回答