使用Python和libxml2根据标签属性匹配XML中的兄弟节点

4 投票
3 回答
2092 浏览
提问于 2025-04-16 13:35


我刚开始学习编程,可能在基础知识上还有些欠缺。

我有一个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 个回答

0

我有一个不使用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
0

每个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属性对这些节点进行排序。

3

使用方法:

 /*/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) 个位置。

撰写回答