比较XML片段?
在这个问题上,想要知道怎么检查两个格式正确的XML片段是否在语义上是相等的。我的需求很简单,只需要知道它们是否“相等”,因为我打算用它来做单元测试。
在我想要的系统中,这两个片段是相等的(注意‘开始’和‘结束’的顺序):
<?xml version='1.0' encoding='utf-8' standalone='yes'?>
<Stats start="1275955200" end="1276041599">
</Stats>
# Reordered start and end
<?xml version='1.0' encoding='utf-8' standalone='yes'?>
<Stats end="1276041599" start="1275955200" >
</Stats>
我有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
10 个回答
6
这里有个简单的解决办法,就是把XML格式的数据转换成字典(可以用xmltodict这个工具),然后把这些字典进行比较。
import json
import xmltodict
class XmlDiff(object):
def __init__(self, xml1, xml2):
self.dict1 = json.loads(json.dumps((xmltodict.parse(xml1))))
self.dict2 = json.loads(json.dumps((xmltodict.parse(xml2))))
def equal(self):
return self.dict1 == self.dict2
单元测试
import unittest
class XMLDiffTestCase(unittest.TestCase):
def test_xml_equal(self):
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>"""
self.assertTrue(XmlDiff(xml1, xml2).equal())
def test_xml_not_equal(self):
xml1 = """<?xml version='1.0' encoding='utf-8' standalone='yes'?>
<Stats start="1275955200">
</Stats>"""
xml2 = """<?xml version='1.0' encoding='utf-8' standalone='yes'?>
<Stats end="1276041599" start="1275955200" >
</Stats>"""
self.assertFalse(XmlDiff(xml1, xml2).equal())
或者用简单的Python方法:
import json
import xmltodict
def xml_equal(a, b):
"""
Compares two XML documents (as string or etree)
Does not care about element order
"""
return json.loads(json.dumps((xmltodict.parse(a)))) == json.loads(json.dumps((xmltodict.parse(b))))
14
在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
27
你可以使用 formencode.doctest_xml_compare 里的 xml_compare 函数,它可以用来比较两个 ElementTree 或 lxml 树。