Metadata version comparison is a GroupDocs.Metadata capability for Python via .NET that reads every property of two document revisions and reports exactly what was added, removed, or changed between them. Properties like Creator, RevisionNumber, and the last-printed timestamp never render on the page, yet they are the first thing an auditor asks about β and reading them by hand means a new code branch per format. This guide dissects a compact pipeline instead: one extraction call flattens each revision’s property tree, a pure-Python diff classifies the result, two tag-driven detectors isolate identity and activity signals, and two exporters serialize the verdict. Every code block comes from the runnable repository above, which ships with two sample DOCX revisions and asserts all six functions on every run. The audience is engineers building compliance tooling who need evidence-grade output rather than a visual compare.
Warning
For Production Use: run the complete implementation against your own document corpus before deployment.
What This Guide Covers
One question: given two versions of the same file, what changed about it, and can you prove it? For audit-trail, retention, and tampering work β not body-text comparison, which is a different problem.
Prerequisites:
Python 3 with pip; the demo pins groupdocs-metadata-net==26.5 in requirements.txt
A GroupDocs license file (optional β without one the demo runs in evaluation mode and says so)
Installation
pip install groupdocs-metadata-net==26.5
Point LICENSE_PATH in main.py at your .lic file, then run python main.py. It diffs the two bundled DOCX revisions, writes output/diff.json and output/diff.csv, and prints ALL PASS when all six stages hold.
Only this component understands file formats, and it delegates that to the Metadata class: one find_properties call walks built-in, custom, and format-specific packages in a single pass, flattened into a dict keyed by qualified name. interpreted_value makes dates and enumerations comparable strings, and nothing here names a format β see Extracting metadata for the mechanism.
Design Pattern: Adapter over format-specific packages
Concurrency Model: Synchronous, single file handle
State Management: Stateless
Cost is dominated by opening the file; body content is never rendered. Scaling is linear and embarrassingly parallel β a pool over 10,000 documents shares no state, and storage latency, not CPU, is the bottleneck.
The diff core never touches a file: both revisions become nameβvalue maps, classified into three buckets on a MetadataDiff value object β added, removed, changed (old/new pairs) β with total_changes as a one-number verdict. A structured object instead of a report string is the decision the rest of the pipeline leans on.
Design Pattern: Value object with set-classification
Concurrency Model: Pure in-memory computation
Two dictionary scans over a few hundred entries β unmeasurable next to the file reads. In a batch service, expose this component: two paths in, one serialized diff out.
Component 3: Tag-Filtered Forensic Detectors
Design Overview
The detectors answer what auditors ask first: who touched the file, and when. detect_ownership_changes narrows to identity properties via tag predicates β Tags.person.creator, Tags.person.editor, Tags.person.manager, Tags.corporate.company β while detect_revision_history targets time tags plus name patterns for revision counters. No format-specific key like dc:creator appears in the code; the tag finds the right property per format, as covered in Find metadata properties. A <missing> sentinel separates a deleted property from a blanked one.
Design Pattern: Predicate-filtered projection
Concurrency Model: Synchronous, one filtered read per file
Each detector re-opens its files β the trade-off for standalone functions. At audit scale, extract once per revision and filter the in-memory dicts instead.
Component 4: Audit Export Layer
Design Overview
Two serializers, one MetadataDiff. JSON keeps the three buckets as top-level maps with explicit from/to fields β stable enough to diff across audit runs. CSV flattens to one row per change with four fixed columns for Excel, warehouse jobs, or a SIEM. Both use only the standard library.
Monitoring: a corpus-wide total_changes spike usually means a bulk process rewrote properties.
Error Handling:main.py asserts every stage and exits non-zero β a ready-made CI smoke test.
Resilience: exports are idempotent; re-runs overwrite the same two files.
How do I prove who changed a document between two versions?
Run both revisions through detect_ownership_changes and detect_revision_history, then export the combined diff. The first reports every identity property whose value differs β Creator, Editor, Manager, Company β with old and new values side by side; the second shows the activity trail: revision counters, editing time, created, modified, and printed timestamps. Together they answer who and when in two function calls, with output you can attach to a case file.
When to Use This Approach
Reach for a metadata diff when the dispute is about the file, not the words in it: chain-of-custody checks, retention audits, contested authorship, or a screen before full e-discovery review. It also wins on volume β no rendering, so a folder of contracts screens in minutes. Use GroupDocs.Comparison for paragraph-level content changes, and both when a claim needs content and property evidence to line up. I default to the metadata pass first; its verdict tells you whether the expensive content review is needed at all.
Common Pitfalls
Comparing raw values instead of interpreted_value β raw objects differ across formats; the string projection lines up.
Hard-coding property names β Author in one format is dc:creator in another; tag predicates survive both.
Treating “missing” as “empty” β a removed property and a blanked one are different findings; hence the sentinel.
Forgetting newline="" in the CSV writer β without it every row on Windows gains a blank line.
Concluding from evaluation mode β unlicensed runs limit some properties; license before attaching output to a finding.
Security and Compliance
The pipeline only reads its inputs β originals stay untouched and defensible. Store the exports under access control and hash them if they enter a legal hold. For the reverse obligation β stripping properties before release β the same search engine drives the removing metadata workflow.
FAQ
Does this work for formats other than DOCX?
Yes β nothing in the pipeline names a format. The Metadata constructor detects the type, and the product documentation lists 170+ formats including PDF, XLSX, PPTX, images, and audio. The samples are DOCX because Office files carry the richest built-in property sets.
Why do the detectors re-open files instead of reusing the extraction?
Each function stands alone so it can be lifted into another codebase, at the cost of two extra file opens. At scale, extract once per revision and filter the in-memory dicts. The classification logic stays identical.
Can I diff two arbitrary documents rather than two versions of one file?
Mechanically yes β the diff has no notion of lineage. But unrelated files report nearly everything as added or changed, so the output only means something for related inputs. Ground the “v1”/“v2” labels in version-control or DMS history first.
Conclusion and Recommendations
Six small functions cover the audit path: flatten, diff, filter for identity, filter for activity, serialize twice. The decisions that make it hold up are unglamorous β interpreted values, a structured diff object, tag predicates, an explicit missing sentinel. Before deployment, review the complete source code, extract once per file at batch scale, and store exports with the same controls as the documents they describe.