Open source command line tool for Linux to diff XML files ignoring element order Open source command line tool for Linux to diff XML files ignoring element order xml xml

Open source command line tool for Linux to diff XML files ignoring element order


I had a similar problem and I eventually found: https://superuser.com/questions/79920/how-can-i-diff-two-xml-files

That post suggests doing a canonical xml sort then doing a diff. Being that you are on linux, this should work for you cleanly. It worked for me on my mac, and should work for people on windows if they have something like cygwin installed:

$ xmllint --c14n a.xml > sortedA.xml$ xmllint --c14n b.xml > sortedB.xml$ diff sortedA.xml sortedB.xml


You're requesting a sort based on the sequence of attributes in the elements being sorted. But your top-level tag elements here have only one attribute: name. If you want multiple tag elements with name="BBB" to sort differently, you need to give them distinct sort keys.

In your example, I'd try something like select="concat(name(), @name, name(*[1]), *[1]/@name)" -- but this is a very shallow key. It uses values from the first child in the input, but the children may shift position during the process. You may be able (knowing your data better than I do) to calculate a good key for each element in a single pass, or you may just need several passes.


First your XML examples are not valid, because they lack a root element. I added a root element. This is a.xml:

<?xml version="1.0" encoding="UTF-8"?><root>    <tag name="AAA">        <attr name="b" value="1"/>        <attr name="c" value="2"/>        <attr name="a" value="3"/>    </tag>    <tag name="BBB">        <attr name="x" value="111"/>        <attr name="z" value="222"/>    </tag>    <tag name="BBB">        <attr name="x" value="333"/>        <attr name="z" value="444"/>    </tag></root>

And this is b.xml:

<?xml version="1.0" encoding="UTF-8"?><root>    <tag name="AAA">        <attr name="a" value="3"/>        <attr name="b" value="1"/>        <attr name="c" value="2"/>    </tag>    <tag name="BBB">        <attr name="z" value="444"/>        <attr name="x" value="333"/>    </tag>    <tag name="BBB">        <attr name="x" value="111"/>        <attr name="z" value="222"/>    </tag></root>

You can create a canonical form for the comparison by merging the siblings with the same name attribute and sorting by the tag name and the value.

In order to merge the sibling elements with the same name you have to ignore the elements which name is the same like a preceding sibling and take the remaining. This can be done on the second element level by the following Xpath:

*[not(@name = preceding-sibling::*/@name)]

You have to take the name of those elements in order to select all the child elements which have a parent with this name. After that you have to sort by name and value. This makes it possible to transform both files into this canonical form:

<?xml version="1.0" encoding="WINDOWS-1252"?><root>    <tag name="AAA">        <attr name="a" value="3"/>        <attr name="b" value="1"/>        <attr name="c" value="2"/>    </tag>    <tag name="BBB">        <attr name="x" value="111"/>        <attr name="x" value="333"/>        <attr name="z" value="222"/>        <attr name="z" value="444"/>    </tag></root>

This will do the transformation:

<?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" encoding="WINDOWS-1252" omit-xml-declaration="no" indent="yes"/>    <xsl:strip-space elements="*"/>    <xsl:template match="/root">        <xsl:copy>                <xsl:copy-of select="@*"/>                <xsl:for-each select="*[not(@name = preceding-sibling::*/@name)]">                    <xsl:variable name="name" select="@name"/>                    <xsl:copy>                        <xsl:copy-of select="@*"/>                        <xsl:for-each select="../*[@name = $name]/*">                            <xsl:sort select="@name"/>                            <xsl:sort select="@value"/>                            <xsl:copy>                                <xsl:copy-of select="@*"/>                            </xsl:copy>                        </xsl:for-each>                    </xsl:copy>                </xsl:for-each>        </xsl:copy>    </xsl:template></xsl:stylesheet>