Сравните XML, игнорируя порядок дочерних элементов

Кто-нибудь знает инструмент, который будет сравнивать два XML-документа. Страхуй эту насмешку... это еще не все. Мне нужно что-то, чтобы убедиться, что каждый узел в файле 1 также находится в файле 2 независимо от порядка. Я думал, что XML Spy сделает это с опцией Игнорировать порядок дочерних узлов, но это не так. Следующее будет считаться тем же:

<Node>
    <Child name="Alpha"/>
    <Child name="Beta"/>
    <Child name="Charlie"/>
</Node>

<Node>
    <Child name="Beta"/>
    <Child name="Charlie"/>
    <Child name="Alpha"/>
</Node>

13 ответов

Решение

Возможно, вы захотите поискать " XML diff tool ", который даст вам более чем адекватные результаты. Одним из них является OxygenXml, инструмент, который я часто использую. Вы также можете попробовать инструмент Microsoft Diff and Patch Tool от Microsoft.

Удачи.

Я написал простой инструмент Python для этого под названием xmldiffs:

Сравните два XML-файла, игнорируя порядок элементов и атрибутов.

Использование: xmldiffs [OPTION] FILE1 FILE2

Любые дополнительные параметры передаются diff команда.

Получить его на https://github.com/joh/xmldiffs

С Beyond Compare вы можете использовать в File Formats-Настройки XML Sort Конверсия. С этой опцией дочерние элементы XML будут отсортированы до сравнения.

Доступна новая версия Beyond Compare.

Я бы использовал XMLUnit для этого, так как он может обслуживать элементы в другом порядке.

С C# Вы можете сделать это, а затем сравнить его с любым инструментом сравнения.

public void Run()
{
    LoadSortAndSave(@".. first file ..");
    LoadSortAndSave(@".. second file ..");
}

public void LoadSortAndSave(String path)
{
    var xdoc = XDocument.Load(path);
    SortXml(xdoc.Root);
    File.WriteAllText(path + ".sorted", xdoc.ToString());
}

private void SortXml(XContainer parent)
{
    var elements = parent.Elements()
        .OrderBy(e => e.Name.LocalName)
        .ToArray();

    Array.ForEach(elements, e => e.Remove());

    foreach (var element in elements)
    {
        parent.Add(element);
        SortXml(element);
    }
}

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

Мой обходной путь состоял в том, чтобы отсортировать два XML-файла, которые я хотел различить, сортируя в алфавитном порядке по имени элемента. Как только они были в согласованном порядке, я мог различать два отсортированных файла, используя обычный инструмент визуального сравнения.

Если этот подход кажется полезным для кого-то еще, я поделился написанным мной сценарием Python для сортировки по адресу http://dalelane.co.uk/blog/?p=3225

Я недавно дал аналогичный ответ здесь ( инструмент командной строки с открытым исходным кодом для Linux для сравнения XML-файлов, игнорируя порядок элементов), но я предоставлю более подробную информацию...

