💡 Exemple complet fonctionnel disponible sur GitHub :
document-version-metadata-diff-python

Ce que vous allez créer

Dans ce guide, vous comparerez chaque propriété de métadonnées entre deux versions d’un document et afficherez exactement ce qui a été ajouté, supprimé ou modifié. Un diff de version de métadonnées est une comparaison au niveau des propriétés de deux révisions d’un même fichier, et il détecte des signaux qu’une comparaison de texte ne voit jamais : un nouveau créateur, un numéro de révision incrémenté, une session d’édition enregistrée après la clôture de la révision. À la fin, vous disposerez d’une solution fonctionnelle ainsi que de deux détecteurs ciblés et de deux formats d’exportation, le tout tiré d’un dépôt exécutable contenant une paire de révisions d’exemple.

Niveau de compétence : développeur Python intermédiaire
Ce dont vous avez besoin : Python 3, pip et deux révisions d’un même document

Ma première exécution de ce script a signalé un changement de valeur de Company que personne dans l’équipe ne se souvenait d’avoir effectué ; cette ligne unique a justifié la mise en place. Tout ce qui suit est prêt à copier‑coller et ne dépasse pas cent lignes.

Le pipeline est délibérément simple : deux ouvertures de fichier, trois compréhensions de dictionnaire, une boucle d’impression. La simplicité est l’objectif. Les différends de version sont résolus en fonction de la capacité à expliquer et reproduire la méthode, et un script aussi petit peut être lu en entier par quiconque conteste la constatation.


1. Installation

pip install groupdocs-metadata-net==26.5

Le dépôt compagnon fixe cette version et fournit document-v1.docx et document-v2.docx afin que le code ci‑dessous s’exécute tel quel. Verrouillez la version avec laquelle votre audit a été réalisé ; la reproductibilité fait partie des preuves.


2. Le code principal

Lisez les deux arbres de propriétés, puis classez le delta à l’aide de la logique d’ensemble. Voici le diff complet :

# Flatten a file's complete property tree into a dict
def read_props(path):
    props = {}
    with Metadata(path) as metadata:
        for p in metadata.find_properties(lambda p: p.name is not None):
            props[p.name] = (str(p.interpreted_value) if p.interpreted_value is not None
                             else (str(p.value) if p.value is not None else ""))
    return props

v1 = read_props("resources/document-v1.docx")
v2 = read_props("resources/document-v2.docx")

# Classify every key; changed entries keep both values
added = {k: v for k, v in v2.items() if k not in v1}
removed = {k: v for k, v in v1.items() if k not in v2}
changed = {k: (v1[k], v2[k]) for k in v1 if k in v2 and v1[k] != v2[k]}

print(f"added={len(added)} removed={len(removed)} changed={len(changed)}")
for k, (old_v, new_v) in changed.items():
    print(f"  {k}: {old_v} -> {new_v}")

C’est le minimum requis. Attendez de faibles décomptes sur de véritables paires de révisions ; un delta de plusieurs dizaines indique généralement que le fichier est passé par une modification de modèle ou une migration de stockage en cours de route. Les sections suivantes expliquent les appels clés et montrent les personnalisations que la plupart des équipes ajoutent en premier.


3. Fonctionnement

  • Metadata : le gestionnaire de contexte qui ouvre un fichier et le libère à la sortie ; une instance par révision.
  • find_properties : parcourt les champs intégrés, les propriétés personnalisées et le XMP en un seul passage, renvoyant tout ce que le prédicat accepte.
  • interpreted_value : la forme lisible par l’homme d’une propriété ; la privilégier signifie que les dates et les énumérations sont comparées comme des chaînes que vous pouvez imprimer dans un rapport.
  • Noms qualifiés comme clés : les champs intégrés et personnalisés ne peuvent pas entrer en collision dans le dictionnaire, ainsi la logique d’ensemble reste sûre.

Rien ici n’analyse les structures DOCX. La documentation du produit répertorie plus de 170 formats derrière le même appel, de sorte que le même script compare des paires PDF ou XLSX.

Une autre caractéristique du design mérite d’être mentionnée : la frontière de l’API se situe aux deux appels read_props. Tout ce qui suit utilise uniquement la bibliothèque standard Python, de sorte que les tests unitaires, les seuils et les règles d’alerte n’interagissent jamais avec la couche document. Les équipes qui intègrent cela dans un service mettent généralement en cache les dictionnaires extraits par révision et permettent à chaque vérification en aval de les réutiliser, limitant les I/O de fichiers à une ouverture par version, quel que soit le nombre de questions posées.


4. Personnalisations courantes

Détecter uniquement les changements de propriété

Lorsque la question est « qui a touché ce fichier », filtrez au moment de la lecture avec des prédicats de tags plutôt qu’en filtrant le diff complet après coup :

# Identity fields only, whatever the format calls them
def read_ownership(path):
    result = {}
    with Metadata(path) as metadata:
        props = metadata.find_properties(lambda p:
            Tags.person.creator in list(p.tags)
            or Tags.person.editor in list(p.tags)
            or Tags.person.manager in list(p.tags)
            or Tags.corporate.company in list(p.tags))
        for prop in props:
            result[prop.name] = (str(prop.interpreted_value)
                                 if prop.interpreted_value is not None
                                 else (str(prop.value) if prop.value is not None else ""))
    return result

