测试 xml.etree.ElementTree 的等效性

36

我对比两个xml元素的等价性很感兴趣;我发现测试元素的tostring方法可以实现这一点;但是,这似乎有些hacky。

有没有更好的方法来测试两个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

下面可以找到一个比较两个元素的函数,详见Itamar的回答 - One
6个回答

41

这个比较函数对我有效:

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))

6
这是一种解决方案。确保空格不会干扰,比如使用etree.XMLParser(remove_blank_text=True)。通过避免在all()中构建列表来进行改进。请注意,由于在测试len()之后,所以zip()有效。 - One
2
太棒了!这似乎可以在元素顺序不同的情况下工作,即使是具有相同标记名称的元素也是如此。 - Fredrik
1
无论元素顺序如何,都无法正常运行。对于具有不同顺序的子元素的相同元素,zip将匹配可能不同的元素,导致错误比较。 - Shillington
1
@Shillington 如果元素顺序不同,您希望比较返回“False”,对吗?属性顺序是另一回事。 - lenz
1
同意 - 我回复的评论暗示它会这样做。然而,有些应用程序可能不关心顺序。 - Shillington

10

比较字符串并不总是有效的。对于判断两个节点是否相等,属性的顺序不应该有影响。但是,如果你进行字符串比较,顺序显然是很重要的。

我不确定这是一个问题还是一个特性,但我的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使用有序字典或类似的东西,而标准的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

另一个问题可能是在比较时什么被认为是不重要的。例如,某些片段可能包含额外的空格,我们不想关心它们。因此,编写一些序列化函数以确切地满足我们的需求总是更好的。


3
.dump(...) 返回 None,因此 ET.dump(h1) == ET.dump(h2) 实际上是在比较 NoneNone - Ivan Kozik
关于属性顺序:特性,请阅读常见问题解答。如何对属性进行排序?https://lxml.de/FAQ.html#how-can-i-sort-the-attributes - Markus Dutschke

4
序列化和反序列化在处理XML时不起作用,因为属性不依赖于顺序(以及其他原因)。例如,这两个元素在逻辑上是相同的,但字符串不同:
<THING a="foo" b="bar"></THING>
<THING b="bar" a="foo"  />

如何进行元素比较是很棘手的。据我所知,Element Tree 没有内置任何东西来为您完成此操作。我需要自己完成这个任务,并使用了下面的代码。它适用于我的需求,但不适用于大型 XML 结构,也不快速或高效!这是一个排序函数而不是相等函数,因此结果为 0 表示相等,其他任何结果表示不相等。将其包装在返回 True 或 False 的函数中留作读者的练习!

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

比较两个XML文件出现问题的主要原因是格式不同,就像他上面说的那样。而且,大多数情况下问题出在尾部区域的空格或换行符上。我有两个逻辑上相同的XML测试文件,但代码没有发现它们是相同的。但是,我只是从代码中删除了.tail比较,它就像魔法般地工作了! - PMN

3
相信或不相信,如果你不知道每个节点有多少子节点,并且想在搜索中包括所有孩子,那么这实际上是处理比较两个节点的最佳方法。
当然,如果你只有一个没有子节点的节点(如你所示),你可以简单地比较标签、属性和尾属性。
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相比有任何重大优势,然而。

你也可以根据需要添加文本:h1.text == h2.text - bmaupin

2
通常比较复杂结构的方法是将它们转换为唯一的文本表示形式,然后比较生成的字符串是否相等。
要比较两个接收到的JSON字符串,您需要将它们转换为JSON对象,然后使用相同的转换器将它们再次转换为字符串并进行比较。我用它来检查JSON源,效果很好。
对于XML,几乎相同,但您可能需要处理(剥离?删除?)“.text”部分(在标签外找到的文本,无论是否为空白)。
因此,简而言之,只要确保根据您的上下文两个等价的XML具有相同的字符串表示形式,您的解决方案就不是黑客行为。

-1

不要金鍍。你現在有的已經很好了。最後 XML 是文本。


是的,如果您担心格式问题,可以先转换为ET,然后将其转储为字符串并进行比较。 - Wyrmwood

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接