если вы пишете программу, которая объединяет два дерева, вы можете настроить логику для определения "совпадений" между деревьями, а также для обработки узлов, которые не совпадают. Вот пример в xslt 2.0 (извините, он так долго):

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

                xmlns:set="http://exslt.org/sets"

                xmlns:primary="primary"
                xmlns:control="control"

                xmlns:util="util"

                exclude-result-prefixes="xsl xs set primary control">

    <!-- xml diff tool

         import this stylesheet from another and call the "compare" template with two args:

             primary: the root of the primary tree to submit to comparison
             control: the root of the control tree to compare against

         the two trees will be walked together. the primary tree will be walked in document order, matching elements
         and attributes from the control tree along the way, building a tree of common content, with appendages
         containing primary and control only content. that tree will then be used to generate the diff.

         the process of matching involves finding, for an element or attribute in the primary tree, the
         equivalent element or attribute in the control tree, *at the same level*, and *regardless of ordering*.

             matching logic is encoded as templates with mode="find-match", providing a hook to wire in specific
             matching logic for particular elements or attributes. for example, an element may "match" based on an
             @id attribute value, irrespective of element ordering; encode this in a mode="find-match" template.

             the treatment of diffs is encoded as templates with mode="primary-only" and "control-only", providing
             hooks for alternate behavior upon encountering differences.

          -->

    <xsl:output method="text"/>

    <xsl:strip-space elements="*"/>

    <xsl:param name="full" select="false()"/><!-- whether to render the full doc, as opposed to just diffs -->

    <xsl:template match="/">
        <xsl:call-template name="compare">
            <xsl:with-param name="primary" select="*/*[1]"/><!-- first child of root element, for example -->
            <xsl:with-param name="control" select="*/*[2]"/><!-- second child of root element, for example -->
        </xsl:call-template>
    </xsl:template>

    <!-- OVERRIDES: templates that can be overridden to provide targeted matching logic and diff treatment -->

    <!-- default find-match template for elements
         (by default, for "complex" elements, name has to match, for "simple" elements, name and value do)
         for context node (from "primary"), choose from among $candidates (from "control") which one matches
         (override with more specific match patterns to effect alternate behavior for targeted elements)
         -->
    <xsl:template match="*" mode="find-match" as="element()?">
        <xsl:param name="candidates" as="element()*"/>
        <xsl:choose>
            <xsl:when test="text() and count(node()) = 1"><!-- simple content -->
                <xsl:sequence select="$candidates[node-name(.) = node-name(current())][text() and count(node()) = 1][. = current()][1]"/>
            </xsl:when>
            <xsl:when test="not(node())"><!-- empty -->
                <xsl:sequence select="$candidates[node-name(.) = node-name(current())][not(node())][1]"/>
            </xsl:when>
            <xsl:otherwise><!-- presumably complex content -->
                <xsl:sequence select="$candidates[node-name(.) = node-name(current())][1]"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <!-- default find-match template for attributes
         (by default, name and value have to match)
         for context attr (from "primary"), choose from among $candidates (from "control") which one matches
         (override with more specific match patterns to effect alternate behavior for targeted attributes)
         -->
    <xsl:template match="@*" mode="find-match" as="attribute()?">
        <xsl:param name="candidates" as="attribute()*"/>
        <xsl:sequence select="$candidates[. = current()][node-name(.) = node-name(current())][1]"/>
    </xsl:template>

    <!-- default primary-only template (override with more specific match patterns to effect alternate behavior) -->
    <xsl:template match="@* | *" mode="primary-only">
        <xsl:apply-templates select="." mode="illegal-primary-only"/>
    </xsl:template>

    <!-- write out a primary-only diff -->
    <xsl:template match="@* | *" mode="illegal-primary-only">
        <primary:only>
            <xsl:copy-of select="."/>
        </primary:only>
    </xsl:template>

    <!-- default control-only template (override with more specific match patterns to effect alternate behavior) -->
    <xsl:template match="@* | *" mode="control-only">
        <xsl:apply-templates select="." mode="illegal-control-only"/>
    </xsl:template>

    <!-- write out a control-only diff -->
    <xsl:template match="@* | *" mode="illegal-control-only">
        <control:only>
            <xsl:copy-of select="."/>
        </control:only>
    </xsl:template>

    <!-- end OVERRIDES -->

    <!-- MACHINERY: for walking the primary and control trees together, finding matches and recursing -->

    <!-- compare "primary" and "control" trees (this is the root of comparison, so CALL THIS ONE !) -->
    <xsl:template name="compare">
        <xsl:param name="primary"/>
        <xsl:param name="control"/>

        <!-- write the xml diff into a variable -->
        <xsl:variable name="diff">
            <xsl:call-template name="match-children">
                <xsl:with-param name="primary" select="$primary"/>
                <xsl:with-param name="control" select="$control"/>
            </xsl:call-template>
        </xsl:variable>

        <!-- "print" the xml diff as textual output -->
        <xsl:apply-templates select="$diff" mode="print">
            <xsl:with-param name="render" select="$full"/>
        </xsl:apply-templates>

    </xsl:template>

    <!-- assume primary (context) element and control element match, so render the "common" element and recurse -->
    <xsl:template match="*" mode="common">
        <xsl:param name="control"/>

        <xsl:copy>
            <xsl:call-template name="match-attributes">
                <xsl:with-param name="primary" select="@*"/>
                <xsl:with-param name="control" select="$control/@*"/>
            </xsl:call-template>

            <xsl:choose>
                <xsl:when test="text() and count(node()) = 1">
                    <xsl:value-of select="."/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:call-template name="match-children">
                        <xsl:with-param name="primary" select="*"/>
                        <xsl:with-param name="control" select="$control/*"/>
                    </xsl:call-template>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:copy>

    </xsl:template>

    <!-- find matches between collections of attributes in primary vs control -->
    <xsl:template name="match-attributes">
        <xsl:param name="primary" as="attribute()*"/>
        <xsl:param name="control" as="attribute()*"/>
        <xsl:param name="primaryCollecting" as="attribute()*"/>

        <xsl:choose>
            <xsl:when test="$primary and $control">
                <xsl:variable name="this" select="$primary[1]"/>
                <xsl:variable name="match" as="attribute()?">
                    <xsl:apply-templates select="$this" mode="find-match">
                        <xsl:with-param name="candidates" select="$control"/>
                    </xsl:apply-templates>
                </xsl:variable>

                <xsl:choose>
                    <xsl:when test="$match">
                        <xsl:copy-of select="$this"/>
                        <xsl:call-template name="match-attributes">
                            <xsl:with-param name="primary" select="subsequence($primary, 2)"/>
                            <xsl:with-param name="control" select="remove($control, 1 + count(set:leading($control, $match)))"/>
                            <xsl:with-param name="primaryCollecting" select="$primaryCollecting"/>
                        </xsl:call-template>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:call-template name="match-attributes">
                            <xsl:with-param name="primary" select="subsequence($primary, 2)"/>
                            <xsl:with-param name="control" select="$control"/>
                            <xsl:with-param name="primaryCollecting" select="$primaryCollecting | $this"/>
                        </xsl:call-template>
                    </xsl:otherwise>
                </xsl:choose>

            </xsl:when>
            <xsl:otherwise>
                <xsl:if test="$primaryCollecting | $primary">
                    <xsl:apply-templates select="$primaryCollecting | $primary" mode="primary-only"/>
                </xsl:if>
                <xsl:if test="$control">
                    <xsl:apply-templates select="$control" mode="control-only"/>
                </xsl:if>
            </xsl:otherwise>
        </xsl:choose>

    </xsl:template>

    <!-- find matches between collections of elements in primary vs control -->
    <xsl:template name="match-children">
        <xsl:param name="primary" as="node()*"/>
        <xsl:param name="control" as="element()*"/>

        <xsl:variable name="this" select="$primary[1]" as="node()?"/>

        <xsl:choose>
            <xsl:when test="$primary and $control">
                <xsl:variable name="match" as="element()?">
                    <xsl:apply-templates select="$this" mode="find-match">
                        <xsl:with-param name="candidates" select="$control"/>
                    </xsl:apply-templates>
                </xsl:variable>

                <xsl:choose>
                    <xsl:when test="$match">
                        <xsl:apply-templates select="$this" mode="common">
                            <xsl:with-param name="control" select="$match"/>
                        </xsl:apply-templates>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:apply-templates select="$this" mode="primary-only"/>
                    </xsl:otherwise>
                </xsl:choose>
                <xsl:call-template name="match-children">
                    <xsl:with-param name="primary" select="subsequence($primary, 2)"/>
                    <xsl:with-param name="control" select="if (not($match)) then $control else remove($control, 1 + count(set:leading($control, $match)))"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:when test="$primary">
                <xsl:apply-templates select="$primary" mode="primary-only"/>
            </xsl:when>
            <xsl:when test="$control">
                <xsl:apply-templates select="$control" mode="control-only"/>
            </xsl:when>
        </xsl:choose>

    </xsl:template>

    <!-- end MACHINERY -->

    <!-- PRINTERS: print templates for writing out the diff -->

    <xsl:template match="*" mode="print">
        <xsl:param name="depth" select="-1"/>
        <xsl:param name="render" select="false()"/>
        <xsl:param name="lineLeader" select="' '"/>
        <xsl:param name="rest" as="element()*"/>

        <xsl:if test="$render or descendant::primary:* or descendant::control:*">

            <xsl:call-template name="whitespace">
                <xsl:with-param name="indent" select="$depth"/>
                <xsl:with-param name="leadChar" select="$lineLeader"/>
            </xsl:call-template>
            <xsl:text>&lt;</xsl:text>
            <xsl:value-of select="name(.)"/>

            <xsl:apply-templates select="@* | primary:*[@*] | control:*[@*]" mode="print">
                <xsl:with-param name="depth" select="$depth"/>
                <xsl:with-param name="render" select="$render"/>
                <xsl:with-param name="lineLeader" select="$lineLeader"/>
            </xsl:apply-templates>

            <xsl:choose>
                <xsl:when test="text() and count(node()) = 1"><!-- field element (just textual content) -->
                    <xsl:text>&gt;</xsl:text>
                    <xsl:value-of select="."/>
                    <xsl:text>&lt;/</xsl:text>
                    <xsl:value-of select="name(.)"/>
                    <xsl:text>&gt;</xsl:text>
                </xsl:when>
                <xsl:when test="count(node()) = 0"><!-- empty (self-closing) element -->
                    <xsl:text>/&gt;</xsl:text>
                </xsl:when>
                <xsl:otherwise><!-- complex content -->
                    <xsl:text>&gt;&#10;</xsl:text>
                    <xsl:apply-templates select="*[not(self::primary:* and @*) and not(self::control:* and @*)]" mode="print">
                        <xsl:with-param name="depth" select="$depth + 1"/>
                        <xsl:with-param name="render" select="$render"/>
                        <xsl:with-param name="lineLeader" select="$lineLeader"/>
                    </xsl:apply-templates>
                    <xsl:call-template name="whitespace">
                        <xsl:with-param name="indent" select="$depth"/>
                        <xsl:with-param name="leadChar" select="$lineLeader"/>
                    </xsl:call-template>
                    <xsl:text>&lt;/</xsl:text>
                    <xsl:value-of select="name(.)"/>
                    <xsl:text>&gt;</xsl:text>
                </xsl:otherwise>
            </xsl:choose>

            <xsl:text>&#10;</xsl:text>

        </xsl:if>

        <!-- write the rest of the elements, if any -->
        <xsl:apply-templates select="$rest" mode="print">
            <xsl:with-param name="depth" select="$depth"/>
            <xsl:with-param name="render" select="$render"/>
            <xsl:with-param name="lineLeader" select="$lineLeader"/>
            <xsl:with-param name="rest" select="()"/><!-- avoid implicit param pass to recursive call! -->
        </xsl:apply-templates>

    </xsl:template>

    <xsl:template match="@*" mode="print">
        <xsl:param name="depth" select="0"/>
        <xsl:param name="render" select="false()"/>
        <xsl:param name="lineLeader" select="' '"/>
        <xsl:param name="rest" as="attribute()*"/>

        <xsl:if test="$render">

            <xsl:text>&#10;</xsl:text>
            <xsl:call-template name="whitespace">
                <xsl:with-param name="indent" select="$depth + 3"/>
                <xsl:with-param name="leadChar" select="$lineLeader"/>
            </xsl:call-template>
            <xsl:value-of select="name(.)"/>
            <xsl:text>="</xsl:text>
            <xsl:value-of select="."/>
            <xsl:text>"</xsl:text>
        </xsl:if>

        <xsl:apply-templates select="$rest" mode="print">
            <xsl:with-param name="depth" select="$depth"/>
            <xsl:with-param name="render" select="$render"/>
            <xsl:with-param name="lineLeader" select="$lineLeader"/>
            <xsl:with-param name="rest" select="()"/><!-- avoid implicit param pass to recursive call! -->
        </xsl:apply-templates>

    </xsl:template>

    <xsl:template match="primary:* | control:*" mode="print">
        <xsl:param name="depth"/>

        <xsl:variable name="diffType" select="util:diff-type(.)"/>
        <xsl:variable name="primary" select="self::primary:*"/>
        <xsl:variable name="lineLeader" select="if ($primary) then '+' else '-'"/>

        <!-- only if this is the first in a sequence of control::* elements, since the rest are handled along with the first... -->
        <xsl:if test="util:diff-type(preceding-sibling::*[1]) != $diffType">
            <xsl:if test="@*">
                <xsl:text>&#10;</xsl:text>
            </xsl:if>
            <xsl:call-template name="diffspace">
                <xsl:with-param name="indent" select="if (@*) then $depth + 3 else $depth"/>
                <xsl:with-param name="primary" select="$primary"/>
            </xsl:call-template>
            <b><i>&lt;!-- ... --&gt;</i></b><!-- something to identify diff sections in output -->
            <xsl:if test="node()">
                <xsl:text>&#10;</xsl:text>
            </xsl:if>
            <xsl:variable name="rest" select="set:leading(following-sibling::*, following-sibling::*[util:diff-type(.) != $diffType])"/>
            <xsl:apply-templates select="@* | node()" mode="print">
                <xsl:with-param name="depth" select="$depth"/>
                <xsl:with-param name="render" select="true()"/>
                <xsl:with-param name="lineLeader" select="$lineLeader"/>
                <xsl:with-param name="rest" select="$rest/@* | $rest/*"/>
            </xsl:apply-templates>
        </xsl:if>
    </xsl:template>

    <xsl:template name="whitespace">
        <xsl:param name="indent" select="0" as="xs:integer"/>
        <xsl:param name="leadChar" select="' '"/>
        <xsl:choose>
            <xsl:when test="$indent > 0">
                <xsl:value-of select="$leadChar"/>
                <xsl:text> </xsl:text>
                <xsl:for-each select="0 to $indent - 1">
                    <xsl:text>  </xsl:text>
                </xsl:for-each>
            </xsl:when>
            <xsl:otherwise>
                <xsl:for-each select="0 to $indent">
                    <xsl:text>  </xsl:text>
                </xsl:for-each>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <xsl:template name="diffspace">
        <xsl:param name="indent" select="0" as="xs:integer"/>
        <xsl:param name="primary" select="false()"/>
        <xsl:for-each select="0 to $indent">
            <xsl:choose>
                <xsl:when test="$primary">
                    <xsl:text>++</xsl:text>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:text>--</xsl:text>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each>
    </xsl:template>

    <!-- just an "enum" for deciding whether to group adjacent diffs -->
    <xsl:function name="util:diff-type" as="xs:integer">
        <xsl:param name="construct"/>
        <xsl:sequence select="if ($construct/self::primary:*[@*]) then 1 else
                              if ($construct/self::control:*[@*]) then 2 else
                              if ($construct/self::primary:*) then 3 else
                              if ($construct/self::control:*) then 4 else
                              if ($construct) then 5 else 0"/>
    </xsl:function>

    <!-- end PRINTERS -->

