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

XML Diff: как сгенерировать XML-diff с помощью XSLT?

Я хотел бы вычислить разницу между двумя файлами XML или узлами с помощью XSL/XSLT. Есть ли какая-либо таблица стилей, легко доступная или любой простой способ сделать это?

4b9b3361

Ответ 1

Интересный вопрос! Однажды я попытался сделать что-то подобное с использованием двух источников XML, и мой опыт состоял в том, что просто нет способа.

Вы можете использовать объект XSL для включения пользовательских функций, а также код для чего-то действительно гладкого. Но я действительно не вижу этого.

Если бы я должен был сделать что-то подобное, я бы обработал два файла XML параллельно с помощью DOM4J, что позволяет мне легко перемещать код программно и делать подробные подзапросы.

Попытка сделать это в XSLT либо докажет, что вы гений, либо побуждаете вас к безумию.

Ответ 2

XSLT управляется данными, то есть он проходит через XML файл с одним источником сверху вниз, ища совпадения шаблонов в таблице стилей XSL. Шаблоны не знают, где они находятся в данных, они просто запускают свой код при согласовании. Вы можете ссылаться на другой источник XML, но программа будет работать в соответствии с обходом исходного источника.

Итак, когда вы приходите к n-му дочернему элементу <blarg>, например, вы можете найти n-й дочерний элемент <blarg> во втором XML, используя функцию document(). Но полезность этого зависит от структуры вашего XML и того, какие сравнения вы пытаетесь сделать.

Это поведение противоположно большинству традиционных сценариев, которые проходят через программный код сверху вниз, вызывая файл данных при указании. Последнее - вытягивание обработки - это то, что вам, вероятно, нужно сравнить с двумя источниками XML. XSLT будет разбиваться в сравнении, как только будет разница.

Ответ 3

Это не тайна! Вот основные шаги:

  • @carillonator прав насчет того, как XSLT обрабатывает документы. Поэтому, чтобы упростить, мы объединим две версии ваших документов в один документ, который вы можете использовать для запуска XSLT diff (вы можете сделать это через командную строку с помощью bash или с любым языком программирования, который вы используете, или даже другое преобразование XSLT [труба]). Это просто инкапсуляция:

    <diff_container>
        <version1>
          ... first version here
        </version1>
        <version2>
          ... second version here
        </version2>
    </diff_container>
    
  • Затем мы запускаем этот документ через наш XSLT diff, XSLT затем выполняет простое пересечение дерева и сравнение узлов между двумя версиями. Это может быть очень простым (был ли элемент изменен? Перемещено? Удалено?) До полукомпонента. Хорошее понимание XPath делает это довольно простым.

    Как и раньше, вы работаете в другой среде, поэтому вы ограничены по сравнению с такими инструментами, как Diff Dog. Однако преимущество наличия алгоритма в XSLT также может иметь реальное значение.

Надеюсь, это помогло. Ура!

Ответ 4

Если то, что вы подразумеваете под diff, - это что-то вроде проверки того, существуют ли элементы в одном документе (или node), но не в другом, вы можете использовать функцию xpath key() с третьим параметром

<?xml version="1.0"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs ="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xsl xs">

<xsl:param name="doc2diff" required="yes"/>
<!-- docB is root node of the "second" document -->
<xsl:variable name="docB" select="document($doc2diff)"/>
<!-- docA is the root node of the first document -->
<xsl:variable name="docA" select="/"/>
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:key name="items" match="Item" use="someId"/>

<xsl:template match="/">
 <ListOfItems>
  <In_A_NotIn_B>
   <xsl:apply-templates select="Item">
    <xsl:with-param name="otherDocument" select="$docB"/>
   </xsl:apply-templates>
  </In_A_NotIn_B>
  <In_B_NotIn_A>
   <xsl:apply-templates select="Item">
    <xsl:with-param name="otherDocument" select="$docA"/>
   </xsl:apply-templates>
  </In_B_NotIn_A>
 </ListOfItems>
</xsl:template>

<xsl:template match="Item">
 <xsl:param name="otherDocument"/>
  <xsl:variable name="SOMEID" select="someId"/>
  <xsl:if test="empty(key('items', $SOMEID, $otherDocument))">
   <xsl:copy-of select="."/>
  </xsl:if>
</xsl:template>

