搜索包含节点的xml节点的有效方法?

时间:2017-04-13 18:12:37

标签: python xml xpath

我正在处理一个XML文件,其中包含两种类型的节点(此处为foobar),如下所示:

<foo>
    <id>123</id>
    <name>The first foo</name>
</foo>
<foo>
    <id>456</id>
    <name>The second foo</name>
</foo>
<bar>
    <name>The first bar</name>
    <foo>123</foo>
</bar>
<bar>
    <name>The second bar</name>
    <foo>123</foo>
</bar>

请注意:

  • 每个foo 一个 bar;
  • 每个bar都有一个或多个 foo

我想为每个bar得到相应的foo节点,所以我写了这段代码:

import xml.etree.ElementTree as ET
root = ET.fromstring(data)
for bar in root.findall('bar'):
    for foo in root.findall('foo'):
        if foo.find('id').text == bar.find('foo').text:
            foo_of_bar = foo
            pass
    print bar.find('name').text + ': ' + foo_of_bar.find('name').text

结果:

The first bar: The first foo
The second bar: The first foo

但我问自己是否有更好的方法,使用XPath语法或更pythonic代码。

1 个答案:

答案 0 :(得分:1)

因此,如果我理解正确,每个ID都至少有一个foo,以及一个或多个bar

因此,组织此方法的有效方法是使用以下字典:

{
    id: (foo, (bar, bar, ...)),
    id: (foo, (bar, bar, ...)),
    ...
}

(或者你可以将foo作为密钥)。

显然你必须寻找所有的foos来获得第一个元素。从那里,您可以对bar[foo='{id}']的XPath表达式进行查找,该表达式搜索具有bar子项的foo元素,这些子项具有引号之间的完整内容。所以:

root = ET.fromstring(data)
foo_bars = {}
for foo in root.findall('foo'):
    foo_id = foo.find('id').text
    bars = tuple(root.findall("bar[foo='{}']".format(foo_id)))
    foo_bars[foo_id] = (foo, bars)
    # Do something with foo (one element),
    # bars (a tuple of elements) and
    # foo_id (A str of the id)

for foo_id, f_bs in foo_bars.items():
    foo, bars = f_bs
    # Also do something

这避免了迭代每个bar元素的每个foo元素,就像每个foo元素一样,你只迭代必要的bar元素。

或者,您可以迭代一次并在找到元素时构建字典。对于较小的xml文件,这会慢得多,但如果你有一个较大的文件,它可能会更快。

import xml.etree.ElementTree as ET
import collections

root = ET.fromstring(data)
foo_bars = collections.defaultdict(lambda: [None, []])
for child in root:
    if child.tag == 'foo':
        # Found a new id
        foo_bars[child.find('id').text][0] = child
    elif child.tag == 'bar':
        foo_bars[child.find('foo').text][1].append(child)
    else:
        # Possibly raise a ValueError?
        pass
for foo_id, f_bs in foo_bars.items():
    foo, bars = f_bs
    # Do something