Exécutez la même boucle de delta sur deux de ces dictionnaires, en utilisant <missing> comme valeur par défaut afin qu’un champ disparu apparaisse toujours. Le prédicat ne nomme aucun champ, ce qui permet à un détecteur de fonctionner pour chaque format lu par la bibliothèque.

Suivre la chronologie des modifications

Remplacez le prédicat par Tags.time ainsi que des règles de noms de compteurs et le détecteur rapporte les mouvements de RevisionNumber, TotalEditingTime et LastPrinted :

props = metadata.find_properties(lambda p:
    Tags.time.modified in list(p.tags)
    or Tags.time.created in list(p.tags)
    or Tags.time.printed in list(p.tags)
    or (p.name is not None and ("Revision" in p.name
        or "EditTime" in p.name or "EditingTime" in p.name)))

Exporter un rapport d’audit

Les résultats qui restent dans la console y meurent. Quatre colonnes couvrent la feuille de calcul et le cas SIEM :

with open("output/diff.csv", "w", encoding="utf-8", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["change_type", "property", "old_value", "new_value"])
    for k, v in added.items():
        writer.writerow(["added", k, "", v])
    for k, v in removed.items():
        writer.writerow(["removed", k, v, ""])
    for k, (old_v, new_v) in changed.items():
        writer.writerow(["changed", k, old_v, new_v])

Le dépôt inclut également un exportateur JSON avec un schéma à trois cartes stable pour les tableaux de bord et les API de gestion de cas.


Où cela s’exécute en pratique

Trois déploiements réapparaissent régulièrement. Les pipelines d’ingestion comparent chaque document entrant à la copie déjà enregistrée et mettent en quarantaine les paires avec des changements d’identité. Les tâches de conformité exécutent le diff selon un planning et archivèrent le CSV par paire, construisant une chronologie des propriétés que personne n’aura à reconstituer plus tard. Et les outils de résolution de litiges exécutent les deux détecteurs à la demande, car lorsqu’une réclamation survient, la question initiale est toujours qui a touché le fichier et quand, et non ce qui a changé au paragraphe quatre.

Un quatrième modèle, comparer un fichier à son propre instantané dernier connu correct, réutilise le même code avec un dictionnaire stocké d’un côté. Dans tous les cas, le fichier d’exportation est le livrable ; la sortie console n’est qu’un bruit de progression. Le modèle de code de sortie du script suit celui du main.py du dépôt, de sorte que les planificateurs et le CI traitent une assertion échouée comme une exécution échouée sans câblage supplémentaire. Aucun d’eux n’a nécessité de code au‑delà de ce que montre cette page.


Quels changements méritent d’être signalés ?

Toute chose que le diff classe, plus le contexte que vous ajoutez. Les propriétés ajoutées et supprimées méritent toujours d’être examinées car elles indiquent que la structure a changé plutôt qu’une valeur. Pour les entrées modifiées, la plupart des équipes alertent d’abord sur les groupes d’identité et de révision et traitent le reste comme informatif. Les détecteurs existent afin que le premier passage ne coûte qu’un appel de fonction.


5. Référence rapide : appels clés

Appel Ce que ça fait
Metadata(path) Ouvre le fichier ; le gestionnaire de contexte le libère à la sortie
find_properties(predicate) Renvoie chaque propriété acceptée par le prédicat, à travers toutes les couches
p.interpreted_value Valeur lisible par l’homme ; revient à p.value si nécessaire
Tags.person.* / Tags.corporate.company Classification d’identité, indépendante du format
Tags.time.* Classification d’horodatage pour le détecteur de révision

Consultez la référence API complète pour l’ensemble des possibilités de recherche et de taggage. Le vocabulaire des tags est plus vaste que ces lignes ; les groupes de tags origin, content et legal utilisent le même test d’appartenance.


6. Problèmes courants et solutions

Le diff est énorme et ressemble à du bruit
→ Les deux chemins ne sont probablement pas des révisions d’un même document. Solution : validez la provenance avant de comparer ; des fichiers non liés produisent des deltas dénués de sens.

Un champ d’auteur connu n’apparaît jamais dans le détecteur de propriété
→ Certains producteurs stockent l’identité dans des champs personnalisés non taggés. Solution : exécutez le diff complet une fois, identifiez le vrai nom de champ, puis étendez le prédicat avec une règle de nom.

La console affiche un avertissement de mode évaluation
→ Aucun fichier de licence n’a été trouvé. Solution : indiquez le LICENSE_PATH dans main.py vers votre fichier .lic, ou conservez le mode évaluation pour le développement ; la logique reste identique.

Les dates s’affichent sous forme de nombres sériaux bruts
→ Le p.value brut s’est glissé quelque part dans le lecteur. Solution : conservez le schéma interpreted_value‑first de read_props ; c’est la raison pour laquelle les rapports restent lisibles.


Prochaines étapes

Vous disposez d’un diff de métadonnées fonctionnel. Voici les prochaines étapes :

  • Batch it : bouclez le script sur des paires de documents et stockez le CSV par paire ; le coût par paire est de deux ouvertures de fichier, et les CSV se concatènent proprement pour une vue globale de la bibliothèque.
  • Schedule it : le main.py du dépôt vérifie chaque étape et renvoie un code de sortie approprié, qui s’intègre directement dans le CI ou un planificateur.
  • Parcourir la version tutoriel : le guide d’utilisation construit le même pipeline en trois tutoriels progressifs.
  • Voir le projet complet : document-version-metadata-diff-python avec la paire de révisions fournie.

Ressources