测试xml.etree.ElementTree的等效性

时间:2011-10-26 15:57:07

标签: python python-3.x elementtree

我对两个xml元素的等价感兴趣;我发现测试元素的串串是有效的;然而,这看起来很糟糕。

有没有更好的方法来测试两个etree元素的等价性?

直接比较元素:

import xml.etree.ElementTree as etree
h1 = etree.Element('hat',{'color':'red'})
h2 = etree.Element('hat',{'color':'red'})

h1 == h2  # False

将元素比较为字符串:

etree.tostring(h1) == etree.tostring(h2)  # True

6 个答案:

答案 0 :(得分:25)

这个比较功能对我有用:

def elements_equal(e1, e2):
    if e1.tag != e2.tag: return False
    if e1.text != e2.text: return False
    if e1.tail != e2.tail: return False
    if e1.attrib != e2.attrib: return False
    if len(e1) != len(e2): return False
    return all(elements_equal(c1, c2) for c1, c2 in zip(e1, e2))

答案 1 :(得分:7)

比较字符串并不总是有效。考虑两个节点等效,属性的顺序无关紧要。但是,如果你进行字符串比较,那么顺序显然很重要。

我不确定这是一个问题还是一个功能,但是我的lxml.etree版本会保留属性的顺序,如果它们是从文件或字符串中解析出来的那样:

>>> from lxml import etree
>>> h1 = etree.XML('<hat color="blue" price="39.90"/>')
>>> h2 = etree.XML('<hat price="39.90" color="blue"/>')
>>> etree.tostring(h1) == etree.tostring(h2)
False

这可能是版本相关的(我在Ubuntu上使用Python 2.7.3和lxml.etree 2.3.2);我记得当我想要(出于可读性原因)时,我找不到一种方法来控制一年前左右属性的顺序。

由于我需要比较不同序列化程序生成的XML文件,除了递归比较每个节点的标记,文本,属性和子项之外,我没有别的办法。当然还有尾巴,如果那里有什么有趣的话。

lxml和xml.etree.ElementTree的比较

事实是,它可能依赖于实现。显然,lxml使用有序的dict或类似的东西,标准的xml.etree.ElementTree不保留属性的顺序:

Python 2.7.1 (r271:86832, Nov 27 2010, 17:19:03) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from lxml import etree
>>> h1 = etree.XML('<hat color="blue" price="39.90"/>')
>>> h2 = etree.XML('<hat price="39.90" color="blue"/>')
>>> etree.tostring(h1) == etree.tostring(h2)
False
>>> etree.tostring(h1)
'<hat color="blue" price="39.90"/>'
>>> etree.tostring(h2)
'<hat price="39.90" color="blue"/>'
>>> etree.dump(h1)
<hat color="blue" price="39.90"/>>>> etree.dump(h2)
<hat price="39.90" color="blue"/>>>>

(是的,缺少新行。但这是一个小问题。)

>>> import xml.etree.ElementTree as ET
>>> h1 = ET.XML('<hat color="blue" price="39.90"/>')
>>> h1
<Element 'hat' at 0x2858978>
>>> h2 = ET.XML('<hat price="39.90" color="blue"/>')
>>> ET.dump(h1)
<hat color="blue" price="39.90" />
>>> ET.dump(h2)
<hat color="blue" price="39.90" />
>>> ET.tostring(h1) == ET.tostring(h2)
True
>>> ET.dump(h1) == ET.dump(h2)
<hat color="blue" price="39.90" />
<hat color="blue" price="39.90" />
True

另一个问题可能是比较不重要的问题。例如,某些片段可能包含额外的空格,我们不想关心。这样,编写一些完全符合我们需要的序列化函数总是更好。

答案 2 :(得分:4)

序列化和反序列化不适用于XML,因为属性不依赖于顺序(和其他原因)例如。这两个元素在逻辑上是相同的,但不同的字符串:

<THING a="foo" b="bar"></THING>
<THING b="bar" a="foo"  />

究竟如何进行元素比较很棘手。据我所知,Element Tree中没有任何东西可以为你做这件事。我需要自己做,并使用下面的代码。它适用于我的需求,但它不适合大型XML结构,并且不快速或高效!这是一个排序函数而不是相等函数,因此0的结果是相等的,而其他任何结果都不是。用真或假返回函数包装它留给读者练习!

def cmp_el(a,b):
    if a.tag < b.tag:
        return -1
    elif a.tag > b.tag:
        return 1
    elif a.tail < b.tail:
        return -1
    elif a.tail > b.tail:
        return 1

    #compare attributes
    aitems = a.attrib.items()
    aitems.sort()
    bitems = b.attrib.items()
    bitems.sort()
    if aitems < bitems:
        return -1
    elif aitems > bitems:
        return 1

    #compare child nodes
    achildren = list(a)
    achildren.sort(cmp=cmp_el)
    bchildren = list(b)
    bchildren.sort(cmp=cmp_el)

    for achild, bchild in zip(achildren, bchildren):
        cmpval = cmp_el(achild, bchild)
        if  cmpval < 0:
            return -1
        elif cmpval > 0:
            return 1    

    #must be equal 
    return 0

答案 3 :(得分:3)

不管你信不信,这实际上是处理比较两个节点的最好方法,如果你不知道每个节点有多少个孩子,你想在搜索中包括所有孩子。

当然,如果你只是像你正在演示的那样有一个无子节点,你可以简单地比较tag,attrib和tail属性:

if h1.tag == h2.tag and h1.attrib == h2.attrib and h1.tail == h2.tail:
    print("h1 and h2 are the same")
else
    print("h1 and h2 are the different")

但是,我认为使用tostring没有任何重大好处。

答案 4 :(得分:2)

比较复杂结构的常用方法是将它们转储到一个共同的唯一文本表示中,并比较结果字符串的相等性。

要比较两个收到的json字符串,您可以将它们转换为json对象,然后将它们转换回字符串(使用相同的转换器)并进行比较。我这样做是为了检查json feed,它运行良好。

对于XML,它几乎是相同的,但你可能必须处理(strip?remove?)“。text”部分(文本,空白与否,可能在标签外找到)。

简而言之,只要您确保两个等效的XML(根据您的上下文)具有相同的字符串表示形式,您的解决方案就不是黑客攻击。

答案 5 :(得分:0)

不要金盘。你拥有的是一个很好的比较。最后,XML是TEXT。