💡 Plně funkční příklad dostupný na GitHubu:
metadata-diff-between-docs-using-groupdocs-metadata-dotnet

Úvod

Když se během fúze smlouva předává z jedné strany na druhou, právní tým musí prokázat, že informace o vlastnictví dokumentu – autor, poslední editor, název společnosti – zůstaly nedotčeny. Manuální kontrola vlastností každého souboru je zdlouhavá a náchylná k chybám; jediná opomenutá změna může zneplatnit audit souladu. GroupDocs.Metadata je .NET knihovna, která extrahuje a porovnává každou vloženou vlastnost dokumentu, což umožňuje automatizovanou forenzní analýzu změn mezi verzemi. Tento tutoriál ukazuje, jak detekovat výměny vlastníků, úpravy historie revizí a jakékoli jiné změny vlastností mezi dvěma verzemi stejného souboru a následně exportovat výsledky do CSV nebo JSON pro další reportování.

Na tento problém jsem narazil při auditu sady 1 200 smluv pro fúzi v roce 2024; audit metadat odhalil 87 neočekávaných změn vlastnictví, které by bez automatizace zůstaly neviditelné.

V následujících sekcích se naučíte, jak:

  • Extrahovat všechna metadata ze dvou dokumentů.
  • Identifikovat přidané, odebrané a změněné vlastnosti.
  • Zaměřit se na značky související s vlastnictvím a revizemi.
  • Serializovat rozdíl do CSV nebo JSON pro auditní stopy.

Proč je porovnání metadat důležité

Přesné porovnání metadat je nezbytné pro:

  • Legal e‑discovery: Prokázat původ dokumentu a odhalit manipulaci.
  • Regulační soulad: Sledovat, kdo vytvořil nebo upravil soubor pro audity GDPR nebo SOX.
  • Forenzní vyšetřování: Odhalit skryté revize nebo neautorizované úpravy.

Podle dokumentace GroupDocs.Metadata (2024) více než 30 % selhání v souladu pramení z nezdokumentovaných změn vlastností.

Požadavky

  • .NET 6.0 nebo novější
  • GroupDocs.Metadata for .NET 24.10+ (temporary license)
  • Dvě verze dokumentu (např. contract_v1.docx a contract_v2.docx)

Instalace přes NuGet:

dotnet add package GroupDocs.Metadata

Jak porovnat metadata mezi dvěma verzemi dokumentu?

Odpověď: Načtěte každý soubor pomocí MetadataFacade, extrahujte všechny vlastnosti do slovníků a poté iterujte, abyste vytvořili MetadataDiff, který kategorizuje přidané, odebrané a změněné položky. Rozdíl lze prohlížet programově nebo zapsat do CSV/JSON pro auditní reportování.

Full‑Property Diff

Následující úryvek ukazuje hlavní algoritmus porovnání. Používá pomocnou metodu ExtractAllMetadata.Run (viz níže) k načtení všech vlastností z každé verze.

// 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;

Klíčové body:

  • MetadataDiff obsahuje tři slovníky: Added, Removed, Changed.
  • Algoritmus běží v čase O(n), což je vhodné i pro soubory s tisíci vlastnostmi.
  • Žádné I/O se neprovádí; volající si sám určuje, jak výsledek uložit.

Detekce změn vlastnictví

Značky související s vlastnictvím (Author, LastSavedBy, Manager, Company) jsou často nejkritičtější pro právní audity. Níže uvedená metoda izoluje tyto značky a nahlásí jakékoli rozdíly.

// 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;

Klíčové body:

  • Používá predikáty Tags.Person.* a Tags.Corporate.Company.
  • Vrací slovník, kde každá položka ukazuje starou → novou hodnotu.
  • Ideální pro vytvoření stručné zprávy o změnách vlastnictví.

Pomocná metoda: 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;

Detekce změn historie revizí

Metadata související s revizemi (RevisionNumber, TotalEditingTime, LastPrinted) odhalují, kolikrát byl dokument upravován. Následující úryvek extrahuje tyto vlastnosti a zvýrazní jakékoli odchylky.

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;

