Trier des éléments XML par valeur d’attribut

Les fichiers XML produits automatiquement peuvent être peu lisibles pour l’œil humain. Assez souvent, ce n’est pas forcément un problème puisqu’il est tout à fait possible que l’exploration et/ou le traitement des fichiers soient également effectués de façon automatique.

Mais dans certains cas, il peut être appréciable (voire nécessaire) de parcourir manuellement le contenu d’une arborescence XML : pour vérifier rapidement la cohérence des contenus d’un corpus, présenter les données ou un extrait des données à un public, apporter des corrections manuelles…

L’une des modifications qui n’a quasiment aucun impact sur les données à proprement parler mais peut faciliter le confort de lecture est de modifier l’ordre des éléments XML, par exemple suivant la valeur de l’un ou l’autre de leurs attributs.

Le script utilisé ne sera pas reproduit intégralement dans l’article. Vous pouvez le télécharger librement sur Github et Gitlab, accompagné d’exemples de fichiers d’entrée et sortie.

Pour rappel, 3 articles ont déjà été consacrés à la constitution et à la modification de corpus textuels au format XML :

  1. Produire un corpus web format XML en 15 lignes de code = Une méthode très simple et rapide pour constituer un corpus XML basique à partir de données de sites web.
  2. Déclaration XML automatique = Comprendre comment fonctionne et maîtriser l’ajout automatique de la déclaration XML lors de la constitution du corpus.
  3. Lire (et modifier) un fichier XML avec Python = Lire les contenus d’un fichier au format XML et ajouter des données ou métadonnées.

Si vous connaissez peu le XML ou Python, la lecture de ces articles devrait vous permettre de comprendre ce qui sera expliqué ici.

Pourquoi trier les éléments par attribut ?

Cela peut être utile dans des situations diverses, mais voici un exemple concret. Dans le cadre de mon travail, j’ai scrapé des données sur des pages web en français et en japonais pour construire un corpus bilingue parallèle. Pour certaines raisons, le XML obtenu lorsque les données sont extraites comporte successivement le texte intégral de chaque version. Soit d’abord tous les éléments textuels en français, puis tous les éléments textuels en japonais correspondants.

Ce n’est pas un problème pour réaliser des traitements automatiques, puisque j’avais pourvu chacun de ces éléments d’un attribut numérique num permettant de les appairer (num identique pour les 2 éléments de la paire de segments de texte équivalents). Mais comme le processus de constitution de ce corpus était encore assez incertain, il m’arrivait fréquemment de faire des vérifications manuelles.

Il était bien plus facile pour évaluer au premier coup d’œil la correspondance entre un élément français et son équivalent en japonais de les rassembler, c’est-à-dire de les trier sur la base de leur attribut num .

Le script Python

Le traitement proposé ici est très proche de ce qui était montré dans l’article expliquant comment lire et modifier automatiquement un fichier XML. Ce qui est assez logique, s’agissant cette fois encore d’apporter une modification à une arborescence XML préexistante.

Fichier d’entrée

Les données prises entrées par le script téléchargeable sont issues d’un fichier XML que j’ai généré à partir du contenu de ce tableau.

Il a la structure suivante :

<LEXIQUE>
    <element type="kanji" id="1">付録</element>
    <!-- Ensemble des éléments de type "kanji" (id "2" à "46") -->
    <element type="kana" id="1">ふろく</element>
    <!-- Ensemble des éléments de type "kana" (id "2" à "46") -->
    <element type="romaji" id="1">furoku</element>
    <!-- Ensemble des éléments de type "romaji" (id "2" à "46") -->
    <element type="français" id="1">appendice</element>
    <!-- Ensemble des éléments de type "français" (id "2" à "46") -->
</LEXIQUE>

La racine LEXIQUE contient des element correspondant à la liste de tous les kanji, puis toutes les transcriptions en kana, puis toutes les transcriptions en rōmaji, puis toutes les traductions françaises. Nous allons trier les éléments sur la base de leur attribut id pour rassembler les éléments correspondant à un même item lexical pour aboutir à une structure semblable à celle-ci :

<LEXIQUE>
    <element type="kanji" id="1">付録</element>
    <element type="kana" id="1">ふろく</element>
    <element type="romaji" id="1">furoku</element>
    <element type="français" id="1">appendice</element>
    <element type="kanji" id="2">辞典</element>
    <element type="kana" id="2">じてん</element>
    <element type="romaji" id="2">jiten</element>
    <element type="français" id="2">dictionnaire, lexique</element>
    <!-- Suite des items (id "3" à "46") -->
</LEXIQUE>

Comme dans la situation décrite tout à l’heure, modifier l’ordre des éléments dans le fichier n’a globalement aucune utilité pour traiter automatiquement le fichier. Il est extrêmement simple de reconstituer les items lexicaux en exploitant l’attribut id. Par exemple, pour les afficher sur la console :

for i in range(1, 47):
    item = [element.text for element in root.findall("element") if element.attrib["id"] == f"{i}"]
    print("; ".join(item))
