Тестирование эквивалентности xml.etree.ElementTree
Меня интересует эквивалентность двух элементов xml; и я обнаружил, что тестирование функции tostring элементов работает; однако, это кажется взломанным. Есть ли лучший способ проверить эквивалентность двух элементов Etree? Пример:
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
Ответы
Ответ 1
Эта функция сравнения работает для меня:
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))
Ответ 2
Сравнение строк не всегда работает. Порядок атрибутов не должен иметь значения при рассмотрении двух эквивалентных узлов. Однако, если вы выполняете сравнение строк, порядок имеет значение.
Я не уверен, что это проблема или функция, но моя версия 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, которые были созданы разными сериализаторами, я не вижу другого способа, кроме рекурсивного сравнения тега, текста, атрибутов и дочерних элементов каждого node. И, конечно, хвост, если там что-то интересное.
Сравнение 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
Другим вопросом может быть то, что считается несущественным при сравнении. Например, некоторые фрагменты могут содержать дополнительные пробелы, и мы не хотим заботиться. Таким образом, всегда лучше написать некоторую функцию сериализации, которая работает именно так, как нам нужно.
Ответ 3
Сериализация и десериализация не будут работать для 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
Ответ 4
Верьте или нет, это на самом деле лучший способ справиться с сравнением двух узлов, если вы не знаете, сколько детей у каждого есть, и вы хотите включить всех детей в поиск.
Конечно, если вы просто имеете бездетный node, как тот, который вы демонстрируете, вы можете просто сравнить свойства тега, атрибута и хвоста:
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.
Ответ 5
Обычный способ сравнения сложных структур состоит в том, чтобы сбрасывать их в общем уникальном текстовом представлении и сравнивать полученные строки для равенства.
Чтобы сравнить две полученные строки json, вы должны преобразовать их в json-объекты, а затем преобразовать их обратно в строки (с одним и тем же преобразователем) и сравнить. Я сделал это, чтобы проверить каналы json, он работает хорошо.
Для XML это почти то же самое, но вам, возможно, придется обрабатывать (strip? remove?) части ".text" (текст, пустой или нет, который может быть найден за пределами тегов).
Итак, ваше решение не является взломом, если вы убедитесь, что два эквивалентных XML (в соответствии с вашим контекстом) будут иметь одинаковое строковое представление.
Ответ 6
Не золотая пластина. У вас есть хорошее сравнение. В конце XML это ТЕКСТ.