</xsl:stylesheet>`

Ответ 5

Это таблица стилей, которую я написал, чтобы сравнить два файла XML с разным порядком в узлах и атрибуте, он будет генерировать два текстовых файла, содержащих упорядоченный список, всех путей листьев. Используйте любой инструмент сравнения текста, чтобы выявить различия или улучшить XSLT, чтобы делать то, что вы хотите.

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output method="text" indent="no" omit-xml-declaration="yes" name="output" />

<xsl:param name="OTHERFILENAME">xml_file_to_diff.xml</xsl:param>
<xsl:param name="ORIGINAL_OUTPUT_FILENAME">ORIGINAL.txt</xsl:param>
<xsl:param name="OTHER_OUTPUT_FILENAME">OTHER.txt</xsl:param>

<xsl:template match="/">
    <xsl:call-template name="convertXMLHierarchyToFullPath">
        <xsl:with-param name="node" select="*"/>
        <xsl:with-param name="filename" select="$ORIGINAL_OUTPUT_FILENAME"/>
    </xsl:call-template>
    <xsl:call-template name="convertXMLHierarchyToFullPath">
        <xsl:with-param name="node" select="document($OTHERFILENAME)/*"/>
        <xsl:with-param name="filename" select="$OTHER_OUTPUT_FILENAME"/>
    </xsl:call-template>
</xsl:template>

<xsl:template name="convertXMLHierarchyToFullPath">
    <xsl:param name="node"/>
    <xsl:param name="filename"/>

    <xsl:variable name="unorderedFullPath">
        <xsl:apply-templates select="$node"/>
    </xsl:variable>

    <xsl:result-document href="{$filename}" format="output">
        <xsl:for-each select="$unorderedFullPath/*">
            <xsl:sort select="@path" data-type="text"/>
            <xsl:value-of select="@path"/>
            <xsl:text>&#xA;</xsl:text>
        </xsl:for-each>
    </xsl:result-document>
</xsl:template>

<xsl:template match="*">
    <xsl:if test="not(*)">
        <leaf>
            <xsl:attribute name="path">
                <xsl:for-each select="ancestor-or-self::*">
                    <xsl:value-of select="name()"/>
                    <xsl:for-each select="@*">
                        <xsl:sort select="name()" data-type="text"/>
                        <xsl:text>[</xsl:text>
                        <xsl:value-of select="name()"/>
                        <xsl:text>:</xsl:text>
                        <xsl:value-of select="."/>
                        <xsl:text>]</xsl:text>
                    </xsl:for-each>
                    <xsl:text>/</xsl:text>
                </xsl:for-each>
                <xsl:value-of select="."/>
            </xsl:attribute>
        </leaf>
    </xsl:if>
    <xsl:apply-templates select="*"/>
</xsl:template>

Ответ 6

Есть способы сделать это, но я бы не сказал, что это просто.

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

Мне пришлось написать дополнительную таблицу стилей, чтобы затем преобразовать ее в более читаемый отчет HTML.

Некоторые инструменты для сравнения, такие как XMLSpy Diff dog, являются хорошими, но дорогостоящими.

Ответ 7

Нашел этот пост в последнее время, но в любом случае я поделюсь своим решением для такого рода проблем. У меня были такие же потребности, как @Vincent: сравнение двух разных файлов XML и быстрое различие между ними. У быстрого diff было слишком много строк, потому что файлы не сортировались, поэтому я решил отсортировать файлы с помощью XSLT, а затем сравнить два файла xml вручную, используя WinMerge (например, простой unix diff также может выполнять задание).

Вот XSLT, который сортирует мой XML файл:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output method="xml" indent="yes" encoding="UTF-8"/>

<xsl:template match="node()|@*">
    <xsl:copy>
            <xsl:apply-templates select="node()|@*">
                    <xsl:sort select="name()" />
                    <xsl:sort select="@*" />
                    <xsl:sort select="*" />
                    <xsl:sort select="text()" />
            </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

Ответ 8

Вот решение Microsoft XSLT 1.0, которое я недавно написал. Это неупорядоченное сравнение XML и атрибутов, которое возвращает элементы во входном файле, не найденные в переменной file2 из b.xml. Если вы измените порядок узлов без изменения каких-либо значений, это не будет считаться разницей. Проверьте это на моем github https://github.com/sflynn1812/xslt-diff-turbo