使用Python和libxml2根据标签属性匹配XML中的兄弟节点
我刚开始学习编程,可能在基础知识上还有些欠缺。
我有一个xml文件:
<mother>
<daughter nr='1' state='nice' name='Ada'>
<daughter nr='2' state='naughty' name='Beta'>
<daughter nr='3' state='nice' name='Cecilia'>
<daughter nr='4' state='neither' name='Dora'>
<daughter nr='5' state='naughty' name='Elis'>
</mother>
我需要做的是根据数字匹配“乖女儿”和“调皮女儿”(乖女儿和她最近的调皮女儿),然后打印出这些配对:
Ada Beta
Cecilia Elis
这是我的代码:
import libxml2, sys
doc = libxml2.parseFile("file.xml")
tree = doc.xpathNewContext()
nice = tree.xpathEval("//daugter[@state='nice']")
for l in nice:
print l.prop("name")
nice_nr = []
for n in nice:
nice_nr.append(n.prop("nr"))
# and the same for the naugty daugters
doc.freeDoc()
我能获取到它们属性的值,但我不知道怎么把它们配对起来。
我找到了一种叫做'following-sibling'的XPath轴,但从我看到的所有例子来看,我不确定它是否适合这里。它的语法有点不同,而且会获取到所有后面的兄弟元素。希望能得到一些帮助。
3 个回答
我有一个不使用xpath的解决方案。这个方案还考虑了女儿节点的顺序。整个文档只遍历了一次。
from lxml.etree import fromstring
data = """the-xml-above"""
def fetch_sorted_daughters(data):
# load data into xml document
doc = fromstring(data)
nice = []
naughty = []
# extract into doubles - number, name
for subelement in doc:
if subelement.tag=='daughter':
nr = subelement.get('nr')
name = subelement.get('name')
if subelement.get('state')=='nice':
nice.append((nr, name))
if subelement.get('state')=='naughty':
naughty.append((nr, name))
del doc # release document
# sort doubles
nice.sort(key=lambda x:x[0])
naughty.sort(key=lambda x:x[0])
# get sorted names from doubles
nice = tuple([double[1] for double in nice])
naughty = tuple([double[1] for double in naughty])
return nice, naughty
nice, naughty = fetch_sorted_daughters(data)
pairs = zip(nice, naughty)
print pairs
每个XPath表达式都会返回一个有序的节点列表。只需将这些列表合并在一起,就能找到对应的配对:
xpath = lambda state: tree.xpathEval("//daughter[@state='%s']" % state)
for nodes in zip(xpath('nice'), xpath('naughty')):
print ' '.join(n.prop('name') for n in nodes)
上面提到的xpath
是一个函数,它会计算XPath表达式,返回与给定state
匹配的子节点。然后,这两个列表会传递给zip
,这个函数会返回每个列表中第i个元素的配对。
如果XML文件中的子节点顺序不对,你可以在传递给zip
之前,先根据nr
属性对这些节点进行排序。
使用方法:
/*/daughter[@state = 'nice'][1]
|
/*/daughter[@state = 'nice'][1]
/following-sibling::daughter[@state='naughty'] [1]
这个代码选择了第一个“乖女儿”和她最近的“调皮女儿”这一对。
如果想选择第二对,可以使用:
/*/daughter[@state = 'nice'][2]
|
/*/daughter[@state = 'nice'][2]
/following-sibling::daughter[@state='naughty'] [1]
...等等。
请注意,这些表达式并不能保证一定会选中一个节点——可能根本没有 daughter
元素,或者并不是每个乖 daughter
元素后面都有一个调皮的 daughter
元素。
如果可以保证在文档中,daughter
元素的顺序是严格按照('nice'
, 'naughty'
)排列的,那么可以使用一个非常简单的 XPath 表达式来获取所有的对:
/*/daughter[@state = 'nice' or @state = 'naughty']
这个表达式会选择所有是顶层元素的子元素的 daughter
元素,并且它们的状态属性是交替的,值为:nice, naughty, nice, naughty,
...
如果使用的 XPath API 返回的是一个对象数组,那么对于每个偶数 k
,这对女儿就分别在这个数组的第 k 个和第 (k+1) 个位置。