Introduction
Lorsque les équipes juridiques ou les analystes légistes doivent prouver qu’un document n’a pas été altéré, se contenter du contenu visible ne suffit pas. Les propriétés cachées — telles que l’auteur, la date de création ou le numéro de révision — peuvent révéler qui a manipulé le fichier et quand. Détecter ces changements subtils entre différentes versions de document est un problème récurrent qui nécessite souvent une inspection manuelle de chaque propriété, une tâche longue et sujette aux erreurs.
GroupDocs.Metadata for Java fournit un moyen programmatique d’extraire chaque champ de métadonnées et de calculer une différence structurée entre deux versions. Dans ce tutoriel, nous comparerons trois approches pratiques : une différence complète des métadonnées, la détection ciblée des changements de propriétaire et l’analyse de l’historique des révisions. Chaque méthode est illustrée par du code concis, copiable‑collable, et nous montrerons également comment exporter les résultats au format CSV ou JSON pour les rapports d’audit.
Je suis tombé sur ce problème en examinant un contrat qui avait été modifié par plusieurs parties pendant des mois ; le texte visible était identique, mais les champs de propriétaire avaient changé en silence.
Comment savoir quels champs de métadonnées ont changé entre deux versions de document ?
GroupDocs.Metadata charge chaque fichier, extrait toutes les propriétés accessibles dans une map, puis parcourt les clés pour classer les ajouts, suppressions et modifications. La bibliothèque gère les balises intégrées et personnalisées, vous offrant ainsi une vue complète sans écrire de parseurs spécifiques aux formats. Le résultat est un objet MetadataDiff que vous pouvez interroger ou sérialiser pour les rapports de conformité.
Prérequis
- Java 8 ou version ultérieure
- GroupDocs.Metadata for Java 24.7 (licence temporaire)
- Deux fichiers de document que vous souhaitez comparer (par ex.
contract_v1.pdfetcontract_v2.pdf)
Installation
Ajoutez la dépendance via Maven :
<dependency>
<groupId>com.groupdocs</groupId>
<artifactId>groupdocs-metadata</artifactId>
<version>24.7</version>
</dependency>
Méthode 1 – Différence complète des métadonnées
Cette méthode extrait toutes les propriétés de métadonnées des deux versions et signale les entrées ajoutées, supprimées et modifiées.
// 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;
Points clés :
- Complet : capture toutes les balises, y compris les personnalisées.
- Logique de map simple : aucune bibliothèque de diff externe requise.
- Objet résultat : les maps
added,removedetchangedsont prêtes pour un traitement ultérieur.
💡 Astuce : utilisez cette approche lorsque vous avez besoin d’une piste d’audit complète pour la conformité réglementaire.
Méthode 2 – Détection des changements de propriétaire
Les litiges juridiques reposent souvent sur qui a créé ou modifié un document. Cette méthode se concentre sur les balises liées aux personnes telles que Creator, Editor, Manager et 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 ne récupère que les balises pertinentes :
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;
Points clés :
- Ciblé : seules les propriétés d’identité sont examinées.
- Sortie claire : renvoie une map où chaque entrée montre les valeurs
[ancien, nouveau]. - Prêt pour la conformité : idéal pour l’e‑discovery ou les litiges de propriété de contrat.
💡 Astuce : combinez cette méthode avec le diff complet si vous avez besoin à la fois d’étendue et de profondeur.
Méthode 3 – Détection des changements d’historique de révision
Les numéros de révision, les horodatages d’édition et les dates d’impression sont invisibles pour les utilisateurs finaux mais essentiels pour les chronologies légistes. Cette méthode isole les balises liées au temps.
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 extrait les horodatages Modified, Created et 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;
Points clés :
- Reconstruction de la chronologie : montre combien de fois un fichier a été édité ou imprimé.
- Deltas numériques : utile pour détecter des révisions suspectes très rapides.
- Léger : seules trois balises sont interrogées, ce qui garde l’exécution rapide.
💡 Astuce : utilisez cette méthode lorsque vous devez prouver qu’un document n’a pas été modifié après une date limite précise.
Comparaison des méthodes : quand utiliser chaque méthode
| Méthode | Meilleur usage | Avantages clés | Limitations |
|---|---|---|---|
| Différence complète des métadonnées | Audit complet, conformité réglementaire | Capture chaque propriété, y compris les balises personnalisées | Consommation mémoire plus importante pour les fichiers très volumineux |
| Détection des changements de propriétaire | Litiges de propriété, e‑discovery | Se concentre sur les champs liés aux personnes, sortie lisible | Ignore les autres métadonnées utiles |
| Détection des changements d’historique de révision | Forensique de chronologie, vérification des journaux de modifications | Isole les horodatages et numéros de révision | Ne montre pas les changements au niveau du contenu |
Choisissez la méthode qui correspond à votre objectif de conformité. Dans de nombreux cas, une combinaison — exécuter le diff complet puis approfondir les sections propriétaire ou révision — offre la meilleure visibilité.
Exportation de la différence
Après avoir obtenu un MetadataDiff, il faut souvent partager les résultats. Voici deux exportateurs simples.
Export 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 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));
Les deux exportateurs s’appuient sur des méthodes auxiliaires (esc, escape, writeMap) qui gèrent correctement les virgules et les guillemets.
Bonnes pratiques et astuces
- Délimitez votre diff : pour les gros PDF, limitez le diff aux balises de propriétaire ou de révision afin de réduire le temps de traitement.
- Validez le format du fichier : vérifiez toujours
metadata.getFileFormat() != FileFormat.Unknownavant d’itérer les propriétés. - Libérez les ressources : utilisez le try‑with‑resources (
try (Metadata metadata = new Metadata(path)) { … }) pour libérer les handles natifs. - Cohérence des versions : assurez‑vous que les deux documents proviennent de la même version de format ; mélanger DOCX et DOC ancien peut donner des résultats trompeurs.
- Sécurité : ne jamais exposer les valeurs brutes de métadonnées dans des API publiques sans les assainir ; utilisez les helpers
esc/escapelors de l’écriture CSV/JSON. - Performance : exportez en CSV pour une ingestion massive dans les SIEM ; le JSON est préférable pour des journaux d’audit lisibles par l’homme.
Conclusion
GroupDocs.Metadata for Java simplifie l’analyse forensique des métadonnées. En combinant les méthodes de diff complet, de détection de propriétaire et d’analyse de l’historique des révisions, vous pouvez créer une chaîne d’audit robuste qui met en lumière les changements cachés, soutient les preuves légales et satisfait les exigences de conformité. L’exportation en CSV ou JSON permet une intégration fluide avec les outils de reporting ou les pipelines de data‑warehouse.
Prochaines étapes :
- Explorez les spécifications de balises avancées pour filtrer les métadonnées personnalisées GroupDocs.Metadata Java docs.
- Apprenez à comparer plusieurs documents en lot API reference.
- Consultez les projets d’exemple officiels pour des implémentations de bout en bout GitHub examples.