</xsl:stylesheet>

рассмотрим этот пример ввода, основанный на вашем:

<test>
    <Node>
        <Child name="Alpha"/>
        <Child name="Beta"/>
        <Child name="Charlie"/>
    </Node>
    <Node>
        <Child name="Beta"/>
        <Child name="Charlie"/>
        <Child name="Alpha"/>
    </Node>
</test>

с таблицей стилей, как есть, следующий результат применительно к примеру:

<Node>
  <Child
++++++++<!-- ... -->
+       name="Alpha"
--------<!-- ... -->
-       name="Beta">
  </Child>
  <Child
++++++++<!-- ... -->
+       name="Beta"
--------<!-- ... -->
-       name="Charlie">
  </Child>
  <Child
++++++++<!-- ... -->
+       name="Charlie"
--------<!-- ... -->
-       name="Alpha">
  </Child>
</Node>

но, если вы добавите этот пользовательский шаблон:

<xsl:template match="Child" mode="find-match" as="element()?">
    <xsl:param name="candidates" as="element()*"/>
    <xsl:sequence select="$candidates[@name = current()/@name][1]"/>
</xsl:template>

который говорит, чтобы соответствовать Child элемент на основе его @name атрибут, то вы не получите выходных данных (то есть нет различий).

Написал простую Java-программу для этого. Хранит два сравниваемых XML в HashMap, с ключом в качестве XPath элемента (включая текстовое значение элемента) и значением как число вхождений этого элемента. затем сравнил два HashMap для набора ключей и значений.

