Вступ

Коли юридичні команди або судові аналітики мають довести, що документ не був підроблений, простий перегляд видимого вмісту недостатній. Приховані властивості — такі як автор, дата створення або номер ревізії — можуть розкрити, хто і коли торкався файлу. Виявлення цих тонких змін між версіями документів є поширеною проблемою, яка часто вимагає ручної перевірки кожної властивості, що є трудомістким і схильним до помилок процесом.

GroupDocs.Metadata для Java надає програмний спосіб отримати всі поля метаданих і обчислити структурний diff між двома версіями. У цьому посібнику ми порівняємо три практичні підходи: повний diff метаданих, виявлення змін власності та аналіз історії ревізій. Кожен метод продемонстровано коротким, готовим до копіювання кодом, а також показано, як експортувати результати у CSV або JSON для аудиторської звітності.

Я зіткнувся з цим, переглядаючи контракт, який редагували кілька сторін протягом місяців; видимий текст був ідентичним, але поля власності змінилися без повідомлення.

Як визначити, які поля метаданих змінилися між двома версіями документу?

GroupDocs.Metadata завантажує кожен файл, витягує всі доступні властивості у карту і потім ітерує ключі, класифікуючи додані, видалені та змінені елементи. Бібліотека обробляє вбудовані та користувацькі теги, тому ви отримуєте повну картину без написання парсерів, специфічних для формату. Результатом є об’єкт MetadataDiff, який можна запитувати або серіалізувати для звітів про відповідність.

Передумови

  • Java 8 або новіша
  • GroupDocs.Metadata for Java 24.7 ([temporary license][TEMP_LICENSE_URL])
  • Два документі, які потрібно порівняти (наприклад, contract_v1.pdf і contract_v2.pdf)

Встановлення

Додайте залежність через Maven:

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

Метод 1 – Повний diff метаданих

Цей метод витягує усі властивості метаданих з обох версій і повідомляє про додані, видалені та змінені записи.

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

Ключові моменти:

  • Всеохоплююче: Захоплює всі теги, включаючи користувацькі.
  • Проста логіка мап: Не потрібна зовнішня бібліотека diff.
  • Об’єкт результату: Карти added, removed і changed готові до подальшої обробки.

💡 Порада: Використовуйте цей метод, коли потрібен повний аудит для регуляторної відповідності.

Метод 2 – Виявлення змін власності

Юридичні спори часто зводяться до того, хто створив або відредагував документ. Цей метод зосереджується на тегах, пов’язаних з особами, таких як Creator, Editor, Manager і 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 витягує лише релевантні теги:

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;

Ключові моменти:

  • Цілеспрямовано: Перевіряються лише властивості, що стосуються ідентифікації.
  • Чіткий вихід: Повертає мапу, де кожен запис показує значення [old, new].
  • Готово до відповідності: Ідеально підходить для e‑discovery або спорів про власність контракту.

💡 Порада: Поєднайте цей метод з повним diff, якщо потрібна і ширина, і глибина аналізу.

Метод 3 – Виявлення змін історії ревізій

Номери ревізій, часові мітки редагування та дати друку невидимі для кінцевих користувачів, але критично важливі для судово‑технічних часових ліній. Цей метод виділяє теги, пов’язані з часом.

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 витягує мітки Modified, Created і 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()))