Подтвердить что ты не робот

Тестирование эквивалентности 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

4b9b3361

Ответ 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 это ТЕКСТ.