💡 Full working example available on GitHub: metadata-diff-doc-versions-using-groupdocs-metadata-nodejs
Introduction
La comparaison de métadonnées est une fonctionnalité de GroupDocs.Metadata qui révèle les modifications cachées entre les versions d’un document, permettant aux auditeurs de vérifier rapidement l’authenticité. Lorsque les équipes juridiques doivent prouver qu’un contrat n’a pas été altéré, les propriétés invisibles — auteur, éditeur, horodatages de révision — racontent souvent la véritable histoire.
J’ai rencontré ce problème en examinant un lot de conventions fournisseurs ; chaque fichier affichait la même date de création, mais les champs d’auteur cachés différaient, et l’audit manuel aurait pris des heures.
Why hidden metadata matters for legal teams
Les professionnels du droit et de la conformité s’appuient sur des preuves immuables. Même si le texte visible reste identique, une modification du champ Creator ou LastPrinted peut indiquer des éditions non autorisées. Détecter ces changements tôt évite des litiges coûteux et satisfait les exigences des audits réglementaires.
Our solution with GroupDocs.Metadata
GroupDocs.Metadata pour Node.js fournit une API unique qui extrait chaque propriété prise en charge, compare deux révisions et génère des rapports de différences structurés. Le SDK gère DOCX, PDF, XLSX et de nombreux autres formats « out‑of‑the‑box », vous n’avez donc pas besoin de parseurs personnalisés pour chaque type de fichier.
Step 1 – Pulling all metadata from a document
L’assistant ci‑dessous ouvre un fichier, parcourt l’arbre complet des propriétés et renvoie une simple map JavaScript de nom de propriété → valeur. Il utilise AnySpecification pour capturer à la fois les champs intégrés et personnalisés.
/** 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;
}
Note de performance : l’extraction des métadonnées d’un DOCX de 5 Mo s’achève en moins de 200 ms sur un CPU typique de 2 GHz [Docs][DOCS_URL].
Step 2 – Spotting differences between two revisions
Une fois les deux versions représentées sous forme d’objets simples, la routine de comparaison construit trois catégories — ajoutées, supprimées et modifiées — et fournit un accesseur pratique 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;
},
};
}
Step 3 – Ownership and revision insights (optional)
Detecting ownership changes
Les auditeurs juridiques se concentrent souvent sur qui a créé ou modifié un document. Cet assistant isole ces champs et signale toute variation.
/** 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;
}
Detecting revision‑history changes
Le numéro de révision, le temps total d’édition et les horodatages de dernière impression révèlent une activité cachée.
/** 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;
}
Step 4 – Exporting audit reports
CSV export
Le format CSV est idéal pour Excel ou l’ingestion par un SIEM. Les champs sont échappés pour gérer les virgules et les sauts de ligne.
/** 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 export
Le JSON conserve la structure hiérarchique pour une consommation programmatique.
/** 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');
}
Full driver script
Le script assemble le tout, affiche un résumé rapide et écrit les rapports CSV et JSON.
const path = require('path');
const fs = require('fs');
const groupdocs = require('@groupdocs/groupdocs.metadata');
function esc(txt) { return /[",\n]/.test(txt) ? `"${txt.replace(/"/g, '""')}"` : txt; }
const diff = compareMetadataSets('resources/document-v1.docx', 'resources/document-v2.doc