💡 Esempio completo funzionante disponibile su GitHub:
metadata-diff-between-docs-using-groupdocs-metadata-dotnet
Introduzione
Quando un contratto cambia mano durante una fusione, il team legale deve dimostrare che le informazioni di proprietà del documento – autore, ultimo editore, nome dell’azienda – sono rimaste intatte. L’ispezione manuale delle proprietà di ogni file è noiosa e soggetta a errori; una singola modifica non rilevata può invalidare un audit di conformità. GroupDocs.Metadata è una libreria .NET che estrae e confronta ogni proprietà incorporata di un documento, consentendo un’analisi forense automatizzata delle modifiche da una versione all’altra. Questo tutorial mostra come rilevare scambi di proprietà, modifiche alla cronologia delle revisioni e qualsiasi altra modifica di proprietà tra due versioni dello stesso file, per poi esportare i risultati in CSV o JSON per report successivi.
Ho incontrato questo problema durante l’audit di un set di 1.200 contratti per una fusione del 2024; l’audit dei metadati ha rivelato 87 cambiamenti di proprietà inattesi che sarebbero stati invisibili senza automazione.
Nelle sezioni seguenti imparerai a:
- Estrarre tutti i metadati da due documenti.
- Identificare proprietà aggiunte, rimosse e modificate.
- Concentrarti su tag relativi a proprietà e revisioni.
- Serializzare il diff in CSV o JSON per le catene di audit.
Perché il Confronto dei Metadati è Importante
Il confronto accurato dei metadati è essenziale per:
- Legal e‑discovery: Dimostrare la provenienza del documento e rilevare manomissioni.
- Conformità normativa: Tracciare chi ha creato o modificato un file per audit GDPR o SOX.
- Indagini forensi: Individuare revisioni nascoste o modifiche non autorizzate.
Secondo la documentazione di GroupDocs.Metadata (2024), oltre il 30 % dei fallimenti di conformità deriva da modifiche di proprietà non documentate.
Prerequisiti
- .NET 6.0 o successivo
- GroupDocs.Metadata per .NET 24.10+ (licenza temporanea)
- Due versioni del documento (ad es.
contract_v1.docxecontract_v2.docx)
Installa tramite NuGet:
dotnet add package GroupDocs.Metadata
Come confronto i metadati tra due versioni di documento?
Risposta: Carica ciascun file con MetadataFacade, estrai tutte le proprietà in dizionari, quindi itera per costruire un MetadataDiff che categorizza le voci aggiunte, rimosse e modificate. Il diff può essere ispezionato programmaticamente o scritto in CSV/JSON per i report di audit.
Diff di Tutte le Proprietà
Il frammento seguente dimostra l’algoritmo di diff principale. Utilizza l’helper ExtractAllMetadata.Run (vedi più avanti) per estrarre ogni proprietà da ciascuna versione.
// Compare all metadata properties between two files
var v1 = ExtractAllMetadata.Run(pathV1);
var v2 = ExtractAllMetadata.Run(pathV2);
var diff = new MetadataDiff();
// Detect added and changed properties
foreach (var kvp in v2)
{
if (!v1.ContainsKey(kvp.Key))
{
diff.Added[kvp.Key] = kvp.Value; // New property in v2
}
else if (v1[kvp.Key] != kvp.Value)
{
diff.Changed[kvp.Key] = (v1[kvp.Key], kvp.Value); // Value changed
}
}
// Detect removed properties
foreach (var kvp in v1)
{
if (!v2.ContainsKey(kvp.Key))
{
diff.Removed[kvp.Key] = kvp.Value; // Property missing in v2
}
}
return diff;
Punti chiave:
MetadataDiffcontiene tre dizionari:Added,Removed,Changed.- L’algoritmo funziona in tempo O(n), adatto a file con migliaia di proprietà.
- Non vengono eseguite operazioni I/O; spetta al chiamante decidere come persistere il risultato.
Rilevare le Modifiche di Proprietà
I tag relativi alla proprietà (Author, LastSavedBy, Manager, Company) sono spesso i più critici per gli audit legali. Il metodo qui sotto isola quei tag e segnala eventuali differenze.
// Get ownership‑related properties from each version
var v1Values = GetOwnershipProperties(pathV1);
var v2Values = GetOwnershipProperties(pathV2);
var changes = new Dictionary<string, (string, string)>();
var allKeys = new HashSet<string>(v1Values.Keys);
foreach (var key in v2Values.Keys) allKeys.Add(key);
foreach (var key in allKeys)
{
var oldV = v1Values.TryGetValue(key, out var o) ? o : "<missing>";
var newV = v2Values.TryGetValue(key, out var n) ? n : "<missing>";
if (oldV != newV)
{
changes[key] = (oldV, newV);
}
}
return changes;
Punti chiave:
- Utilizza i predicati
Tags.Person.*eTags.Corporate.Company. - Restituisce un dizionario in cui ogni voce mostra vecchio → nuovo valore.
- Ideale per generare un report conciso delle modifiche di proprietà.
Helper: GetOwnershipProperties
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
using (var metadata = new MetadataFacade(path))
{
if (metadata.FileFormat == FileFormat.Unknown) return dict;
var props = metadata.FindProperties(p =>
p.Tags.Contains(Tags.Person.Creator) ||
p.Tags.Contains(Tags.Person.Editor) ||
p.Tags.Contains(Tags.Person.Manager) ||
p.Tags.Contains(Tags.Corporate.Company));
foreach (var p in props)
{
dict[p.Name] = p.InterpretedValue?.ToString() ?? p.Value?.ToString() ?? string.Empty;
}
}
return dict;
Rilevare le Modifiche alla Cronologia delle Revisioni
I metadati relativi alle revisioni (RevisionNumber, TotalEditingTime, LastPrinted) rivelano quante volte un documento è stato modificato. Il frammento sotto estrae quelle proprietà e mette in evidenza eventuali scostamenti.
var v1 = GetRevisionProperties(pathV1);
var v2 = GetRevisionProperties(pathV2);
var changes = new Dictionary<string, (string, string)>();
var allKeys = new HashSet<string>(v1.Keys);
foreach (var k in v2.Keys) allKeys.Add(k);
foreach (var key in allKeys)
{
var oldV = v1.TryGetValue(key, out var o) ? o : "<missing>";
var newV = v2.TryGetValue(key, out var n) ? n : "<missing>";
if (oldV != newV)
{
changes[key] = (oldV, newV);
}
}
return changes;
Punti chiave:
- Cattura sia timestamp sia contatori numerici di revisione.
- Utile per individuare modifiche nascoste che non sono state salvate come versioni separate.
Helper: GetRevisionProperties
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
using (var metadata = new MetadataFacade(path))
{
if (metadata.FileFormat == FileFormat.Unknown) return dict;
var props = metadata.FindProperties(p =>
p.Tags.Contains(Tags.Time.Modified) ||
p.Tags.Contains(Tags.Time.Created) ||
p.Tags.Contains(Tags.Time.Printed) ||
p.Name != null && (p.Name.Contains("Revision") || p.Name.Contains("EditTime") || p.Name.Contains("EditingTime")));
foreach (var p in props)
{
dict[p.Name] = p.InterpretedValue?.ToString() ?? p.Value?.ToString() ?? string.Empty;
}
}
return dict;
Esportare il Diff in CSV
I team di conformità spesso hanno bisogno di un report adatto a fogli di calcolo. Il metodo seguente scrive il MetadataDiff in un file CSV con quattro colonne.
var sb = new StringBuilder();
sb.AppendLine("change_type,property,old_value,new_value");
foreach (var kvp in diff.Added)
{
sb.AppendLine($"added,{CsvEscape(kvp.Key)},,{CsvEscape(kvp.Value)}");
}
foreach (var kvp in diff.Removed)
{
sb.AppendLine($"removed,{CsvEscape(kvp.Key)},{CsvEscape(kvp.Value)},");
}
foreach (var kvp in diff.Changed)
{
sb.AppendLine($"changed,{CsvEscape(kvp.Key)},{CsvEscape(kvp.Value.OldValue)},{CsvEscape(kvp.Value.NewValue)}");
}
File.WriteAllText(outputPath, sb.ToString());
Punti chiave:
CsvEscapequotizza in modo sicuro i campi contenenti virgole o interruzioni di riga.- Il file risultante può essere aperto direttamente in Excel o caricato in un SIEM.
Helper: CsvEscape
if (string.IsNullOrEmpty(s)) return string.Empty;
if (s.Contains(",") || s.Contains("\"") || s.Contains("\n"))
{
return "\"" + s.Replace("\"", "\"\"") + "\"";
}
return s;
Esportare il Diff in JSON
Per pipeline programmatiche, spesso è preferibile un payload JSON. Il metodo qui sotto produce uno schema stabile con tre oggetti di primo livello.
var sb = new StringBuilder();
sb.AppendLine("{");
sb.AppendLine(" \"added\": {");
WriteMap(sb, diff.Added);
sb.AppendLine(" },");
sb.AppendLine(" \"removed\": {");
WriteMap(sb, diff.Removed);
sb.AppendLine(" },");
sb.AppendLine(" \"changed\": {");
var changedItems = 0;
foreach (var kvp in diff.Changed)
{
var comma = ++changedItems < diff.Changed.Count ? "," : string.Empty;
sb.AppendLine($" \"{Escape(kvp.Key)}\": {{ \"from\": \"{Escape(kvp.Value.OldValue)}\", \"to\": \"{Escape(kvp.Value.NewValue)}\" }}{comma}");
}
sb.AppendLine(" }");
sb.AppendLine("}");
File.WriteAllText(outputPath, sb.ToString());
Punti chiave:
WriteMapscrive semplici oggetti chiave‑valore per le sezioni aggiunte/rimosse.Escapegarantisce stringhe compatibili con JSON.
Helper: WriteMap & Escape
var i = 0;
foreach (var kvp in map)
{
var comma = ++i < map.Count ? "," : string.Empty;
sb.AppendLine($" \"{Escape(kvp.Key)}\": \"{Escape(kvp.Value)}\"{comma}");
}
return s?.Replace("\\", "\\\\").Replace("\"", "\\\"") ?? string.Empty;
Confronto dei Metodi: Quando Usare Ciascuno
| Metodo | Ideale Per | Vantaggi Principali | Limitazioni |
|---|---|---|---|
| Full‑Property Diff | Audit forense generali | Rileva qualsiasi proprietà aggiunta/rimossa/modificata | Può produrre diff molto grandi per file complessi |
| Ownership Detection | Controlli di provenienza legale | Si concentra solo sui tag legati all’identità | Ignora altre modifiche di metadati |
| Revision History Detection | Analisi dell’attività di editing | Evidenzia contatori temporali e di revisione | Richiede che le proprietà siano presenti nel file |
| CSV Export | Report basati su fogli di calcolo | Facile da aprire in Excel, leggibile da umano | Struttura piatta limitata |
| JSON Export | Pipeline automatizzate, dashboard | Strutturato, leggibile da macchina | Payload leggermente più grande |
Scegli il diff a proprietà complete quando ti serve un audit esaustivo; combinalo con l’esportazione CSV per revisioni rapide da parte degli stakeholder. Per controlli di conformità automatizzati, indirizza direttamente l’output JSON al tuo sistema di monitoraggio.
Best Practice e Suggerimenti
- Rilascia
MetadataFacadesubito: avvolgilo in un bloccousingper liberare le risorse native. - Limita l’estrazione ai tag necessari: filtrare per
Tags.Person.*oTags.Time.*riduce l’uso di memoria per PDF di grandi dimensioni. - Valida i formati dei file:
metadata.FileFormat == FileFormat.Unknownindica un file non supportato o corrotto. - Versiona i report di audit: includi la versione della libreria (
GroupDocs.Metadata 24.10) nell’intestazione del file esportato per tracciabilità. - Sicurezza: non registrare valori grezzi di proprietà che possano contenere dati personali; maschera i PII prima di persistere.
- Performance: per documenti > 50 MB, considera lo streaming dell’estrazione dei metadati (attualmente non esposto nell’API) o elabora i file in batch paralleli.
Conclusione
GroupDocs.Metadata offre un modo robusto e programmatico per confrontare ogni informazione incorporata tra due versioni di documento. Estrarre tutti i metadati, concentrarsi su tag di proprietà o di revisione e esportare il diff in CSV o JSON ti permette di costruire workflow forensi ripetibili che soddisfano requisiti legali, di conformità e di sicurezza.
Passi successivi:
- Esplora la lista completa dei tag supportati per personalizzare il diff in base a esigenze normative specifiche.
- Scopri come confrontare più documenti simultaneamente (riferimento API).
- Dai un’occhiata ai progetti di esempio su GitHub per scenari di elaborazione batch (Repo Esempi).