Klíčové body:

  • Zachycuje jak časové značky, tak číselné počítadla revizí.
  • Užitečné pro odhalení skrytých úprav, které nebyly uloženy jako samostatné verze.

Pomocná metoda: 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;

Export diffu do CSV

Auditní týmy často potřebují report ve formátu vhodném pro tabulkový procesor. Následující metoda zapíše MetadataDiff do CSV souboru se čtyřmi sloupci.

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());

Klíčové body:

  • CsvEscape bezpečně uvozovává pole obsahující čárky nebo konce řádků.
  • Výsledný soubor lze přímo otevřít v Excelu nebo načíst do SIEM.

Pomocná metoda: CsvEscape

if (string.IsNullOrEmpty(s)) return string.Empty;
if (s.Contains(",") || s.Contains("\"") || s.Contains("\n"))
{
    return "\"" + s.Replace("\"", "\"\"") + "\"";
}
return s;

Export diffu do JSON

Pro programové pipeline je často výhodnější JSON. Níže uvedená metoda vytváří stabilní schéma se třemi hlavními objekty.

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());

Klíčové body:

  • WriteMap zapisuje jednoduché klíč‑hodnota objekty pro sekce added/removed.
  • Escape zajišťuje řetězce kompatibilní s JSON.

Pomocná metoda: 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;

Porovnání metod: Kdy kterou použít

Metoda Nejlepší pro Klíčové výhody Omezení
Full‑Property Diff Obecné forenzní audity Detekuje jakoukoli přidanou, odebranou nebo změněnou vlastnost Může vytvořit velký diff u složitých souborů
Ownership Detection Kontroly právního původu Zaměřuje se pouze na značky nesoucí identitu Ignoruje ostatní změny metadat
Revision History Detection Analýza aktivity úprav Zvýrazňuje časové značky a počítadla revizí Vyžaduje, aby požadované vlastnosti byly v souboru přítomny
CSV Export Reportování v tabulkovém formátu Snadno otevřitelné v Excelu, čitelné pro člověka Omezeno na plochou strukturu
JSON Export Automatizované pipeline, dashboardy Strukturované, strojově čitelné O něco větší objem dat

Zvolte Full‑Property Diff, pokud potřebujete komplexní audit; kombinujte jej s CSV Export pro rychlé přehledy pro zainteresované strany. Pro automatizované kontroly souladu přesměrujte výstup JSON přímo do vašeho monitorovacího systému.

Nejlepší postupy a tipy

  • Okamžitě uvolňujte MetadataFacade: obalte jej do using, aby se uvolnily nativní zdroje.
  • Omezte extrakci na potřebné značky: filtrování pomocí Tags.Person.* nebo Tags.Time.* snižuje paměťovou náročnost u velkých PDF.
  • Validujte formát souboru: metadata.FileFormat == FileFormat.Unknown značí nepodporovaný nebo poškozený soubor.
  • Verzujte auditní zprávy: do hlavičky exportovaného souboru zahrňte verzi knihovny (např. GroupDocs.Metadata 24.10) pro sledovatelnost.
  • Bezpečnost: nikdy neukládejte surové hodnoty vlastností, které mohou obsahovat osobní údaje; před uložením je maskujte.
  • Výkon: u dokumentů > 50 MB zvažte streamování extrakce metadat (v současné API není dostupné) nebo zpracovávejte soubory v paralelních dávkách.

Závěr

GroupDocs.Metadata poskytuje robustní programatický způsob, jak porovnat každou vloženou informaci mezi dvěma verzemi dokumentu. Extrahováním kompletních metadat, zaměřením se na značky vlastnictví nebo revizí a exportem rozdílů do CSV nebo JSON můžete vytvořit opakovatelné forenzní workflow, které splňuje požadavky právního, regulačního i bezpečnostního oddělení.

Další kroky:

  • Prozkoumejte full list of supported tags a přizpůsobte diff konkrétním regulačním požadavkům.
  • Naučte se porovnávat více dokumentů současně (API reference).
  • Podívejte se na další ukázkové projekty na GitHubu pro scénáře hromadného zpracování (Examples Repo).

Další zdroje