Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Metadata Change Detection for Document Versions

Product Page Docs Blog Free Support Temporary License

πŸš€ Quick Start

document-version-metadata-diff-python is a runnable Python demo that answers one question fast: what changed in a document's metadata between two revisions? Install groupdocs-metadata-net==26.5, run python main.py, and the seeded pair of DOCX revisions produces a classified property diff, two forensic reports, and JSON plus CSV exports, each step asserted with a PASS line.

✨ What You'll Learn

  • Flatten a file's complete property tree into one Python dict with a single call
  • Classify metadata differences into added, removed, and changed with both values kept
  • Isolate ownership signals (Creator, Editor, Manager, Company) using tag predicates
  • Isolate the editing timeline: revision counters, editing time, created/modified/printed stamps
  • Ship findings as a stable JSON schema and a four-column CSV

πŸ“‹ Table of Contents

πŸ“– About This Repository

This repository shows metadata version comparison end to end using GroupDocs.Metadata for Python via .NET. The library reads built-in fields, custom properties, and XMP through one find_properties call across 170+ formats, per the product documentation, and its tag system classifies identity and time properties without format-specific names. The examples target engineers who need change detection inside document workflows, from automated intake to dispute support. Everything downstream of the API stays in plain dicts, so the audit logic reads like ordinary Python. I keep this project around as my smoke test after library upgrades; if the six asserts pass, the metadata layer behaves.

πŸ”‘ Key Features

GroupDocs.Metadata Capabilities

Feature Description
Whole-tree search find_properties walks every metadata package behind one predicate
Tag classification Tags.person, Tags.corporate, and Tags.time mark properties by meaning
Interpreted values dates and enumerations arrive human-readable, not as raw serials
Format breadth the same calls serve DOCX, PDF, XLSX, images, and audio
Write support properties can be updated or removed with the same API family

What This Repository Demonstrates

A complete two-revision diff pipeline in six small functions. Tag-driven detectors handle the ownership and revision questions, audit-grade exports feed dashboards (JSON) and spreadsheets or SIEMs (CSV), and a self-asserting main.py verifies every step on the seeded sample pair.

βš™οΈ Prerequisites

You need Python 3 (any actively supported CPython with pip) and the package: pip install groupdocs-metadata-net==26.5. A license is optional. Evaluation mode runs everything here, and setting LICENSE_PATH in main.py lifts the evaluation limits.

πŸ“ Repository Structure

document-version-metadata-diff-python/
β”‚
β”œβ”€β”€ main.py
β”œβ”€β”€ requirements.txt
β”œβ”€β”€ methods/
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ compare_metadata_sets.py
β”‚   β”œβ”€β”€ detect_ownership_changes.py
β”‚   β”œβ”€β”€ detect_revision_history.py
β”‚   β”œβ”€β”€ export_diff_to_csv.py
β”‚   β”œβ”€β”€ export_diff_to_json.py
β”‚   └── extract_all_metadata.py
└── resources/
    β”œβ”€β”€ document-v1.docx
    └── document-v2.docx

File Overview

  • main.py – drives all six functions against the sample pair and asserts each result
  • requirements.txt – pins groupdocs-metadata-net==26.5
  • methods/extract_all_metadata.py – property tree to dict
  • methods/compare_metadata_sets.py – the MetadataDiff builder
  • methods/detect_ownership_changes.py – identity-change detector
  • methods/detect_revision_history.py – editing-timeline detector
  • methods/export_diff_to_json.py / export_diff_to_csv.py – report writers
  • resources/ – seeded document-v1.docx and document-v2.docx

πŸ’» Implementation Examples

Example 1: Extracts every accessible metadata property

The foundation call. One pass collects built-in, custom, and XMP properties keyed by qualified name, preferring interpreted_value so the dict is readable as-is.

result = {}
with Metadata(document_path) as metadata:
    for prop in metadata.find_properties(lambda p: p.name is not None):
        key = prop.name
        value = (str(prop.interpreted_value) if prop.interpreted_value is not None
                 else (str(prop.value) if prop.value is not None else ""))
        result[key] = value
return result

What this example shows:

find_properties with a name predicate is the whole extraction story; there is no per-layer code. The returned dict is what every later function consumes, and its size on the sample files is asserted to be non-zero by main.py.


Example 2: Compares metadata between two document versions

The core diff. Both revisions go through the extractor, then set logic fills a MetadataDiff value object with three maps.

v1 = extract_all_metadata(path_v1)
v2 = extract_all_metadata(path_v2)
diff = MetadataDiff()