/ ** * создает карту элементов с текстовыми значениями и без вложенных узлов.
* Здесь ключом карты является XPATH элемента, объединенного с текстовым значением элемента, значением элемента является число вхождений этого элемента.
* * @param xmlContent * @return * @throws ParserConfigurationException * @throws SAXException * @throws IOException * /

private static Map<String, Long> getMapOfElementsOfXML(String xmlContent)

        throws ParserConfigurationException, SAXException, IOException {

    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

    dbf.setValidating(false);

    DocumentBuilder db = dbf.newDocumentBuilder();

    Document doc1 = db.parse(new ByteArrayInputStream(xmlContent.getBytes()));

    NodeList entries = doc1.getElementsByTagName("*");

    Map<String, Long> mapElements = new HashMap<>();

    for (int i = 0; i < entries.getLength(); i++) {

        Element element = (Element) entries.item(i);

        if (element.getChildNodes().getLength() == 1&&element.getTextContent()!=null) {

            final String elementWithXPathAndValue = getXPath(element.getParentNode())

                    + "/"

                    + element.getParentNode().getNodeName()

                    + "/"

                    + element.getTagName()

                    + "/"

                    + element.getTextContent();

            Long countValue = mapElements.get(elementWithXPathAndValue);

            if (countValue == null) {

                countValue = Long.valueOf(0l);

            } else {

                ++countValue;

            }

            mapElements.put(elementWithXPathAndValue, countValue);

        }

    }

    return mapElements;

}

