Úvod

Když právní týmy nebo forenzní analytici potřebují prokázat, že dokument nebyl pozměněn, pouhý pohled na viditelný obsah nestačí. Skryté vlastnosti — například autor, datum vytvoření nebo číslo revize — mohou odhalit, kdo soubor upravil a kdy. Detekce těchto jemných změn napříč verzemi dokumentu je častý problém, který často vyžaduje ruční kontrolu každé vlastnosti, což je časově náročné a náchylné k chybám.

GroupDocs.Metadata pro Java poskytuje programatický způsob, jak extrahovat každé metadata pole a vypočítat strukturovaný diff mezi dvěma verzemi. V tomto tutoriálu porovnáme tři praktické přístupy: kompletní diff metadat, zaměřenou detekci změn vlastnictví a analýzu historie revizí. Každá metoda je demonstrována stručným, připraveným k zkopírování kódem a také ukážeme, jak exportovat výsledky do CSV nebo JSON pro auditní zprávy.

Setkal jsem se s tím při revizi smlouvy, kterou během několika měsíců upravovalo několik stran; viditelný text byl identický, ale pole vlastnictví se tiše změnila.

Jak zjistit, které metadata pole se změnily mezi dvěma verzemi dokumentu?

GroupDocs.Metadata načte každý soubor, extrahuje všechny přístupné vlastnosti do mapy a poté iteruje přes klíče, aby klasifikoval přidání, odebrání a úpravy. Knihovna zvládá vestavěné i vlastní značky, takže získáte kompletní obrázek bez psaní formátově specifických parserů. Výsledkem je objekt MetadataDiff, který můžete dotazovat nebo serializovat pro souladové zprávy.

Požadavky

  • Java 8 nebo novější
  • GroupDocs.Metadata pro Java 24.7 (temporary license)
  • Dva dokumenty, které chcete porovnat (např. contract_v1.pdf a contract_v2.pdf)

Instalace

Přidejte závislost pomocí Maven:

<dependency>
    <groupId>com.groupdocs</groupId>
    <artifactId>groupdocs-metadata</artifactId>
    <version>24.7</version>
</dependency>

Metoda 1 – Kompletní diff metadat

Tato metoda extrahuje každou metadata vlastnost z obou verzí a hlásí přidané, odebrané a změněné položky.

// CompareMetadataSets.run – returns a MetadataDiff object
Map<String, String> v1 = ExtractAllMetadata.run(pathV1);
Map<String, String> v2 = ExtractAllMetadata.run(pathV2);
MetadataDiff diff = new MetadataDiff();

// Detect added and changed properties
for (Map.Entry<String, String> e : v2.entrySet()) {
    String key = e.getKey();
    String val = e.getValue();
    if (!v1.containsKey(key)) {
        diff.added.put(key, val);               // New property in v2
    } else if (!v1.get(key).equals(val)) {
        diff.changed.put(key, new String[]{v1.get(key), val}); // Value changed
    }
}
// Detect removed properties
for (Map.Entry<String, String> e : v1.entrySet()) {
    if (!v2.containsKey(e.getKey())) {
        diff.removed.put(e.getKey(), e.getValue());
    }
}
return diff;

Klíčové body:

  • Komplexní: Zachytí všechny značky, včetně vlastních.
  • Jednoduchá logika map: Není vyžadována externí knihovna pro diff.
  • Objekt výsledku: mapy added, removed a changed jsou připraveny k dalšímu zpracování.

💡 Tip: Použijte tuto metodu, když potřebujete kompletní auditní stopu pro regulatorní soulad.

Metoda 2 – Detekce změn vlastnictví

Právní spory často závisí na tom, kdo dokument vytvořil nebo upravil. Tato metoda se zaměřuje na značky související s osobou, jako jsou Creator, Editor, Manager a Company.

