【问题标题】:Testing Equivalence of xml.etree.ElementTree测试 xml.etree.ElementTree 的等价性
【发布时间】:2011-12-15 20:44:06
【问题描述】:

我对两个 xml 元素的等价性感兴趣;我发现测试元素的 tostring 是有效的;但是,这似乎很老套。

有没有更好的方法来测试两个 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's answer找到。

标签: python python-3.x elementtree


【解决方案1】:

这个比较功能对我有用:

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

【讨论】:

  • 这是一个解决方案。确保空格不会干扰,例如通过使用etree.XMLParser(remove_blank_text=True)。通过避免在all() 中构建列表来改进。请注意,zip() 有效,因为 len() 之前已经过测试。
  • 整洁!无论元素顺序如何,这似乎都有效,即使对于具有相同标记名的元素也是如此。
  • 无论元素顺序如何,这都不起作用。对于具有不同顺序的子元素的相同元素,zip 将匹配可能不同的元素,从而导致 False 比较。
  • @Shillington 如果元素顺序不同,您希望比较返回False,不是吗?属性顺序是另一回事。
  • 同意 - 我回复的评论暗示它会。但是,有些应用程序不关心顺序。
【解决方案2】:

序列化和反序列化不适用于 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 文件出现问题的主要原因是他上面所说的格式不同。而且,大多数情况下,pronlem 位于尾部的空格或换行符中。我有两个逻辑上相同的 XML 文件进行测试,但代码没有发现它们是相同的。但是,我刚刚从代码中删除了 .tail 比较,它就像一个魅力!
【解决方案3】:

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

我不确定这是问题还是功能,但我的 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

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

【讨论】:

  • .dump(...) 返回None,所以ET.dump(h1) == ET.dump(h2) 实际上是将NoneNone 进行比较。
【解决方案4】:

比较复杂结构的常用方法是将它们转储为常见的唯一文本表示,然后比较结果字符串是否相等。

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

对于 XML,它几乎相同,但您可能必须处理(剥离?删除?)“.text”部分(文本,无论是否空白,可能在标签之外找到)。

因此,简而言之,只要您确保两个等效的 XML(根据您的上下文)将具有相同的字符串表示,您的解决方案就不是一个 hack。

【讨论】:

    【解决方案5】:

    信不信由你,如果您不知道每个节点可能有多少个子节点并且您希望在搜索中包含所有子节点,那么这实际上是处理比较两个节点的最佳方法。

    当然,如果你只是像你演示的那样有一个无子节点,你可以简单地比较 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 相比没有任何重大好处。

    【讨论】:

    • 您也可以根据自己的需要扔文字:h1.text == h2.text
    【解决方案6】:

    不要镀金。你有一个很好的比较。在 XML 末尾是 TEXT。

    【讨论】:

    • 是的,如果您担心格式,请转换为 ET,然后转储为字符串并进行比较。
    猜你喜欢
    • 2019-06-11
    • 1970-01-01
    • 2021-01-03
    • 2021-04-10
    • 2013-10-03
    • 1970-01-01
    • 2017-03-29
    • 1970-01-01
    • 2011-09-03
    相关资源
    最近更新 更多