Python 比较XML片段?

Python 比较XML片段?,python,xml,diff,Python,Xml,Diff,在此基础上,如何检查两个格式良好的XML片段在语义上是否相等。我所需要的只是“相等”或“不相等”,因为我正在使用它进行单元测试 在我想要的系统中,这些是相等的(注意“开始”的顺序) 和“结束”): #重新排序的开始和结束 我有lmxl和其他工具供我使用,一个只允许属性重新排序的简单函数也可以很好地工作 基于IanB答案的工作片段: from formencode.doctest_xml_compare import xml_compare # have to strip these or

在此基础上,如何检查两个格式良好的XML片段在语义上是否相等。我所需要的只是“相等”或“不相等”,因为我正在使用它进行单元测试

在我想要的系统中,这些是相等的(注意“开始”的顺序) 和“结束”):


#重新排序的开始和结束
我有lmxl和其他工具供我使用,一个只允许属性重新排序的简单函数也可以很好地工作


基于IanB答案的工作片段:

from formencode.doctest_xml_compare import xml_compare
# have to strip these or fromstring carps
xml1 = """    <?xml version='1.0' encoding='utf-8' standalone='yes'?>
    <Stats start="1275955200" end="1276041599"></Stats>"""
xml2 = """     <?xml version='1.0' encoding='utf-8' standalone='yes'?>
    <Stats end="1276041599" start="1275955200"></Stats>"""
xml3 = """ <?xml version='1.0' encoding='utf-8' standalone='yes'?>
    <Stats start="1275955200"></Stats>"""

from lxml import etree
tree1 = etree.fromstring(xml1.strip())
tree2 = etree.fromstring(xml2.strip())
tree3 = etree.fromstring(xml3.strip())

import sys
reporter = lambda x: sys.stdout.write(x + "\n")

assert xml_compare(tree1,tree2,reporter)
assert xml_compare(tree1,tree3,reporter) is False

