💡 Full working example available on GitHub:
metadata-diff-doc-versions-using-groupdocs-metadata-nodejs

Вступ

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

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

Чому приховані метадані важливі для юридичних команд

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

Наше рішення з GroupDocs.Metadata

GroupDocs.Metadata для Node.js надає єдиний API, який витягує всі підтримувані властивості, порівнює два ревізії та створює структуровані звіти про різницю. SDK підтримує DOCX, PDF, XLSX та багато інших форматів «з коробки», тому вам не потрібні кастомні парсери для кожного типу файлів.

Крок 1 – Отримання всіх метаданих з документа

Нижче наведений допоміжний код відкриває файл, обходить повне дерево властивостей і повертає просту карту JavaScript у вигляді назва властивості → значення. Він використовує AnySpecification для захоплення як вбудованих, так і користувацьких полів.

/** Extract all metadata from a file */
function extractAllMetadata(documentPath) {
  const result = {};
  const metadata = new groupdocs.Metadata(documentPath);
  try {
    const properties = metadata.findProperties(new groupdocs.AnySpecification());
    for (let i = 0; i < properties.getCount(); i++) {
      const p = properties.get_Item(i);
      const name = p.getName();
      const value = valueToString(p);
      result[name] = value;
    }
  } finally {
    metadata.close();
  }
  return result;
}

Примітка про продуктивність: витяг метаданих з DOCX розміром 5 МБ завершується за 200 мс на типічному 2 ГГц CPU [Docs][DOCS_URL].

Крок 2 – Виявлення відмінностей між двома ревізіями

Коли обидві версії представлені у вигляді простих об’єктів, процедура порівняння формує три категорії — added, removed та changed — і надає зручний геттер totalChanges.

/** Compare two metadata maps */
function compareMetadataSets(pathV1, pathV2) {
  const v1 = extractAllMetadata(pathV1);
  const v2 = extractAllMetadata(pathV2);

  const added = {};
  const removed = {};
  const changed = {};

  for (const k of Object.keys(v2)) {
    if (!(k in v1)) added[k] = v2[k];
    else if (v1[k] !== v2[k]) changed[k] = { from: v1[k], to: v2[k] };
  }
  for (const k of Object.keys(v1)) {
    if (!(k in v2)) removed[k] = v1[k];
  }

  return {
    added,
    removed,
    changed,
    get totalChanges() {
      return Object.keys(this.added).length + Object.keys(this.removed).length + Object.keys(this.changed).length;
    },
  };
}

Крок 3 – Інсайти про власність та ревізії (необов’язково)

Виявлення змін власності

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

/** Ownership change detection */
function detectOwnershipChanges(pathV1, pathV2) {
  const v1 = readOwnership(pathV1);
  const v2 = readOwnership(pathV2);
  const all = new Set([...Object.keys(v1), ...Object.keys(v2)]);
  const changes = {};
  for (const k of all) {
    const oldV = v1[k] !== undefined ? v1[k] : '<missing>';
    const newV = v2[k] !== undefined ? v2[k] : '<missing>';
    if (oldV !== newV) changes[k] = { from: oldV, to: newV };
  }
  return changes;
}

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

Номер ревізії, загальний час редагування та час останнього друку розкривають приховану активність.

/** Revision history change detection */
function detectRevisionHistory(pathV1, pathV2) {
  const v1 = readRevision(pathV1);
  const v2 = readRevision(pathV2);
  const all = new Set([...Object.keys(v1), ...Object.keys(v2)]);
  const changes = {};
  for (const k of all) {
    const oldV = v1[k] !== undefined ? v1[k] : '<missing>';
    const newV = v2[k] !== undefined ? v2[k] : '<missing>';
    if (oldV !== newV) changes[k] = { from: oldV, to: newV };
  }
  return changes;
}

Крок 4 – Експорт аудиторських звітів

Експорт у CSV

Формат CSV ідеальний для Excel або SIEM. Поля екрануються, щоб коректно обробляти коми та переноси рядків.

/** Export diff to CSV */
function exportDiffToCsv(diff, outputPath) {
  const rows = ['change_type,property,old_value,new_value'];
  for (const [k, v] of Object.entries(diff.added)) rows.push(`added,${esc(k)},,${esc(v)}`);
  for (const [k, v] of Object.entries(diff.removed)) rows.push(`removed,${esc(k)},${esc(v)},`);
  for (const [k, o] of Object.entries(diff.changed)) rows.push(`changed,${esc(k)},${esc(o.from)},${esc(o.to)}`);
  fs.writeFileSync(outputPath, rows.join('\n') + '\n', 'utf-8');
}

Експорт у JSON

JSON зберігає ієрархічну структуру для програмного споживання.

/** Export diff to JSON */
function exportDiffToJson(diff, outputPath) {
  const payload = { added: diff.added, removed: diff.removed, changed: diff.changed };
  fs.writeFileSync(outputPath, JSON.stringify(payload, null, 2), 'utf-8');
}

Повний скрипт‑драйвер

Скрипт об’єднує всі частини, виводить короткий підсумок і записує звіти у CSV та JSON.

const path = require('path');
const fs = require('fs');
const groupdocs = require('@groupdocs/groupdocs.metadata');

function esc(txt) { return /[",\