💡 Esempio completo funzionante disponibile su GitHub:
document-version-metadata-diff-python

Cosa Costruirai

In questa guida confronterai ogni proprietà dei metadati tra due versioni di un documento e stamperai esattamente ciò che è stato aggiunto, rimosso o modificato. Un confronto di versione dei metadati è un confronto a livello di proprietà di due revisioni di uno stesso file e cattura segnali che un confronto di testo non vede mai: un nuovo Creator, un RevisionNumber incrementato, una sessione di editing registrata dopo la chiusura della revisione. Alla fine avrai una soluzione funzionante più due rilevatori mirati e due formati di esportazione, tutti tratti da un repository eseguibile fornito con una coppia di revisioni di esempio.

Livello di abilità: sviluppatore Python intermedio
Cosa ti serve: Python 3, pip e due revisioni di uno stesso documento

La mia prima esecuzione di questo script ha segnalato una modifica al valore Company che nessuno del team ricordava di aver effettuato; quella singola riga ha giustificato l’intera configurazione. Tutto quanto segue è pronto per il copia‑incolla e conta ben meno di cento righe.

Il flusso è deliberatamente noioso: due aperture di file, tre comprensioni di dizionario, un ciclo di stampa. La noia è l’obiettivo. Le controversie sulle versioni si risolvono in base a quanto il metodo può essere spiegato e ripetuto, e uno script così piccolo può essere letto nella sua interezza da chiunque metta in dubbio il risultato.


1. Installazione

pip install groupdocs-metadata-net==26.5

Il repository di accompagnamento fissa questa versione e fornisce document-v1.docx e document-v2.docx così il codice qui sotto funziona così com’è. Blocca la versione con cui è stato eseguito il tuo audit; la riproducibilità è parte della prova.


2. Il Codice Principale

Leggi entrambi gli alberi di proprietà, poi classifica il delta con la logica degli insiemi. Questo è l’intero confronto:

# Flatten a file's complete property tree into a dict
def read_props(path):
    props = {}
    with Metadata(path) as metadata:
        for p in metadata.find_properties(lambda p: p.name is not None):
            props[p.name] = (str(p.interpreted_value) if p.interpreted_value is not None
                             else (str(p.value) if p.value is not None else ""))
    return props

v1 = read_props("resources/document-v1.docx")
v2 = read_props("resources/document-v2.docx")

# Classify every key; changed entries keep both values
added = {k: v for k, v in v2.items() if k not in v1}
removed = {k: v for k, v in v1.items() if k not in v2}
changed = {k: (v1[k], v2[k]) for k in v1 if k in v2 and v1[k] != v2[k]}

print(f"added={len(added)} removed={len(removed)} changed={len(changed)}")
for k, (old_v, new_v) in changed.items():
    print(f"  {k}: {old_v} -> {new_v}")

Questo è il minimo indispensabile. Aspettati conteggi ridotti su coppie di revisioni genuine; un delta di decine di elementi di solito indica che il file è passato attraverso una modifica di modello o una migrazione di archiviazione. Le sezioni successive spiegano le chiamate chiave e mostrano le personalizzazioni che la maggior parte dei team aggiunge per prime.


3. Come Funziona

  • Metadata: il context manager che apre un file e lo rilascia all’uscita; un’istanza per revisione.
  • find_properties: attraversa campi integrati, proprietà personalizzate e XMP in un unico passaggio, restituendo tutto ciò che il predicato accetta.
  • interpreted_value: la forma leggibile da un umano di una proprietà; preferirla significa che date ed enumerazioni vengono confrontate come stringhe stampabili in un report.
  • Nomi qualificati come chiavi: i campi integrati e personalizzati non possono collidere nel dizionario, quindi la logica degli insiemi rimane sicura.

Qui non si analizzano strutture DOCX. La documentazione del prodotto elenca oltre 170 formati supportati dalla stessa chiamata, quindi lo stesso script confronta coppie PDF o XLSX.

Un altro aspetto degno di nota è il confine dell’API: termina alle due chiamate read_props. Tutto ciò che segue è Python della libreria standard, quindi test unitari, soglie e regole di avviso non toccano lo strato documento. I team che lo incapsulano in un servizio solitamente memorizzano nella cache i dizionari estratti per revisione e consentono a tutti i controlli a valle di riutilizzarli, mantenendo un’unica apertura di file per versione indipendentemente dal numero di domande poste.


4. Personalizzazioni Comuni

Rileva solo i cambi di proprietà

Quando la domanda è “chi ha toccato questo file”, filtra al momento della lettura con predicati sui tag invece di filtrare il diff completo dopo:

# Identity fields only, whatever the format calls them
def read_ownership(path):
    result = {}
    with Metadata(path) as metadata:
        props = metadata.find_properties(lambda p:
            Tags.person.creator in list(p.tags)
            or Tags.person.editor in list(p.tags)
            or Tags.person.manager in list(p.tags)
            or Tags.corporate.company in list(p.tags))
        for prop in props:
            result[prop.name] = (str(prop.interpreted_value)
                                 if prop.interpreted_value is not None
                                 else (str(prop.value) if prop.value is not None else ""))
    return result

