Тестирование эквивалентности xml.etree.ElementTree - PullRequest
23 голосов
/ 26 октября 2011

Меня интересует эквивалентность двух элементов xml;и я обнаружил, что тестирование tostring элементов работает;однако, это кажется странным.

Есть ли лучший способ проверить эквивалентность двух простых элементов?

Сравнение элементов напрямую:

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

Ответы [ 6 ]

25 голосов
/ 22 июня 2014

Эта функция сравнения работает для меня:

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))
7 голосов
/ 26 сентября 2012

Сравнение строк не всегда работает. Порядок атрибутов не должен иметь значения для рассмотрения двух узлов эквивалентными. Однако, если вы выполняете сравнение строк, порядок, очевидно, имеет значение.

Я не уверен, является ли это проблемой или функцией, но моя версия 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

Это может зависеть от версии (я использую Python 2.7.3 с lxml.etree 2.3.2 в Ubuntu); Я помню, что не мог найти способ контролировать порядок атрибутов год назад или около того, когда я хотел (из соображений читабельности).

Поскольку мне нужно сравнить 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

Другим вопросом может быть то, что считается неважным при сравнении. Например, некоторые фрагменты могут содержать лишние пробелы, и мы не хотим заботиться. Таким образом, всегда лучше написать некоторую сериализационную функцию, которая работает именно так, как нам нужно.

4 голосов
/ 28 августа 2013

Сериализация и десериализация не будут работать для 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
3 голосов
/ 26 октября 2011

Верьте или нет, но на самом деле это лучший способ справиться со сравнением двух узлов, если вы не знаете, сколько у каждого из них может быть детей, и хотите включить в поиск всех детей.

Конечно,если у вас просто есть бездетный узел, подобный тому, который вы демонстрируете, вы можете просто сравнить свойства 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,однако.

2 голосов
/ 26 октября 2011

Обычный способ сравнения сложных структур - выгрузить их в общее уникальное текстовое представление и сравнить полученные строки на равенство.

Чтобы сравнить две полученные строки json, вы должны преобразовать их в объекты json, а затем преобразовать их обратно в строки (с тем же преобразователем) и сравнить.Я сделал это, чтобы проверить каналы json, он работает хорошо.

Для XML это почти то же самое, но вам, возможно, придется обрабатывать (strip? Remove?) Части ".text" (текст, пустое поле).или нет, это может быть найдено за пределами тегов).

Короче говоря, ваше решение не является хаком, если вы убедитесь, что два эквивалентных XML (в соответствии с вашим контекстом) будут иметь одинаковое строковое представление.

0 голосов
/ 26 октября 2011

Не золотая табличка.Тот, который у вас есть, - хорошее сравнение.В конце XML это TEXT.

...