2010-06-09 29 views
30

Xây dựng trên another SO question, cách có thể kiểm tra xem hai đoạn mã XML được định dạng tốt có bằng ngữ nghĩa hay không. Tất cả những gì tôi cần là "bằng nhau" hay không, vì tôi đang sử dụng nó cho các bài kiểm tra đơn vị.So sánh các đoạn mã XML?

Trong hệ thống tôi muốn, đây sẽ là bằng nhau (lưu ý thứ tự của 'start' và 'kết thúc'):

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

Tôi có lmxl và các công cụ khác theo ý của tôi, và một chức năng đơn giản điều đó chỉ cho phép sắp xếp lại các thuộc tính sẽ hoạt động tốt!


Working Đoạn dựa trên câu trả lời IanB của:

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 
+1

'từ formencode.doctest_xml_compare nhập khẩu xml_compare' – laike9m

Trả lời

24

Bạn có thể sử dụng formencode.doctest_xml_compare - hàm xml_compare so sánh hai cây ElementTree hoặc lxml.

+0

Cảm ơn Ian, tôi rất vui vì một người nào đó bạn đã có giải pháp này! –

+2

Chức năng này không chính xác, nếu bạn hoán đổi thứ tự thuộc tính trong xml, nó sẽ trả về Sai. – mnowotka

+0

@mnowotka không đúng, so sánh bằng _attributes_ theo thứ tự khác nhau – Anentropic

2

Nếu bạn tham gia một cách tiếp cận DOM, bạn có thể đi qua hai cây cùng một lúc trong khi so sánh các nút (loại nút, văn bản, thuộc tính) như bạn đi.

Một giải pháp đệ quy sẽ là thanh lịch nhất - chỉ cần ngắn mạch hơn nữa so sánh một lần một cặp nút không phải là "bình đẳng" hoặc khi bạn phát hiện một chiếc lá trong một cây khi đó là một chi nhánh tại khác vv

+1

Đây là giải pháp, tôi chỉ hy vọng một ai đó đã viết một rồi. –

5

Tôi có cùng một vấn đề: hai tài liệu tôi muốn so sánh có cùng thuộc tính nhưng theo các thứ tự khác nhau.

Dường như XML Canonicalization (C14N) trong lxml hoạt động tốt cho điều này, nhưng tôi chắc chắn không phải là chuyên gia về XML. Tôi tò mò muốn biết liệu có ai khác có thể chỉ ra những hạn chế đối với phương pháp này hay không.

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) 

Chạy này mang lại cho tôi:

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

Cũng có cách tiếp cận này trong tâm trí và đang tìm kiếm những hạn chế hoặc cho dù điều này thực sự có thể là cách kinh điển để so sánh tài liệu xml ... (pun dự định) – michuelnik

+0

Tôi đã sử dụng điều này trong hơn một năm trên một trang web tôi chạy mà so sánh Tài liệu XML cho mục đích kiểm soát phiên bản. Nó hoạt động khá tốt nhưng c14n không kiểm soát việc có cùng các phần tử con theo thứ tự khác nhau, vì vậy đôi khi tôi vẫn nhận được các kết quả giả. –

+0

C14n có sắp xếp lại các con không? Tôi sẽ đoán không ... Bạn có nghĩa là trường hợp mà cùng một đứa trẻ có mặt nhưng theo thứ tự khác nhau bạn muốn có một kết quả "không có sự khác biệt" nhưng điều này mang lại "sự khác biệt được phát hiện"? Theo thứ tự của con tôi có thể là quan trọng. ;) – michuelnik

1

Suy nghĩ về vấn đề này, tôi đã đưa ra các giải pháp sau đây mà ám phần tử XML có thể so sánh và sắp xếp được:

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 
14

Thứ tự của các các yếu tố có thể có ý nghĩa trong XML, điều này có thể là lý do tại sao hầu hết các phương pháp khác được đề xuất sẽ so sánh không bình đẳng nếu thứ tự khác ... ngay cả khi các phần tử có cùng thuộc tính và nội dung văn bản.

Nhưng tôi cũng muốn một sự so sánh trật tự-insensitive, vì vậy tôi đã đưa ra với điều này:

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 
+1

Đây chắc chắn là câu trả lời hay nhất và nên được chấp nhận. Đây là câu trả lời duy nhất thực sự quan tâm đến thực tế quan trọng là thứ tự các trường trong XML không quan trọng. – mnowotka

+3

có hai điều cần xem xét: thứ tự của _attributes_ thực sự không quan trọng. tuy nhiên thứ tự của các phần tử là quan trọng trong XML, mã này là dành cho một trường hợp đặc biệt mà bạn không quan tâm thứ tự của các phần tử – Anentropic

0

Thích ứng Anentropic's great answer để Python 3 (về cơ bản, thay đổi iteritems() để items(), và basestring-string):

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).items(): 
     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, str): 
     a = etree.tostring(a) 
    if not isinstance(b, str): 
     b = etree.tostring(b) 
    a = normalise_dict(xmltodict.parse(a)) 
    b = normalise_dict(xmltodict.parse(b)) 
    return a == b 
+1

Bạn có thể sử dụng tùy chọn 'dict_constructor = dict' cho xmltodict:' xmltodict.parse (a, dict_constructor = dict) ', vì vậy bạn không cần sử dụng hàm' normalise_dict'. – inoks

0

Kể từ order of attributes is not significant in XML, bạn muốn bỏ qua sự khác biệt do các thứ tự thuộc tính khác nhau và XML canonicalization (C14N) xác định thuộc tính đơn đặt hàng s, bạn có thể phương pháp đó để kiểm tra sự bình đẳng:

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

Lưu ý rằng ví dụ này giả định Python 3.Với Python 3, việc sử dụng các chuỗi b'''...'''io.BytesIO là bắt buộc, trong khi với Python 2 phương pháp này cũng hoạt động với các chuỗi thông thường và io.StringIO.

5

Dưới đây là một giải pháp đơn giản, chuyển đổi XML vào từ điển (với xmltodict) và so sánh các từ điển cùng

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 

kiểm tra đơn vị

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

hoặc trong phương pháp python đơn giản:

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

gì về đoạn mã sau đây? Có thể dễ dàng tăng cường thêm công attribs cũng như:

def separator(self): 
    return "[email protected]#$%^&*" # 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