Esegui lo stesso ciclo di delta su due di questi dizionari, usando <missing> come valore predefinito così un campo scomparso viene comunque segnalato. I nomi dei predicati non includono alcun campo, il che permette a un singolo rilevatore di servire tutti i formati letti dalla libreria.

Traccia la cronologia di modifica

Sostituisci il predicato con Tags.time più regole sui nomi dei contatori e il rilevatore segnala RevisionNumber, TotalEditingTime e variazioni di LastPrinted:

props = metadata.find_properties(lambda p:
    Tags.time.modified in list(p.tags)
    or Tags.time.created in list(p.tags)
    or Tags.time.printed in list(p.tags)
    or (p.name is not None and ("Revision" in p.name
        or "EditTime" in p.name or "EditingTime" in p.name)))

Esporta un rapporto di audit

Le scoperte che rimangono nella console muoiono lì. Quattro colonne coprono il foglio di calcolo e il caso SIEM:

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

Il repository include anche un esportatore JSON con uno schema a tre mappe stabile per dashboard e API di gestione dei casi.


Dove Questo Viene Eseguito in Pratica

Tre tipologie di distribuzione ricompaiono spesso. Le pipeline di ingestione confrontano ogni documento in arrivo con la copia già in archivio e mettono in quarantena le coppie con cambi di identità. I job di conformità eseguono il confronto su base programmata e archiviano il CSV per coppia, costruendo una cronologia delle proprietà che nessuno deve ricostruire in seguito. Gli strumenti di disputa eseguono entrambi i rilevatori su richiesta, perché quando arriva una contestazione la domanda iniziale è sempre “chi ha toccato il file e quando”, non “cosa è cambiato al paragrafo quattro”.

Un quarto modello, confrontare un file con il proprio ultimo snapshot considerato buono, riutilizza lo stesso codice con un dizionario memorizzato da un lato. In tutti i casi, il file di esportazione è il risultato consegnabile; l’output della console è solo rumore di avanzamento. Il pattern di exit‑code dello script segue quello del main.py del repository, così scheduler e CI trattano un’asserzione fallita come un’esecuzione fallita senza ulteriori configurazioni. Nessuno di questi scenari ha richiesto codice oltre a quanto mostrato in questa pagina.


Cosa Conta Come una Modifica da Segnalare?

Qualsiasi elemento classificato dal diff più il contesto che aggiungi. Le proprietà aggiunte e rimosse meritano sempre un’occhiata perché indicano una modifica della struttura più che un semplice valore. Per le voci modificate, la maggior parte dei team avvisa prima sui gruppi di identità e revisione e tratta il resto come informativo. I rilevatori esistono proprio per far sì che il primo passaggio costi una sola chiamata di funzione.


5. Riferimento Rapido: Chiamate Chiave

Chiamata Cosa Fa
Metadata(path) Apre il file; il context manager gestisce il rilascio
find_properties(predicate) Restituisce ogni proprietà accettata dal predicato, su tutti i livelli
p.interpreted_value Valore leggibile; se assente ricade su p.value
Tags.person.* / Tags.corporate.company Classificazione dell’identità, indipendente dal formato
Tags.time.* Classificazione dei timestamp per il rilevatore di revisione

Vedi il riferimento API completo per l’intera superficie di ricerca e tagging. Il vocabolario dei tag è più ampio di queste righe; i gruppi di origine, contenuto e legale seguono lo stesso test di appartenenza.


6. Problemi Comuni e Correzioni

Il diff è enorme e sembra solo rumore
→ Probabilmente i due percorsi non sono revisioni dello stesso documento. Correggi: verifica la provenienza prima del confronto; file non correlati producono delta senza senso.

Un campo autore noto non compare nel rilevatore di proprietà
→ Alcuni produttori memorizzano l’identità in campi personalizzati non taggati. Correggi: esegui il diff completo una volta, individua il nome reale del campo e amplia il predicato con una regola sul nome.

La console mostra un avviso di modalità valutazione
→ Nessun file di licenza è stato trovato. Correggi: imposta LICENSE_PATH in main.py verso il tuo file .lic, oppure mantieni la modalità valutazione per sviluppo; la logica è identica.

Le date vengono stampate come numeri seriali grezzi
→ Il valore grezzo p.value è sfuggito a qualche lettore. Correggi: mantieni il pattern interpreted_value‑first in read_props; è il motivo per cui i report rimangono leggibili.


Prossimi Passi

Hai un diff di metadati funzionante. Ecco dove puoi andare da qui:

  • Batch: fai un ciclo sullo script per coppie di documenti e salva il CSV per ciascuna; il costo per coppia è due aperture di file, e i CSV si concatenano facilmente per una vista a livello di libreria.
  • Schedula: il main.py del repository verifica ogni passaggio e restituisce un exit code corretto, pronto per CI o un pianificatore.
  • Segui il tutorial versionato: la guida ai casi d’uso costruisce la stessa pipeline in tre tutorial graduali.
  • Vedi l’intero progetto: document-version-metadata-diff-python con la coppia di revisioni di esempio.

Risorse