2017-04-13 135 views
0

我在同一水平上有兩種節點(這裏foobar)的XML文件的工作,像這樣:高效的方法來搜索包含節點的xml節點?

<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代碼。

+0

什麼是ET?你能否顯示進口? –

+0

ET for'xml.etree.ElementTree' – roipoussiere

+0

我們在談論多少個節點?幾百或幾十萬? – Tomalak

回答

1

所以,如果我理解正確的話,每個ID都會有至少一個foo,以及一個或多個bar秒。

因此,要組織這樣的一個有效的方法是一本字典,如:

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

(或者你可以有富的關鍵)。

顯然你必須尋找所有的foos來獲得第一個元素。從那裏,您可以爲bar[foo='{id}']的XPath表達式執行findall搜索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 

這避免了迭代,每欄元素的每個元素富,爲富每一個元素,你只在必要的酒吧元素進行迭代。


或者,您可以迭代一次,並在找到元素時構建字典。對於較小的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