Why compare XML as data
Two XML files that carry the same data rarely print the same way. One tool writes attributes in the order they were set and another sorts them; one indents with two spaces and another with four; one writes <policy/> and another <policy></policy>. A line diff reports all of it, and in a long POM or configuration file the one edit that matters is buried.
This page rewrites both files into one canonical form first: attributes sorted by name, one element per line, indentation dropped and runs of spaces inside text collapsed. Then it compares those lines and names each change by its path, such as /project/dependencies/dependency[3]/version, down to the attribute when a single one changed. It opens on two versions of a Log4j2 configuration that differ in layout on almost every line and in substance on three.
What it reports
Attribute order and layout
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" maxThreads="200"/>
<Connector
maxThreads="200"
SSLEnabled="true"
protocol="HTTP/1.1"
port="8443"></Connector>No changes. The attributes are in another order and spread over several lines, and the empty element is written with a closing tag, but it is the same element with the same values.
One attribute in a long element
<Loggers> <Logger name="com.example.payments" level="info" additivity="false"/> </Loggers>
<Loggers>
<Logger additivity="false" level="debug" name="com.example.payments"/>
</Loggers>One change, at /Loggers/Logger/@level: info to debug. The attributes around it moved and the line was re-indented, and neither is reported.
Repeated elements numbered
<dependencies>
<dependency>
<artifactId>spring-web</artifactId>
<version>6.1.4</version>
</dependency>
<dependency>
<artifactId>postgresql</artifactId>
<version>42.7.3</version>
</dependency>
</dependencies><dependencies>
<dependency>
<artifactId>spring-web</artifactId>
<version>6.1.4</version>
</dependency>
<dependency>
<artifactId>postgresql</artifactId>
<version>42.7.4</version>
</dependency>
</dependencies>One change, at /dependencies/dependency[2]/version. Elements with the same name are numbered in document order, so the path says which of them changed.
Options
- Canonical form
- Always on when comparing as XML: attributes sorted by name, whitespace between elements dropped, runs of spaces inside text collapsed to one, single and double quotes treated alike, and
<a/>read the same as<a></a>. The XML declaration is not compared. - Noise
- A timestamp, UUID, hash or request ID that differs, in an attribute or in text, is labelled noise and counted apart, so two exports taken minutes apart do not look changed.
- Back to line by line
- Compares the two files as written, so indentation and attribute order count again. Use it when the layout itself is under review, and the whitespace and case options apply there.
- Open or drop a file
- Each side takes a file of up to 5 MB, opened with its button or dropped onto the box. The file is read in your browser.
In a terminal
diff -u <(xmllint --format old.xml) <(xmllint --format new.xml)
Re-indents both files and writes empty elements one way, so layout stops counting. Attribute order still counts: on this page's sample the reordered attributes of
ConfigurationandConsoleshow as changed lines.diff -u <(xmllint --c14n --noblanks old.xml | xmllint --format -) <(xmllint --c14n --noblanks new.xml | xmllint --format -)
Canonical XML (
--c14n) puts attributes in a fixed order and--noblanksdrops the old indentation first. On this page's sample what is left is the three real changes: the log file name, the logger's level and the addedAppenderRef.
- What the page does that these do not
- It reports lines, not paths: the page names
/Configuration/Loggers/Logger/@levelas the change, while here the attribute has to be found by reading the changed line. - What the terminal does better
- xmllint exits with 1 on a file that is not well formed, so a build can run it as a check, and the second command took about a second on two files of 10 and 12 MB.
xmllint comes with libxml2 and is built into macOS.
Questions
Does attribute order matter in XML?
No. The XML specification says the order of attributes in a start tag is not significant, so this page sorts them before comparing. <a x="1" y="2"/> and <a y="2" x="1"/> are the same element.
Does element order matter?
Yes. XML keeps the order of elements and many formats rely on it, so an element that moved is reported as removed in one place and added in another.
Are comments compared?
Yes. A comment that was added, removed or reworded is reported, because in a configuration file a comment often explains the setting under it.
What about namespaces and entities?
They are compared as written. A prefix change such as x:item to y:item is reported even when both prefixes name the same namespace, and & and & count as different text. Entities are not expanded and DTDs are not fetched, so a file cannot make the page load anything.
What if a file is not well-formed?
The page says so and compares the two as plain text. A missing closing tag is the usual cause. Fix it and the comparison by element comes back on its own.