Эффективный алгоритм для сравнения узлов XML
Я хочу определить, являются ли два разных дочерних узла в документе XML равными или нет. Два узла следует считать равными, если они имеют одинаковый набор атрибутов и дочерних нот, а все дочерние ноты тоже равны (т.е. Все подэлементы должны быть равны).
Входной документ может быть очень большим (до 60 МБ, более 100 000 узлов для сравнения), а производительность - проблема.
Что было бы эффективным способом проверки равенства двух узлов?
Пример:
<w:p>
<w:pPr>
<w:spacing w:after="120"/>
</w:pPr>
<w:r>
<w:t>Hello</w:t>
</w:r>
</w:p>
<w:p>
<w:pPr>
<w:spacing w:after="240"/>
</w:pPr>
<w:r>
<w:t>World</w:t>
</w:r>
</w:p>
Этот фрагмент XML описывает абзацы в документе OpenXML. Алгоритм будет использоваться для определения того, содержит ли документ абзац (w: p node) с теми же свойствами (w: pPr node) как еще один абзац ранее в документе.
Одна из моих идей - хранить внешний XML-код узлов в хэш-наборе (обычно мне нужно было бы получить представление канонической строки сначала, где атрибуты и дочерние заметки будут отсортированы всегда одинаково, но я могу ожидать, узлы уже должны быть в такой форме).
Другая идея заключалась бы в создании объекта XmlNode для каждого node и записи сравнения, который сравнивает все атрибуты и дочерние узлы.
Моя среда - С# (.Net 2.0); любая обратная связь и дальнейшие идеи очень приветствуются. Может быть, у кого-то даже есть хорошее решение?
EDIT: Microsoft XmlDiff API действительно может это сделать, но мне было интересно, будет ли более легкий подход. XmlDiff, кажется, всегда создает diffgram и всегда создает каноническое представление node во-первых, обе вещи, которые мне не нужны.
EDIT2: Я, наконец, внедрил свой собственный XmlNodeEqualityComparer на основе предлагаемого здесь предложения. Большое спасибо!!!!
Спасибо,
диво
Ответы
Ответ 1
Я бы рекомендовал не перекладывать свою собственную функцию создания хэша и вместо этого полагаться на встроенный метод XNodeEqualityComparer
GetHashCode
. Это гарантирует учет атрибутов и узлов-потомков при создании результата и может сэкономить вам некоторое время.
Ваш код будет выглядеть следующим образом:
XNodeEqualityComparer comparer = new XNodeEqualityComparer();
XDocument doc = XDocument.Load("XmlFile1.xml");
Dictionary<int, XNode> nodeDictionary = new Dictionary<int, XNode>();
foreach (XNode node in doc.Elements("doc").Elements("node"))
{
int hash = comparer.GetHashCode(node);
if (nodeDictionary.ContainsKey(hash))
{
// A duplicate has been found. Execute your logic here
// ...
}
else
{
nodeDictionary.Add(hash, node);
}
}
Мой XmlFile1.xml:
<?xml version="1.0" encoding="utf-8" ?>
<doc>
<node att="A">Blah</node>
<node att="A">Blah</node>
<node att="B">
<inner>Innertext</inner>
</node>
<node>Blah</node>
<node att="B">
<inner>Different</inner>
</node>
</doc>
nodeDictionary
будет содержать уникальную коллекцию узлов и их хэшей. Дубликаты обнаруживаются с помощью метода Dictionary
ContainsKey
, передавая хэш node, который мы генерируем с использованием метода XNodeEqualityComparer
GetHashCode
.
Я думаю, что это должно быть достаточно быстро для ваших нужд.
Ответ 2
Как насчет этого подхода:
Для всех узлов <w:pPr>
в документе (я полагаю, что не более одного на <w:p>
) объединяет все соответствующие данные (имена элементов, атрибуты, значения) в строку:
// string format is really irrelevant, so this is just a bogus example
'!w:[email protected]="true"!w:[email protected]:before="10"@w:after="120"'
Сделайте это в алфавитном порядке, чтобы учитывать различный порядок документов.
Создайте коллекцию, используя эти строки, в качестве ключа и ссылку на соответствующий <w:p>
node как значение.
В процессе выполнения этого, когда вы достигли точки, когда данный ключ уже существует в коллекции, вы нашли абзац с теми же свойствами. Работа со списком узлов в качестве значения коллекции, если вы хотите продолжать сбор.
Я не могу сказать, насколько хорошо это будет выполняться, но я думаю, это не так сложно реализовать и выяснить.
Ответ 3
Очень сложно даже правильно определить проблему
"Когда два документа xml равны?"
Есть много причин для этого:
- XML-документ - это дерево, которое может иметь разные текстовые представления.
- Только узлы с пробелами могут рассматриваться или не учитываться при сравнении
- Узлы комментариев могут рассматриваться или не учитываться при сравнении.
- Узлы PI могут рассматриваться или не учитываться при сравнении.
- Лексические отличия: или
- Различные префиксы могут быть связаны с тем же пространством имен в двух документах
- Пространство имен node может отображаться как определено в node документа doc1 и как не определено, но унаследовано от родителя соответствующего node в doc2
- Цитаты могут использоваться вокруг атрибута в doc1, но апострофы могут использоваться в doc2
- Объекты могут использоваться в doc1, но они могут быть предварительно расширены в doc2
- Два документа могут иметь разные, но семантически эквивалентные DTD
- Etc.
Поэтому представляется наивным и нереалистичным пытаться создать правильную реализацию функции для сравнения равенства двух XML-документов.
Моя рекомендация использовать функцию deep-equal() с совместимый движок XPath 2.0.
Ответ 4
Вот хеш-функция, с которой я столкнулся, пытаясь решить часть вашей проблемы. Обратите внимание, что у меня очень мало опыта написания хеш-функций, и я включил его в основном, чтобы получить обратную связь от людей относительно эффективности в решении этой конкретной проблемы. Я бы не рекомендовал его использовать в производстве.
static int HashXElement(XElement elem)
{
int hash = 23;
foreach (XAttribute attrib in elem.Attributes())
{
int attribHash = 23;
attribHash = attribHash * 37 + attrib.Name.GetHashCode();
attribHash = attribHash * 37 + attrib.Value.GetHashCode();
hash = hash ^ attribHash;
}
foreach(XElement subElem in elem.Descendants())
{
hash = hash * 37 + XmlHash(subElem);
}
hash = hash * 37 + elem.Value.GetHashCode();
return hash;
}
Идеи состояли в том, чтобы сделать упорядочение сундомов значительным, но порядок атрибутов не значителен.
Ответ 5
не прямой ответ на ваш вопрос, но тесно связанный с тем, что вы пытаетесь достичь: посмотрите XmlDiff (.net XML-инструменты)