来自formencode.doctest\u xml\u compare导入xml\u compare
#必须剥去这些或从线鲤鱼身上剥去
xml1=“”
"""
xml2=“”
"""
xml3=“”
"""
从lxml导入etree
tree1=etree.fromstring(xml1.strip())
tree2=etree.fromstring(xml2.strip())
tree3=etree.fromstring(xml3.strip())
导入系统
reporter=lambda x:sys.stdout.write(x+“\n”)
断言xml_比较(tree1、tree2、reporter)
断言xml_比较(tree1、tree3、reporter)为False

如果采用DOM方法,则可以在比较节点(节点类型、文本、属性)时同时遍历这两棵树


递归解决方案将是最优雅的——只要在一对节点不“相等”或在一棵树中检测到一片叶子,而它是另一棵树中的一个分支时,简化进一步的比较,等等。

您可以使用--xml\u compare函数比较两个ElementTree或lxml树。

我遇到了同样的问题:我想要比较的两个文档具有相同的属性,但顺序不同

lxml中的XML规范化(C14N)似乎可以很好地实现这一点,但我绝对不是XML专家。我很想知道是否有人能指出这种方法的缺点

parser = etree.XMLParser(remove_blank_text=True)

xml1 = etree.fromstring(xml_string1, parser)
xml2 = etree.fromstring(xml_string2, parser)

print "xml1 == xml2: " + str(xml1 == xml2)

ppxml1 = etree.tostring(xml1, pretty_print=True)
ppxml2 = etree.tostring(xml2, pretty_print=True)

print "pretty(xml1) == pretty(xml2): " + str(ppxml1 == ppxml2)

xml_string_io1 = StringIO()
xml1.getroottree().write_c14n(xml_string_io1)
cxml1 = xml_string_io1.getvalue()

xml_string_io2 = StringIO()
xml2.getroottree().write_c14n(xml_string_io2)
cxml2 = xml_string_io2.getvalue()

print "canonicalize(xml1) == canonicalize(xml2): " + str(cxml1 == cxml2)
运行此命令可以让我:

$ python test.py 
xml1 == xml2: false
pretty(xml1) == pretty(xml2): false
canonicalize(xml1) == canonicalize(xml2): true

考虑到这个问题,我提出了以下解决方案,使XML元素具有可比性和可排序性:

import xml.etree.ElementTree as ET
def cmpElement(x, y):
    # compare type
    r = cmp(type(x), type(y))
    if r: return r 
    # compare tag
    r = cmp(x.tag, y.tag)
    if r: return r
    # compare tag attributes
    r = cmp(x.attrib, y.attrib)
    if r: return r
    # compare stripped text content
    xtext = (x.text and x.text.strip()) or None
    ytext = (y.text and y.text.strip()) or None
    r = cmp(xtext, ytext)
    if r: return r
    # compare sorted children
    if len(x) or len(y):
        return cmp(sorted(x.getchildren()), sorted(y.getchildren()))
    return 0

ET._ElementInterface.__lt__ = lambda self, other: cmpElement(self, other) == -1
ET._ElementInterface.__gt__ = lambda self, other: cmpElement(self, other) == 1
ET._ElementInterface.__le__ = lambda self, other: cmpElement(self, other) <= 0
ET._ElementInterface.__ge__ = lambda self, other: cmpElement(self, other) >= 0
ET._ElementInterface.__eq__ = lambda self, other: cmpElement(self, other) == 0
ET._ElementInterface.__ne__ = lambda self, other: cmpElement(self, other) != 0
将xml.etree.ElementTree作为ET导入
def Cmplement(x,y):
#比较类型
r=cmp(类型(x),类型(y))
if r:返回r
#比较标记
r=cmp(x标记,y标记)
if r:返回r
#比较标记属性
r=cmp(x.attrib,y.attrib)
if r:返回r
#比较精简的文本内容
xtext=(x.text和x.text.strip())或无
ytext=(y.text和y.text.strip())或无
r=cmp(xtext,ytext)
if r:返回r
#比较已排序的子项
如果len(x)或len(y):
返回cmp(已排序(x.getchildren()),已排序(y.getchildren())
返回0
等元素接口。\uuuu lt\uuuuu=lambda self,其他:cmpElement(self,other)=-1
ET.\U元素接口。\uu gt\uuuu=lambda self,其他:cmpElement(self,other)=1
等元素接口。等元素=lambda self,其他:cmpElement(self,其他)=0
等元素接口。等元素=lambda self,其他:cmpElement(self,other)=0
ET._element接口。u ne_u=lambda self,other:cmpElement(self,other)!=0

元素的顺序在XML中可能很重要,这可能就是为什么建议的大多数其他方法在顺序不同时会比较不相等的原因。。。即使元素具有相同的属性和文本内容

但我也想要一个不区分顺序的比较,所以我想到了这个:

from lxml import etree
import xmltodict  # pip install xmltodict


def normalise_dict(d):
    """
    Recursively convert dict-like object (eg OrderedDict) into plain dict.
    Sorts list values.
    """
    out = {}
    for k, v in dict(d).iteritems():
        if hasattr(v, 'iteritems'):
            out[k] = normalise_dict(v)
        elif isinstance(v, list):
            out[k] = []
            for item in sorted(v):
                if hasattr(item, 'iteritems'):
                    out[k].append(normalise_dict(item))
                else:
                    out[k].append(item)
        else:
            out[k] = v
    return out


def xml_compare(a, b):
    """
    Compares two XML documents (as string or etree)

    Does not care about element order
    """
    if not isinstance(a, basestring):
        a = etree.tostring(a)
    if not isinstance(b, basestring):
        b = etree.tostring(b)
    a = normalise_dict(xmltodict.parse(a))
    b = normalise_dict(xmltodict.parse(b))
    return a == b
适应Python 3(基本上,将
iteritems()
更改为
items()
,将
basestring
更改为
string
):

由于要忽略由于不同属性顺序和确定顺序属性而产生的差异,因此可以使用该方法来测试相等性:

xml1 = b'''    <?xml version='1.0' encoding='utf-8' standalone='yes'?>
    <Stats start="1275955200" end="1276041599"></Stats>'''
xml2 = b'''     <?xml version='1.0' encoding='utf-8' standalone='yes'?>
    <Stats end="1276041599" start="1275955200"></Stats>'''
xml3 = b''' <?xml version='1.0' encoding='utf-8' standalone='yes'?>
    <Stats start="1275955200"></Stats>'''

import lxml.etree

tree1 = lxml.etree.fromstring(xml1.strip())
tree2 = lxml.etree.fromstring(xml2.strip())
tree3 = lxml.etree.fromstring(xml3.strip())

import io

b1 = io.BytesIO()
b2 = io.BytesIO()
b3 = io.BytesIO()

tree1.getroottree().write_c14n(b1)
tree2.getroottree().write_c14n(b2)
tree3.getroottree().write_c14n(b3)

assert b1.getvalue() == b2.getvalue()
assert b1.getvalue() != b3.getvalue()
xml1=b''
'''
xml2=b''
'''
xml3=b''
'''
导入lxml.etree
tree1=lxml.etree.fromstring(xml1.strip())
tree2=lxml.etree.fromstring(xml2.strip())
tree3=lxml.etree.fromstring(xml3.strip())
输入io
b1=io.BytesIO()
b2=io.BytesIO()
b3=io.BytesIO()
tree1.getroottree().write_c14n(b1)
tree2.getroottree().write_c14n(b2)
tree3.getroottree().write_c14n(b3)
断言b1.getvalue()==b2.getvalue()
断言b1.getvalue()!=b3.getvalue()

请注意,此示例假定为Python3。在Python3中,必须使用
b''.'''
字符串和
io.BytesIO
,而在Python2中,此方法也适用于普通字符串和
io.StringIO

这里是一个简单的解决方案,将XML转换为字典(使用xmltodict)并将字典进行比较

导入json
导入xmltodict
类XmlDiff(对象):
定义初始化(self,xml1,xml2):
self.dict1=json.loads(json.dumps((xmltodict.parse(xml1)))
self.dict2=json.loads(json.dumps((xmltodict.parse(xml2)))
def相等(自身):
返回self.dict1==self.dict2
单元测试

导入单元测试
类XMLDiffTestCase(unittest.TestCase):
def test_xml_equal(自):
xml1=“”
"""
xml2=“”
"""
self.assertTrue(XmlDiff(xml1,xml2.equal())
def测试xml不相等(自身):
xml1=“”
"""
xml2=“”
"""
self.assertFalse(XmlDiff(xml1,xml2.equal())
或者使用简单的python方法:

导入json
导入xmltodict
定义xml_等于(a,b):
"""
比较两个XML文档(作为字符串或etree)
不关心元素顺序
"""
返回json.loads(json.dumps((xmltodict.parse(a)))==json.loads(json.dumps((xmltodict.parse(b)))

SimpleTAL使用自定义的xml.sax处理程序来比较xml文档 (比较getXMLChecksum的结果)
但是我更喜欢生成一个列表,而不是md5散列,下面的代码片段呢?可以轻松增强以包括属性:

def separator(self):
    return "!@#$%^&*" # Very ugly separator

def _traverseXML(self, xmlElem, tags, xpaths):
    tags.append(xmlElem.tag)
    for e in xmlElem:
        self._traverseXML(e, tags, xpaths)

    text = ''
    if (xmlElem.text):
        text = xmlElem.text.strip()

    xpaths.add("/".join(tags) + self.separator() + text)
    tags.pop()

def _xmlToSet(self, xml):
    xpaths = set() # output
    tags = list()
    root = ET.fromstring(xml)
    self._traverseXML(root, tags, xpaths)

    return xpaths

def _areXMLsAlike(self, xml1, xml2):
    xpaths1 = self._xmlToSet(xml1)
    xpaths2 = self._xmlToSet(xml2)`enter code here`

    return xpaths1 == xpaths2

这就是解决方案,我只是希望有人已经写了一个。也考虑到了这种方法,我正在寻找缺点,或者这是否真的是比较xml文档的标准方法。。。(双关语)我已经在我运行的一个网站上使用了一年多,该网站比较XML文档以实现版本控制。这是公平的
xml1 = b'''    <?xml version='1.0' encoding='utf-8' standalone='yes'?>
    <Stats start="1275955200" end="1276041599"></Stats>'''
xml2 = b'''     <?xml version='1.0' encoding='utf-8' standalone='yes'?>
    <Stats end="1276041599" start="1275955200"></Stats>'''
xml3 = b''' <?xml version='1.0' encoding='utf-8' standalone='yes'?>
    <Stats start="1275955200"></Stats>'''

import lxml.etree

tree1 = lxml.etree.fromstring(xml1.strip())
tree2 = lxml.etree.fromstring(xml2.strip())
tree3 = lxml.etree.fromstring(xml3.strip())

import io

b1 = io.BytesIO()
b2 = io.BytesIO()
b3 = io.BytesIO()

tree1.getroottree().write_c14n(b1)
tree2.getroottree().write_c14n(b2)
tree3.getroottree().write_c14n(b3)

assert b1.getvalue() == b2.getvalue()
assert b1.getvalue() != b3.getvalue()
def separator(self):
    return "!@#$%^&*" # Very ugly separator

def _traverseXML(self, xmlElem, tags, xpaths):
    tags.append(xmlElem.tag)
    for e in xmlElem:
        self._traverseXML(e, tags, xpaths)

    text = ''
    if (xmlElem.text):
        text = xmlElem.text.strip()

    xpaths.add("/".join(tags) + self.separator() + text)
    tags.pop()

def _xmlToSet(self, xml):
    xpaths = set() # output
    tags = list()
    root = ET.fromstring(xml)
    self._traverseXML(root, tags, xpaths)

    return xpaths

def _areXMLsAlike(self, xml1, xml2):
    xpaths1 = self._xmlToSet(xml1)
    xpaths2 = self._xmlToSet(xml2)`enter code here`

    return xpaths1 == xpaths2