// DetectOwnershipChanges.run – returns a map of changed ownership fields
Map<String, String> v1 = readOwnership(pathV1);
Map<String, String> v2 = readOwnership(pathV2);
Set<String> allKeys = new HashSet<>(v1.keySet());
allKeys.addAll(v2.keySet());
Map<String, String[]> changes = new LinkedHashMap<>();
for (String key : allKeys) {
    String oldVal = v1.getOrDefault(key, "<missing>");
    String newVal = v2.getOrDefault(key, "<missing>");
    if (!oldVal.equals(newVal)) {
        changes.put(key, new String[]{oldVal, newVal});
    }
}
return changes;

readOwnership získává jen relevantní značky:

Map<String, String> result = new LinkedHashMap<>();
try (Metadata metadata = new Metadata(path)) {
    if (metadata.getFileFormat() == FileFormat.Unknown) return result;
    for (MetadataProperty p : metadata.findProperties(
            new ContainsTagSpecification(Tags.getPerson().getCreator())
                .or(new ContainsTagSpecification(Tags.getPerson().getEditor()))
                .or(new ContainsTagSpecification(Tags.getPerson().getManager()))
                .or(new ContainsTagSpecification(Tags.getCorporate().getCompany())))) {
        String value = "";
        if (p.getValue() != null && p.getValue().getRawValue() != null) {
            value = String.valueOf(p.getValue().getRawValue());
        }
        result.put(p.getName(), value);
    }
}
return result;

Klíčové body:

  • Cílené: Prozkoumávají se pouze vlastnosti související s identitou.
  • Přehledný výstup: Vrací mapu, kde každá položka ukazuje hodnoty [old, new].
  • Připravené pro soulad: Ideální pro e‑discovery nebo spory o vlastnictví smluv.

💡 Tip: Kombinujte tuto metodu s kompletním diffe, pokud potřebujete jak šířku, tak hloubku.

Metoda 3 – Detekce změn v historii revizí

Čísla revizí, časová razítka úprav a data tisku jsou pro koncové uživatele neviditelná, ale jsou klíčová pro forenzní časové osy. Tato metoda izoluje časově související značky.

Map<String, String> v1 = readRevision(pathV1);
Map<String, String> v2 = readRevision(pathV2);
Set<String> allKeys = new HashSet<>(v1.keySet());
allKeys.addAll(v2.keySet());
Map<String, String[]> changes = new LinkedHashMap<>();
for (String key : allKeys) {
    String oldVal = v1.getOrDefault(key, "<missing>");
    String newVal = v2.getOrDefault(key, "<missing>");
    if (!oldVal.equals(newVal)) {
        changes.put(key, new String[]{oldVal, newVal});
    }
}
return changes;

readRevision extrahuje časové razítka Modified, Created a Printed:

Map<String, String> result = new LinkedHashMap<>();
try (Metadata metadata = new Metadata(path)) {
    if (metadata.getFileFormat() == FileFormat.Unknown) return result;
    for (MetadataProperty p : metadata.findProperties(
            new ContainsTagSpecification(Tags.getTime().getModified())
                .or(new ContainsTagSpecification(Tags.getTime().getCreated()))
                .or(new ContainsTagSpecification(Tags.getTime().getPrinted())))) {
        String value = "";
        if (p.getValue() != null && p.getValue().getRawValue() != null) {
            value = String.valueOf(p.getValue().getRawValue());
        }
        result.put(p.getName(), value);
    }
}
return result;

Klíčové body:

  • Rekonstrukce časové osy: Ukazuje, kolikrát byl soubor upravován nebo tištěn.
  • Číselné rozdíly: Užitečné pro detekci podezřelých rychlých revizí.
  • Lehké: Dotazuje se jen na tři značky, což udržuje rychlý výkon.

💡 Tip: Použijte tuto metodu, když potřebujete prokázat, že dokument nebyl po konkrétním termínu změněn.

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

Metoda Nejlepší pro Klíčové výhody Omezení
Kompletní diff metadat Kompletní audit, regulatorní soulad Zachytí každou vlastnost, včetně vlastních značek Větší paměťová náročnost u velmi velkých souborů
Detekce změn vlastnictví Právní spory o vlastnictví, e‑discovery Zaměřuje se na pole související s osobou, snadno čitelné Ignoruje další užitečná metadata
Detekce změn v historii revizí Forenzní časová osa, ověření změnového logu Izoluje časové razítka a čísla revizí Nezobrazuje změny na úrovni obsahu