付録; ふろく; furoku; appendice
辞典; じてん; jiten; dictionnaire, lexique
英和辞典; えいわじてん; eiwa jiten; dictionnaire anglais-japonais
英英辞典; えいえいじてん; eiei jiten; dictionnaire anglais-anglais
仏和辞典; ふつわじてん; futsuwa jiten; dictionnaire français-japonais
和仏辞典; わふつじてん; wafutsu jiten; dictionnaire japonais-français
辞書; じしょ; jisho; dictionnaire, lexique
辞書形; じしょけい; jishokei; forme de citation
電子辞書; でんしじしょ; denshi jisho; dictionnaire électronique
見出し; みだし; midashi; entrée (de dictionnaire), table des matières
省略; しょうりゃく; shōryaku; abréviation

(...)

La réorganisation des éléments n’a dans ce cas d’intérêt que face à un observateur humain, pour intervenir manuellement sur le fichier ou disposer d’un plus grand confort de lecture, notamment s’il a vocation à être partagé.

En entrée : fichier ou arborescence ?

Si j’ai évoqué précédemment la modification d’une arborescence plutôt que d’un fichier, ce n’est pas anodin. Il n’est en effet pas du tout indispensable que la structure XML soit écrite dans un fichier pour la manipuler.

Je fournis ici un fichier d’entrée par commodité, mais en conditions réelles il serait tout à fait possible de manipuler une arborescence XML non issue d’un fichier, par exemple générée dans une étape antérieure du même script1. L’important est de disposer d’une arborescence XML sous forme d’un objet de classe xml.etree.ElementTree.ElementTree. Dans l’exemple il est appelé tree et comme d’habitude lors de la lecture d’une structure XML avec ElementTree, la première chose à faire est d’associer sa racine à une variable, ici nommée root.

tree = ET.parse(input_file)
root = tree.getroot()

Pour manipuler la structure XML, plus besoin de tree, il faut maintenant utiliser root.

Classer les éléments XML

Le traitement à proprement parler tient en fait en 3 lignes de code, que voici :

elements = root.findall("element")
elements_tries = sorted(elements, key=lambda element: int(element.attrib["id"]))
root[:] = elements_tries

Ces lignes correspondent respectivement aux 3 étapes suivantes :

  1. Récupération de la liste des éléments à trier. Dans l’arborescence prise en entrée les éléments XML s’appellent element et ont pour parent la racine root, d’où root.findall("element") pour les extraire. La variable element correspond maintenant à une liste contenant l’ensemble des element, dans leur ordre d’origine.
  2. Classement des éléments selon la valeur de leur attribut id. La fonction .sorted() prend en argument la liste d’éléments à trier (ici elements) et a également un paramètre key qui permet de spécifier le critère de tri à appliquer. Dans le cas présent, ce critère est l’attribut id. Attention, si l’attribut id se présente sous forme de chiffres et nombres, il s’agit en fait d’une chaîne de caractères : cet attribut ne peut pas être utilisé tel quel comme clé de tri, sinon l’ordre sera faussé. Il est indispensable de convertir la valeur en id en entier, avec int()2.
  3. Remplacement du contenu de root par la liste d’éléments triés. Le contenu de root est intégralement supprimé et remplacé par le contenu de la variable elements_tries. Ici ça fonctionne tout à fait, puisque les element qui ont été triés dépendaient directement de la racine root. Cependant, si vous avez classé des éléments contenus dans un sous-élément, c’est le contenu de ce dernier qu’il faut remplacer et non celui de la racine.

La variable root contient maintenant une arborescence dans laquelle les éléments ont été triés.

Fichier de sortie

Je ne détaillerai pas la façon de produire le fichier de sortie, des explications sont déjà disponibles dans les articles précédents.

Voici juste quelques précisions qui concernent plus particulièrement les traitements effectués ici.

  • Entre le début et la fin du script, la structure XML contenue dans root a été modifiée. Si le XML d’origine provient d’un fichier d’entrée, pour en conserver une copie il suffit d’utiliser un chemin d’accès différent pour le fichier de sortie. Le fichier d’entrée restera inchangé, et le fichier de sortie contiendra la nouvelle arborescence avec les éléments triés.
  • S’il n’y avait pas de fichier d’entrée, vous pouvez générer 2 fichiers de sortie. Pour écrire un fichier de sortie contenant l’arborescence XML sans tri, il est bien sûr impératif de le faire avant de réorganiser le contenu de root3, soit avant la ligne root[:] = elements_tries.
  • Sinon, vous pouvez tout à fait vous passer de fichier de sortie si vous n’en avez pas besoin. Une solution serait de simplement conserver dans une variable supplémentaire une copie non altérée de la structure XML d’origine, afin d’obtenir au final une variable correspondant à l’arborescence non triée, et une autre correspondant à l’arborescence triée.
Citer ce billet : “Trier des éléments XML par valeur d’attribut”, par Alexander Delaporte. Publié sur Tekipaki le 29/03/2022. Lien : https://tekipaki.hypotheses.org/2278.
  1. Vous trouverez dans cet article des explications vous permettant de construire votre propre arborescence XML avec Python. []
  2. Si element.attrib["id"] était utilisé à la place de int(element.attrib["id"]), le classement se ferait en ordre alphabétique et placerait donc les element dans cet ordre : 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21… []
  3. Ou autre élément parent des éléments à classer. []

Auteur/autrice : Alexander Delaporte

Ingénieur d'études au Centre de recherches linguistiques sur l'Asie Orientale

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.