Two SOAP responses look identical to the eye but one customer keeps reporting a mismatch. Two AndroidManifest.xml files differ by exactly one permission and a release build is broken. Two Spring config files survived a merge and you need to know whether anything semantic moved. Plain diff shows reordered attributes, reflowed whitespace, and reshuffled lines — all noise. A structural XML diff parses both documents into trees and compares the trees, not the bytes.
This tool parses each side with fast-xml-parser into a normalized object graph, then walks the trees in parallel and emits a path-keyed change list: + added, - removed, ~ changed. Attribute order and inter-tag whitespace disappear into the parser; namespace prefixes are preserved on the path. Everything runs in the browser.
How to use
- Paste the left (older) XML into the left pane.
- Paste the right (newer) XML into the right pane.
- Review the change list — paths are color-coded with
+, -, and ~. Attribute changes show up under an @_ path segment. - Toggle Hide unchanged to suppress the deep-equal parts of the tree and focus on real deltas.
- Copy any path or value with the inline copy icons to paste into a ticket, PR, or log line.
Structural diff vs textual diff — why the noise disappears
A textual diff treats files as line sequences. The four hardest categories of XML noise it cannot ignore:
| Noise category | Text diff | This tool (structural) |
|---|
| Indentation / whitespace between tags | Reports every changed line | Ignored |
| Attribute reordering | Whole line marked changed | Ignored (attributes are unordered) |
| Single-line vs multi-line element | Reports as add/remove | Identical if content matches |
| Comments & processing instructions | Reported | Dropped (semantically inert) |
| Real value change | Reported (mixed with noise) | Reported (only signal) |
The tradeoff: structural diff cannot tell you that a comment was reworded, because comments are dropped. If comment changes matter (e.g. a YAML/XML config where the comment is the documentation), use line-based Diff Viewer instead.
Namespaces and attribute ordering
XML namespaces are URLs bound to short prefixes. The path the tool reports preserves the prefix you used in the source:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<m:GetPrice xmlns:m="http://example.com/stock">
<m:Symbol>ACME</m:Symbol>
</m:GetPrice>
</soap:Body>
</soap:Envelope>
Path in diff: soap:Envelope.soap:Body.m:GetPrice.m:SymbolTwo important consequences:
- Prefix-only changes still count. If one side uses
soap: and the other uses s: but binds the same URL, the tool sees different paths. This is a known structural-diff limitation. For namespace-equivalence diffs, canonicalize both sides with xmllint --c14n first. - Default-namespace XML can surprise.
<root xmlns="http://x"> binds an empty prefix, so paths look unprefixed in the diff, but the elements still belong to that namespace.
Attribute ordering is ignored entirely. The XML spec says attributes are an unordered set; fast-xml-parser stores them in an object, which has no order semantics for the comparison.
Examples
Example 1: Price drop in a product feed
Same product, only the inner text of price changed. Attribute reordering on the price element is invisible.
Input
<!-- left -->
<product id="SKU-101">
<name>Mechanical Keyboard</name>
<price currency="USD">129.99</price>
</product>
<!-- right -->
<product id="SKU-101">
<name>Mechanical Keyboard</name>
<price currency="USD">119.99</price>
</product>
Output
~ product.price
129.99 -> 119.99Example 2: Config drift between staging and production
Connection pool grew in production, a log-level element was removed, an attribute was added. Attribute additions surface with the @_ path prefix.
Input
<!-- left (staging) -->
<config>
<pool size="10"/>
<logLevel>debug</logLevel>
</config>
<!-- right (prod) -->
<config>
<pool size="25" timeout="5000"/>
</config>
Output
~ config.pool.@_size
10 -> 25
+ config.pool.@_timeout
5000
- config.logLevel
debugExample 3: Repeated elements compared by index
Repeated book elements are arrays — items are compared by position. Inserting a new book in the middle shifts every subsequent index.
Input
<!-- left -->
<library>
<book id="1"/>
<book id="2"/>
</library>
<!-- right -->
<library>
<book id="1"/>
<book id="3"/>
<book id="2"/>
</library>
Output
~ library.book[1].@_id
2 -> 3
+ library.book[2]
{@_id: "2"}
# If you care about
# identity-based matching
# (book.id="2" is "the same"
# even if reordered), pre-sort
# both sides by the key.XML specification reference
Quick reference from W3C XML 1.0. The converter enforces these rules on input; anything outside them is rejected or normalized.
| Element | Meaning | Example |
|---|
| Prolog | Optional XML declaration | <?xml version="1.0"?> |
| Element | Opening + closing tag wrapping content | <p>hi</p> |
| Attribute | name="value" on an opening tag | <a href="x"> |
| CDATA | Literal text including markup characters | <![CDATA[<x/>]]> |
| Entity | Five predefined: < > & " ' | & |
Common invalid forms
<b><i>bold italic</b></i> // overlapping elements
<tag> // missing closing tag
<a href=x> // attribute value not quoted
Common pitfalls
- Mixed content collapses.
<p>hello <b>world</b>!</p> loses the ordering of text and child elements when parsed into a JS object — text becomes a#text key, child becomes b, the relationship between them is gone. Most config XML does not have mixed content; XHTML does. For HTML comparison use a DOM-based diff. - Whitespace inside elements is preserved. Inter-tag whitespace is dropped, but whitespace inside text content is kept.
<n>A</n> vs <n>A </n> shows up as a change. - Comment changes are invisible. If you rely on comments for configuration intent, pair the structural diff with a line diff to catch comment drift.
- CDATA sections are unwrapped.
<![CDATA[<x>]]> becomes the literal string <x> after parsing. Two semantically identical files that differ only in CDATA wrapping will diff equal. - Self-closing vs explicit-empty tags.
<br/> and <br></br> parse identically. The diff will treat them as the same.
This tool vs xmldiff, xmllint, and diff
diff -u left.xml right.xml: textual, fast, available everywhere. Use when whitespace and order changes are themselves the signal (e.g., reviewing a formatter change).xmldiff (Python): full XML tree diff with edit scripts (insert/delete/move). The CLI standard for structural diff. Use for batch reconciliation and CI pipelines where you want a machine-readable patch format.xmllint --c14n + diff: canonicalize first (sorts attributes, normalizes namespaces) and then text-diff. Cheap and effective for namespace-heavy XML. The poor man's structural diff.- This tool: zero install, browser-only, suited for ad-hoc comparisons during debugging. Output is human-friendly path notation rather than an edit script.
When NOT to use this
- HTML / XHTML with mixed content. Use a DOM-based diff like
diff-dom or browser DevTools. - Very large documents. The parser loads both files into memory. For multi-megabyte SOAP archives, use streaming tools (
xmldiff, diffxml) instead. - Schema validation. This tool reports structural deltas, not violations of an XSD or RelaxNG schema. Use
xmllint --schema for that. - Comment-sensitive configs. When the comments document intent, pair with Diff Viewer to also catch comment changes.
- Identity-based array matching.The tool compares repeated elements by index. For "is item with
id="42"still the same on both sides?", sort both sides by the key before diffing.
Troubleshooting
- Diff reports massive changes after a refactor that was a no-op.You probably reordered repeated child elements. Repeated children are compared by index. Sort both sides by a stable attribute first if the order does not matter.
- An attribute change does not appear. Check the path — attribute keys are prefixed with
@_. book.@_id means "the idattribute of book". - "Invalid XML" on input that opens fine in a browser.Browsers are lenient about prolog encoding. The parser is strict — strip BOM, ensure UTF-8, drop the
<?xml-stylesheet?> processing instruction if present. - The diff says nothing changed but the files look different.Differences are semantically inert: whitespace, attribute order, comments, CDATA wrapping. Switch to Diff Viewer for a textual comparison.
Frequently asked questions
How are attributes compared?
Attributes are modeled as keys prefixed with @_. Attribute changes show up as path entries like book.@_id. Order does not matter — two elements with the same set of attribute key/value pairs are equal regardless of source order.
Is element order significant?
For repeated elements (arrays of the same tag name), yes — items are compared by index. For distinct child elements, no — the order of name vs email as siblings is irrelevant. If your data has identity (book.id, user.uuid), pre-sort both sides by the key before diffing to make order changes invisible.
Is my XML uploaded?
No. Both documents are parsed and compared entirely in your browser usingfast-xml-parser. No network request is made when you click compare. Verify in DevTools' Network panel — the tab works offline.
Does it handle namespaces?
Yes, but the prefix is preserved literally in the path. If one document usessoap: and the other uses s: bound to the same URL, the tool will report different paths. For namespace-equivalence comparison, canonicalize both sides first with xmllint --c14n.
Does whitespace or attribute order affect the diff?
No. Inter-tag whitespace and attribute order are both normalized during parsing, so only semantically meaningful differences surface. Whitespace inside text content (between the opening and closing tag) is preserved — that is data, not formatting.
Why are my comments missing from the diff?
The parser drops comments before the tree comparison. XML comments are informational only — they do not affect the document's meaning to a consumer. If the comments are themselves the signal (a config file where comments document feature flags), pair this tool with line-based Diff Viewer.
Related guides
JSON vs YAML: When to Use What — A Developer's Guide
JSON wins on APIs; YAML wins on configs. Side-by-side syntax, parser behaviour, and where each fits across Kubernetes manifests, REST payloads, and GitHub Actions.
XML vs JSON: Differences, Use Cases, and Migration Guide
XML still runs banking, SOAP, EPUB, and OOXML; JSON owns everything else. Syntax differences, tooling, and a sober migration playbook for teams stuck on legacy XML.
YAML Configuration Files: Syntax, Best Practices, and Common Pitfalls
YAML whitespace is unforgiving and the Norway problem (no:) still bites. Types, anchors, aliases, and the rules that keep Kubernetes manifests, GitHub Actions, and Helm values readable.