Vyberte metodu, která odpovídá vašemu cíli souladnosti. V mnoha případech kombinace — spustíte kompletní diff a poté se zaměříte na sekce vlastnictví nebo revizí — poskytne nejvíce informací.

Export diffu

Po získání objektu MetadataDiff často potřebujete výsledky sdílet. Níže jsou dva jednoduché exportéry.

Export do CSV

StringBuilder sb = new StringBuilder();
sb.append("change_type,property,old_value,new_value\n");
for (Map.Entry<String, String> e : diff.added.entrySet()) {
    sb.append("added,").append(esc(e.getKey()))
      .append(",,").append(esc(e.getValue())).append("\n");
}
for (Map.Entry<String, String> e : diff.removed.entrySet()) {
    sb.append("removed,").append(esc(e.getKey()))
      .append(",").append(esc(e.getValue()))
      .append(",\n");
}
for (Map.Entry<String, String[]> e : diff.changed.entrySet()) {
    sb.append("changed,").append(esc(e.getKey()))
      .append(",").append(esc(e.getValue()[0]))
      .append(",").append(esc(e.getValue()[1])).append("\n");
}
Files.write(Paths.get(outputPath), sb.toString().getBytes(StandardCharsets.UTF_8));

Export do JSON

StringBuilder sb = new StringBuilder();
sb.append("{\n");
sb.append("  \"added\": {\n");
writeMap(sb, diff.added);
sb.append("  },\n");
sb.append("  \"removed\": {\n");
writeMap(sb, diff.removed);
sb.append("  },\n");
sb.append("  \"changed\": {\n");
int i = 0;
for (Map.Entry<String, String[]> e : diff.changed.entrySet()) {
    String comma = ++i < diff.changed.size() ? "," : "";
    sb.append("    \"").append(escape(e.getKey()))
      .append("\": { \"from\": \"")
      .append(escape(e.getValue()[0])).append("\", \"to\": \"")
      .append(escape(e.getValue()[1])).append("\" }")
      .append(comma).append("\n");
}
sb.append("  }\n");
sb.append("}\n");
Files.write(Paths.get(outputPath), sb.toString().getBytes(StandardCharsets.UTF_8));

Oba exportéry používají pomocné metody (esc, escape, writeMap), které bezpečně zacházejí s čárkami a uvozovkami.

Nejlepší postupy a tipy

  • Omezte rozsah diffu: Pro velké PDF soubory omezte diff na značky vlastnictví nebo revize, aby se snížila doba zpracování.
  • Ověřte formát souboru: Vždy zkontrolujte metadata.getFileFormat() != FileFormat.Unknown před iterací vlastností.
  • Uvolněte zdroje: Používejte try‑with‑resources (try (Metadata metadata = new Metadata(path)) { … }) k uvolnění nativních handle.
  • Konzistence verzí: Ujistěte se, že oba dokumenty jsou ze stejné verze formátu souboru; kombinace DOCX a staršího DOC může vést k zavádějícím výsledkům.
  • Bezpečnost: Nikdy neexponujte surové hodnoty metadat ve veřejných API bez sanitace; při zápisu CSV/JSON používejte pomocné funkce esc/escape.
  • Výkon: Exportujte do CSV pro hromadný import do SIEM; JSON je lepší pro lidsky čitelné auditní logy.

Závěr

GroupDocs.Metadata pro Java usnadňuje forenzní analýzu metadat. Využitím metod kompletního diffu, detekce změn vlastnictví a analýzy historie revizí můžete vytvořit robustní auditní pipeline, která odhalí skryté změny, podpoří právní důkazy a splní požadavky na soulad. Export do CSV nebo JSON umožňuje snadnou integraci s nástroji pro reportování nebo datovými sklady.

Další kroky:

  • Prozkoumejte pokročilé specifikace značek pro filtrování vlastních metadat GroupDocs.Metadata Java docs.
  • Naučte se, jak porovnávat více dokumentů najednou API reference.
  • Podívejte se na oficiální ukázkové projekty pro end‑to‑end implementace GitHub examples.

Další zdroje