for k, v in v2.items():
    if k not in v1:
        diff.added[k] = v
    elif v1[k] != v:
        diff.changed[k] = (v1[k], v)

for k, v in v1.items():
    if k not in v2:
        diff.removed[k] = v

return diff

What this example shows:

Changed entries keep old and new values as a pair, so the result is a finding rather than a hint. total_changes on the object sums all three maps for quick thresholds.


Example 3: Detects ownership and authorship changes

The identity question, answered directly. The module-level _read_ownership helper collects only properties tagged as person or company, and the delta loop reports differences with <missing> marking one-sided fields.

v1 = _read_ownership(path_v1)
v2 = _read_ownership(path_v2)
all_keys = set(v1.keys()) | set(v2.keys())
changes = {}
for k in all_keys:
    old_v = v1.get(k, "<missing>")
    new_v = v2.get(k, "<missing>")
    if old_v != new_v:
        changes[k] = (old_v, new_v)
return changes

What this example shows:

Tag predicates make the detector format-independent: no property is named, yet Creator, LastSavedBy, Manager, and Company are all covered. A disappeared identity field surfaces just as loudly as a changed one.


Example 4: Detects revision-history and editing-time changes

The timing question. _read_revision mixes Tags.time predicates with name rules for Revision and EditTime counters, catching classified timestamps and counter fields together.

v1 = _read_revision(path_v1)
v2 = _read_revision(path_v2)
all_keys = set(v1.keys()) | set(v2.keys())
changes = {}
for k in all_keys:
    old_v = v1.get(k, "<missing>")
    new_v = v2.get(k, "<missing>")
    if old_v != new_v:
        changes[k] = (old_v, new_v)
return changes

What this example shows:

RevisionNumber, TotalEditingTime, and LastPrinted move with every editing session even when the visible text is untouched. This detector surfaces that invisible activity as from/to pairs.


Example 5: Exports the diff as a JSON audit report

Machine-facing output. The three maps serialize into a stable schema, with changed entries expanded into from/to objects.

payload = {
    "added": diff.added,
    "removed": diff.removed,
    "changed": {k: {"from": v[0], "to": v[1]} for k, v in diff.changed.items()},
}
with open(output_path, "w", encoding="utf-8") as f:
    json.dump(payload, f, indent=2, ensure_ascii=False)

What this example shows:

A dashboard or case-management API ingests diff.json without transformation, and because the schema never varies, reports from different runs line up into a timeline.


Example 6: Exports the diff as a CSV audit report

People-facing output. Four columns, one row per change, ready for Excel or a SIEM.

with open(output_path, "w", encoding="utf-8", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["change_type", "property", "old_value", "new_value"])
    for k, v in diff.added.items():
        writer.writerow(["added", k, "", v])
    for k, v in diff.removed.items():
        writer.writerow(["removed", k, v, ""])
    for k, (old_v, new_v) in diff.changed.items():
        writer.writerow(["changed", k, old_v, new_v])

What this example shows:

The flattening rule is fixed: added rows leave old_value empty, removed rows leave new_value empty, changed rows carry both. No parsing code needed on the receiving end.

Which properties count as ownership signals?

Four tag groups: Tags.person.creator covers Author and LastSavedBy, Tags.person.editor covers the last editor, Tags.person.manager maps to Manager, and Tags.corporate.company maps to Company. The detector reports any of them whose value differs between versions, including fields present on only one side. Everything else, timestamps included, belongs to the revision detector instead. That split keeps each report readable.

πŸ“š Related Resources

Explore these additional resources to deepen your understanding of metadata version comparison:

  • Step-by-step use case guide in the documentation – the same pipeline as a tutorial series: Read the guide β†’

  • In-depth blog article about this project – the quick-start walkthrough built on this repo: Read the article β†’

  • How to Compare Document Metadata Between Versions in Java – the same audit approach on the Java platform: Read the article β†’

  • Edit Metadata in Python Applications – the wider read/update/remove API surface: Read the article β†’

  • Best Practices in Metadata Management – the policy context around property hygiene: Read the article β†’

🏷️ Keywords

document metadata diff, compare metadata versions, python metadata comparison, detect authorship changes, revision number tracking, TotalEditingTime, LastPrinted, document forensics python, e-discovery metadata, metadata audit report, csv audit export, json diff schema, groupdocs metadata python, python via .net, find_properties, metadata tags, docx properties, document version control, compliance snapshot, metadata tampering detection


About

Diff every metadata property between two document versions in Python. GroupDocs.Metadata powers ownership checks, revision forensics, and CSV/JSON audit exports.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages