XML tools
Compare XML Records in Python by a Unique ID
Compare two XML exports by record ID in Python, detect duplicate keys, and separate additions, removals, and changed values without losing records.
Match records using the data contract
When two XML exports list the same records in different orders, comparing children by position can produce a noisy report. If the format guarantees a unique record identifier, compare records by that key instead. This is an application-level comparison, so the key and included fields must come from the export contract.
The example below uses a small inventory format with an items root and direct item children. Each item has one id attribute and one qty child. Record order is deliberately ignored, but duplicate identifiers, missing values and unexpected structure are rejected rather than silently discarded.
Validate the shape before building a dictionary
import xml.etree.ElementTree as ET
def inventory(text):
root = ET.fromstring(text)
if root.tag != "items" or root.attrib:
raise ValueError("Expected an items root without attributes")
if (root.text or "").strip():
raise ValueError("Unexpected root text")
records = {}
for item in root:
children = list(item)
if (item.tag != "item" or set(item.attrib) != {"id"}
or len(children) != 1 or children[0].tag != "qty"):
raise ValueError("Expected item with id and one qty")
key = item.attrib["id"]
qty = children[0]
if not key or key in records:
raise ValueError("Missing or duplicate item ID")
if (qty.attrib or len(qty) or (item.text or "").strip()
or (item.tail or "").strip() or (qty.tail or "").strip()):
raise ValueError("Unexpected inventory content")
value = qty.text or ""
if not value.isascii() or not value.isdecimal():
raise ValueError("Quantity must contain ASCII digits")
records[key] = int(value)
return records
def compare_inventory(left, right):
a, b = inventory(left), inventory(right)
return {
"added": sorted(b.keys() - a.keys()),
"removed": sorted(a.keys() - b.keys()),
"changed": {key: [a[key], b[key]] for key in sorted(a.keys() & b.keys())
if a[key] != b[key]},
}
before = '<items><item id="A"><qty>2</qty></item></items>'
after = '<items><item id="A"><qty>3</qty></item></items>'
print(compare_inventory(before, after))
The changed mapping contains A with the values 2 and 3. The lists of added and removed identifiers are empty. Reordering two valid items leaves the result unchanged because position is not part of this inventory's comparison rule.
Reject duplicate identifiers before assigning values
A dictionary assignment overwrites an existing value for the same key. Without the explicit duplicate check, two A records could become one and hide a malformed export. A result based on that overwritten dictionary would look tidy while losing evidence.
Keep identifiers as strings. An ID of 001 may differ from 1 even though both can be converted to the same integer. Quantities have a different rule here: they are nonnegative whole counts, so converting accepted ASCII digits to an integer makes 02 and 2 equal by design.
Keep the accepted format narrow and visible
This sample rejects namespaces and extra attributes because they are outside its stated inventory format. For a real namespaced export, use the exact expanded names from that format and extend the validation deliberately. Do not simply remove prefixes from all names; two namespaces can define different elements with the same local spelling.
Standard ElementTree parsing omits comments and processing instructions. The record report therefore concerns accepted inventory values, not exact source identity. If an import contract also cares about signatures, comments or file bytes, perform those checks separately.
Test a new record, a removed record, a changed quantity, reordered records, duplicate IDs, an empty quantity and an unexpected child. A malformed XML document should raise a parser error; an invalid inventory should raise a validation error. Neither should produce an empty differences report.
The ElementTree documentation is the reference for parsing. Use the interactive XML comparison tool to inspect unfamiliar structure first, then encode only the business rules your importer actually requires.


