如何使用Python检查两个XML文件是否等价?

5
如何检查两个XML文件是否相等?
例如,即使排序不同,这两个XML文件也是相同的。我需要检查这两个XML文件是否包含相同的文本信息,而忽略其顺序。
<a>
   <b>hello</b>
   <c><d>world</d></c>
</a>

<a>
   <c><d>world</d></c>
   <b>hello</b>
</a>

有没有相关的工具可用于此?

2
实际上,它们并不相同,因为XML通常还包含元素的顺序。因此,如果您想将其定义为“相同”,您可能需要编写自己的比较函数。 - poke
这些文件可能在语义上是等价的,也可能不是。您确定在您的情况下顺序不重要吗?在许多XML文件中,顺序很重要。 - Jon Skeet
1
@poke和@Jon:感谢您的评论,我已将标题从“相同”更改为“等效”。 - prosseek
3个回答

11

一切都取决于您对“等效”的定义。

假设您只关心文本节点(例如:您的示例中的d标签甚至不重要,您只关心内容word),则可以创建每个文档的文本节点集并进行比较。使用lxml,代码如下:

from lxml import etree

tree1 = etree.parse('example1.xml')
tree2 = etree.parse('example2.xml')

print set(tree1.getroot().itertext()) == set(tree2.getroot().itertext())

你甚至可能想忽略空格节点,可以采取以下方式: ```python 你的代码示例 ``` ```javascript 你的代码示例 ```
set(i for i in tree.getroot().itertext() if i.strip())

请注意,使用集合意味着您将不考虑文档中某些文本出现的次数(这可能是您想要的,也可能不是)。如果顺序不重要,但出现次数重要,则可以使用字典代替集合,并跟踪出现次数(例如,在Python 2.7中使用collections.defaultdict()collections.Counter)。
但是,如果只有根元素的直接子元素的顺序(在您的情况下,a元素的子元素)可以被忽略,并且这些元素内部的所有内容都很重要,则需要另一种方法。例如,您可以对每个子元素进行XML规范化,以获取每个子元素的规范化版本(同样,我不知道这是否足够规范化满足您的需求)。
from lxml import etree

tree1 = etree.parse('example1.xml')
tree2 = etree.parse('example2.xml')

set1 = set(etree.tostring(i, method='c14n') for i in tree1.getroot())
set2 = set(etree.tostring(i, method='c14n') for i in tree2.getroot())

print set1 == set2

注意:为了使示例更简单,我使用了lxml的开发版本,在旧版本中,etree.tostring()没有method='c14n',只有一个在ElementTree上写入类似文件对象的c14n()方法。因此,在那里让它工作,您必须将每个元素复制到自己的树中,并使用StringIO()对象作为虚拟文件。同时,这种做法可能不适用于非常大的文件。
但是请注意:强烈警告 - 您必须确切知道您所需要的“等效”,并基于该知识创建自己的解决方案!

1

在XML中,顺序很重要,因此您提供的这两个文件是不同的。通常,您可以将XML规范化,然后将文件作为文本进行比较,但如果您想进行无序比较,则可能需要使用其中之一的bazillion XML解析器自己实现它(顺便说一下,我推荐使用lxml)。


1
如何规范化XML文件?谢谢。 - prosseek
规范化的想法是在XML中删除不重要的空格。有很多外部工具可以做到这一点,可能也有库支持。谷歌一下就知道了。 - Gintautas Miliauskas
3
在你的情况下,你可能还想按字母顺序重新排列<b>元素(或其他一些一致的排序方式),因为你想忽略它们的顺序。 - Lie Ryan

0

我的解决方案如下。比较所有属性,标签迭代。 一些代码参考自:Testing Equivalence of xml.etree.ElementTree

import xml.etree.ElementTree as ET


def elements_equal(e1, e2):
    if e1.tag != e2.tag: 
        return False
    if e1.text != e2.text: 
        if  e1.text!=None and e2.text!=None :
            return False
    if e1.tail != e2.tail:
        if e1.tail!=None and e2.tail!=None:
            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))



def is_two_xml_equal(f1, f2):
    tree1 = ET.parse(f1)
    root1 = tree1.getroot()
    tree2 = ET.parse(f2)
    root2 = tree2.getroot()
    return elements_equal(root1,root3)

f1 = '2.xml'
f2 = '1.xml'
print(is_two_xml_equal(f1, f2))

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