static String getXPath(Node node) {

    Node parent = node.getParentNode();

    if (parent == null) {

        return "";

    }

    return getXPath(parent) + "/" + parent.getNodeName();

}

Полная программа здесь https://comparetwoxmlsignoringstanzaordering.blogspot.com/2018/12/java-program-to-compare-two-xmls.html

Вот разностное решение с использованием SWI-Prolog

:- use_module(library(xpath)).
load_trees(XmlRoot1, XmlRoot2) :-
    load_xml('./xml_source_1.xml', XmlRoot1, _),
    load_xml('./xml_source_2.xml', XmlRoot2, _).

find_differences(Reference, Root1, Root2) :-
    xpath(Root1, //'Child'(@name=Name), Node),
    not(xpath(Root2, //'Child'(@name=Name), Node)),
    writeln([Reference, Name, Node]).

diff :-
    load_trees(Root1, Root2),
    (find_differences('1', Root1, Root2) ; find_differences('2', Root2, Root1)).

Prolog объединит переменную Name, чтобы сопоставить узлы из файла 1 и файла 2. Объединение по переменной Node обнаруживает "diff".

Вот пример выходных данных ниже:

% file 1 and file 2 have no differences 
?- diff.
false.

% "Alpha" was updated  in file 2
?- diff.
[1,Alpha,element(Child,[name=Alpha],[])]
[2,Alpha,element(Child,[name=Alpha,age=7],[])]
false.

Вы можете использовать плагин «pom sorter» в Idea Intellij и использовать собственный инструмент Intellij «Сравнить файлы».

Ссылка на торговую площадку для плагина pom sorter: https://plugins.jetbrains.com/plugin/7084-pom-sorter

/**
     * @author sdiallo
     * @since 2017-01-16
     * <p>
     * Compare the content of two XML file
     * </p>
     * <ul>
     * <li>Ignore the white space</li>
     * <li>Ignore the attribute order</li>
     * <li>Ignore the comment</li>
     * <li>Ignore Sequence child nodes are not the same</li>
     * <ul>
     * 
     * @param String XML
     *            first Content to be compared
     * @param String XML
     *            second Content to be compared
     * @return List the differences computed between the two files
     *         <ul>
     *         <li>null means the files are equal</li>         
     *         <li>elsewhere the files are different</li>
     *         <ul>
     * */
    public static List buildDiffXMLs(String xmlExpected, String xmlGenerated) {
        List<?> differencesList = null;

    XMLUnit.setIgnoreAttributeOrder(true);
    XMLUnit.setIgnoreComments(true);
    XMLUnit.setIgnoreWhitespace(true);

    try {
        DetailedDiff diff = new DetailedDiff(XMLUnit.compareXML(
                xmlExpected, xmlGenerated));

        // Two documents are considered to be "similar" if they contain the
        // same elements and attributes regardless of order.
        if ( !diff.identical() && !diff.similar()) {
            differencesList = diff.getAllDifferences();
        }// end if

    } catch (SAXException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

    return differencesList;
}// buildDiffXMLs

Самый простой способ сделать это — использовать инструмент управления версиями, такой как git черепахи.

  1. Создайте учетную запись на гитхабе
  2. Создайте репозиторий git в своей учетной записи git
  3. Проверить этот репозиторий
  4. Добавьте другую сторону файла для сравнения
  5. Отправьте контент на сервер
  6. Измените источник с оставшейся стороной
  7. Сравните свой контент с любым исходным файлом

В качестве (очень) быстрого и грязного подхода я сделал это в крайнем случае:

  1. Открыть Excel
  2. Вставьте файл 1 в столбец A, по одной строке на строку. Назовите диапазон "FILE1"
  3. Вставьте файл 2 в столбец B, по одной строке на строку. Назовите диапазон "FILE2"
  4. В C1 введите формулу:

    =IF(ISERROR(VLOOKUP(B1,FILE1,1,FALSE)),"DIFF","")
    
  5. В D1 введите форум:

    =IF(ISERROR(VLOOKUP(A1,FILE2,1,FALSE)),"DIFF","")
    
  6. Заполните столбцы C и D внизу файлов.

Это выделит все строки, которые появляются в одном файле, но не в другом файле. Это не опрятно, но иногда вам просто нужно работать с тем, что у вас есть.

Другие вопросы по тегам