Recherche rapide d’informations grâce à XPath

22/10/2004

Avec l’avènement de XML, de nombreuses informations se retrouvent dans des fichiers au format texte structuré de façon arborescente, qu’il s’agisse de fichiers de configuration, de données à proprement parler ou de résultats de requête. Si la structure XML apporte lisibilité et robustesse, le problème d’accès à l’information demeure. Nous allons voir comment XPath y apporte une réponse vraiment puissante.

Magazine Login: n°120, septembre 2004 – Frédéric Laurent

De nombreuses techniques permettent de retrouver une valeur noyée au plus profond d’un arbre XML, mais elles ne sont pas toutes aisées à mettre en oeuvre. XPath, langage non XML, spécifié par le W3C, permet de déclarer de façon concise l’information recherchée. Le travail du programmeur est allégé en terme de code mais aussi de maintenance. Il peut en effet se concentrer uniquement sur la formulation de sa requête (le quoi) et non sur la façon d’y accéder (le comment).

Pour illustrer l’article, nous définissons deux types d’information à retrouver dans deux fichiers XML différents. Tout d’abord, nous souhaitons connaître la liste des servlets déclarées dans un fichier de configuration d’une application web J2EE (le fichier web.xml). Les noms de servlet sont définis par la balise <servlet-name>. Le fichier ne contient pas d’espace de noms. L’expression sera donc très simple. Le second exemple, en revanche, est plus complexe. A partir du fichier d’actualités du W3C (au format RSS 1.0, c’est à dire avec le support des espaces de noms), nous souhaitons connaître les annonces des nouveaux documents de travail. Ce qui revient à localiser le texte des balises <description> dont les balises adjacentes <title> contiennent le mot “Draft”. Cet exemple utilise donc les concepts d’axe XPath, d’espace de noms et de fonction XPath.

Différents types d’accès

Plusieurs techniques sont envisageables pour accéder à une information contenue dans un fichier XML. On peut citer la recherche de texte à l’aide des expressions régulières, l’API SAX, le modèle DOM, les librairies de correspondance (désigné par le terme anglais “binding”) XML vers des objets de n’importe quel langage de programmation, et bien sûr XPath.

Utilisation des expressions régulières

Un fichier XML est un fichier texte. Pendant longtemps, l’utilisation des expressions régulières a fait le bonheur des développeurs devant trouver des données dans ces fichiers texte. Cette technique est donc parfaitement applicable pour retrouver notre liste de servlets. En python, une première version naïve serait la suivante :

servletname="<servlet-name>(.*?)</servlet-name>"
webxml = open("WEB-INF\\web.xml").read()
print re.findall(servletname, webxml)
['invoker', 'CompressionFilterTestServlet', 'HelloWorldExample',
 'RequestInfoExample', 'RequestHeaderExample', 'RequestParamExample',
 'CookieExample', 'SessionExample', 'CompressionFilterTestServlet',
 'HelloWorldExample', 'RequestInfoExample', 'RequestHeaderExample',
 'RequestParamExample', 'CookieExample', 'SessionExample']

Pour ce cas simple, la solution semble parfaite. Cependant, il faudra penser à traiter les retours à la ligne entre la balise ouvrante et le texte à proprement dit. Le code se complexifie alors. Mais surtout comment repérer que <servlet-name>invoker</servlet-name> est déclaré ou ne l’est pas car compris entre un début et une fin de commentaire (resp. <!-- et -->) ? Complexifier encore l’expression régulière ? Ajouter du code pour contrôler cet aspect ? Faire une première lecture pour supprimer toutes les parties XML contenues dans des commentaires et travailler sur cette version épurée ? Les solutions sont multiples, mais elles présentent toutes de nombreux inconvénients. En occultant la sémantique du langage XML, le travail devient complexe et difficilement maintenable. Et que dire de notre second exemple…

Utilisation de SAX

SAX permet de répondre de façon plus intéressante que les expressions régulières car le format XML est traité de façon adaptée : les problèmes d’espaces entre balise et texte, ou de balises entre commentaires se résolvent d’eux-mêmes. Si SAX fournit un moyen rapide d’accéder à l’information puisqu’il est possible de ne prendre en compte que les données qui nous intéressent, il n’en demeure pas moins qu’il faut construire un programme pour trouver les différents noms de servlet. La classe dédiée peut être relativement simple, elle a cependant le principal inconvénient de fixer dans le programme la façon d’accéder à l’information. Si le format évolue, le programme doit être changé, recompilé, redéployé (selon le cas, et le langage).

Utilisation de DOM

L’API DOM, spécifiée par le W3C, permet également d’accéder aux informations des fichiers XML. Une fois le document lu en mémoire dans une structure de données standard, l’accès se fait par l’appel aux méthodes définies sur les classes Document, Element, Attribute… A l’image de SAX, cette solution exploite pleinement la spécificité du format XML et règle nombre de problèmes. Cependant, elle ne résout pas la dépendance entre le code du programme et le format du fichier XML.

S’il existe des alternatives à DOM, comme JDOM ou DOM4J par exemple, qui rendent le code plus concis et plus lisible, les inconvénients exposés ci-dessus persistent.

Utilisation du binding

Il s’agit d’obtenir une représentation du fichier XML sous forme de classe. L’accès est simple, cependant encore une fois, la consultation d’une information impose la génération de code (Java, Python ou autre), sa compilation éventuelle, son déploiement… Tout cela étant sujet à modification, et donc difficilement maintenable. De plus, pour accéder à la liste des noms de servlet, il faut coder explicitement l’accès par des appels à des accesseurs de la classe générée.

Utilisation de XPath

XPath : un langage pour intérroger les fichiers XML

La spécification XPath est assez simple. Peu de notions permettent de composer une expression qui localisera, dans le document XML, une simple valeur, ou un ensemble de noeuds. Une expression XPath définit …(lire la suite)

Face à tous ces problèmes de couplage fort entre le format XML et le code du programme, XPath apporte une réponse vraiment satisfaisante. La requête d’accès à l’information est la seule chose à définir. Le codage de l’affichage du résultat reste indépendant (des informations recherchées). Si la structure du fichier source XML change, seule l’expression XPath doit évoluer. Qui plus est, les instructions qui exploitent le résultat de l’évaluation sont vraiment simples. Il s’agit d’un parcours d’un ensemble de noeuds pour les résultats multiples (ensembles de noeuds, ou "node-set") ou la consultation d’une valeur. Enfin, l’utilisation de cette technologie permet également d’obtenir le résultat en tant que noeud DOM d’un document. Lui ajouter un nouveau noeud fils, un attribut ou du texte est donc une formalité. XPath ne se cantonne donc pas à la seule consultation d’information, mais fournit un véritable moyen d’accès à une partie du document XML. Libre au programmeur d’utiliser le résultat comme bon lui semble.

XPath et la gestion des espaces de noms

Lors de la formulation d’une requête XPath, chaque espace de noms doit être explicitement nommé. Chaque élément doit pouvoir être préfixé… (lire la suite)

L’apparition d’une spécification claire pour l’utilisation d’API XPath s’est faite attendre. La conséquence immédiate est le développement par chaque librairie XML de sa propre API. Ainsi pour Java, Saxon, JDOM, Xalan, DOM4j,… fournissent ce service de façon propriétaire. Le W3C progresse sur une recommandation (qui n’est encore qu’un document de travail), et les différents projets comblent peu à peu ce manque de cohérence. Nous allons voir comment accéder aux informations désirées en utilisant d’une part Saxon et d’autre part l’API standard XPath (uniquement implémentée par Xalan, à ce jour).

Le premier exemple permet de manipuler une expression XPath triviale, puisqu’il s’agit d’obtenir la valeur textuelle de tous les noeuds <servlet-name>. L’expression //servlet-name/text() convient parfaitement. Le second exemple est plus ardu. Une des expressions XPath utilisable (il est possible de fournir de nombreuses autres solutions pour accéder à la même information) est :

//dns1:description[contains(preceding-sibling::dns1:title,'Draft')]/text()

ce qui peut se traduire en français par : prendre le texte de toutes les balises <description> (dans l’espace de noms dns1) qui contiennent un noeud frère nommé <title> (dans l’espace de noms dns1) et dont le contenu contient le mot ‘Draft’.

XPath avec Saxon

Le listing 1 présente comment utiliser Saxon pour obtenir notre liste de servlets. Le programme se contente de lire un fichier XML et d’évaluer l’expression XPath. Une fois le fichier lu (depuis le système de fichiers ou depuis une URL), l’objet XPathEvaluator de l’API Saxon est créé avec la référence sur le flux XML. Il servira à évaluer les requètes. Ce service est rendu par la méthode evaluate(). Elle renvoie un objet du type Java classique [List][23] qu’il suffit de parcourir pour afficher l’ensemble des résultats. Saxon fournit pour la représentation des noeuds, une classe [NodeInfo][24], qui offre contrairement à la classe [Node][25] du W3C, une méthode getStringValue() permettant d’en avoir une représentation textuelle. L’affichage est donc grandement simplifié.

Le listing 2 correspond au second exemple. Il est plus complexe puisqu’il faut gérer les espaces de noms. Chaque couple (préfixe, URI de l’espace de noms) doit être déclaré au niveau de l’évaluateur XPath. Enfin, les espaces de noms par défaut spécifiés dans le document XML (par une déclaration xmlns="http://...") doivent également posséder un préfixe au niveau XPath. Ainsi, la constante DEFAULT_NS_PREFIX définit la valeur textuelle du préfixe à laquelle un entier, incrémenté automatiquement, est accolé. Il n’existe pas de moyen prédéfini pour collecter les espaces de noms d’un document XML. Deux solutions relativement simples sont envisageables. La première consiste à utiliser la récursivité de la structure XML et les méthodes d’accès aux informations fournies par DOM. Le parcours récursif teste tous les attributs de tous les noeuds Element afin de trouver ceux qui commencent par "xmlns". La seconde solution est basée sur l’analyse syntaxique SAX en se concentrant uniquement sur les espaces de noms. C’est cette solution qui est retenue dans notre exemple. Ainsi, avant de fournir le flux XML à l’objet [XPathEvalutor][26], il est analysé une première fois par une classe dérivant du [DefaultHandler][27] de SAX 2. Seul le signal startPrefixMapping(prefix,uri) est utilisé. Cette méthode voit passer chaque déclaration d’espace de noms, elle se charge de l’enregistrer dans un objet [StandaloneContext][28] prévu, entre autres, à cet effet dans l’API Saxon.

Utilisation de l’API XPath DOM niveau 3

Les exemples suivants se basent sur des abstractions d’API. Le but recherché est bien sûr d’être indépendant de toute implémentation, afin d’offrir une pérennité et une maintenabilité plus grandes. La première abstraction concerne la lecture du document. Les documents XML sont lus en utilisant l’API JAXP. Définie par Sun, elle permet de ne pas se préoccuper de l’analyseur syntaxique sous-jacent. L’évaluation XPath se fait en utilisant l’ensemble des classes spécifiées par le groupe de travail DOM du W3C. Ainsi, même si actuellement seule Xalan implémente ce document de travail, l’utilisation de l’évaluateur XPath est indépendante. Dès qu’une autre librairie supportera XPath DOM niveau 3, il sera trivial de remplacer Xalan. Le code de l’exemple n’aura pas besoin d’évoluer (on notera d’ailleurs que les imports utilisés ne dépendent pas de Xalan). Il reste cependant un bémol à cette indépendance. Le document est très jeune, et l’implémentation de Xalan n’est pas finalisée. Ainsi, la création de l’évaluateur XPath est un peu différente de ce qu’elle devrait être (et de ce qu’elle sera dans les mois à venir). En effet, elle est effective en faisant appel explicitement à une classe de la librairie Xalan.

XPathEvaluator evaluator = new org.apache.xpath.domapi.XPathEvaluatorImpl(this.doc);

Dans la version finale, il faudra utiliser le mécanisme d’interrogation (les méthodes isSupported() et getFeature() de l’interface Node) de l’implémentation afin de choisir la bonne façon de créer l’objet XPathEvaluator.

Interroger une implémentation indirectement…

La recommandation DOM Niveau 3 introduit un mécanisme permettant d’agir selon les réponses d’une implémentation… (lire la suite)

Enfin, l’écriture sur la sortie standard du résultat de l’évaluation est également indépendante de toute librairie, puisque c’est une transformation XSLT identité qui l’effectue (la transformation identité prend en entrée un document XML et produit en sortie le même document XML).

Le listing 3 permet d’illustrer l’évaluation d’expressions simples, sans espaces de noms. La méthode evaluate() de l’XPathEvaluator produit un résultat qui est parcouru élément par élément. Chaque élément est affiché sur la sortie standard, par un simple println dans le cas d’un noeud Texte, ou par la transformation identité s’il s’agit d’un fragment de document.

Le listing 4 se charge de traiter les espaces de noms. L’analyse des déclarations d’espace de noms se fait de façon similaire à celle présente dans le listing 2. Par contre, la résolution des préfixes se fait de façon différente. Lors de la lecture du document source, un nouveau document DOM est créé. Son seul but est de porter l’ensemble des déclarations d’espace de noms. On peut se demander pourquoi créer un nouveau document, alors que le document RSS du W3C porte déjà toutes les déclarations sur son noeud racine (<rdf:RDF>). Pourquoi alors ne pas l’utiliser directement ? La raison est simple et double: le fichier RSS n’est qu’un exemple et il s’agit donc d’un cas particulier qui ne fonctionne que dans la mesure ou toutes les déclarations sont faites au niveau le plus haut. Si un nouvel espace de noms (par défaut ou préfixé) est déclaré dans les profondeurs de l’arbre, il ne sera pas pris en compte. De plus, cette méthode permet d’associer, de façon claire, un préfixe pour chaque espace de noms par défaut.

Un évaluateur XPathNSResolver est créé par la méthode createNSResolver(). Il sera consulté à chaque fois que le moteur d’évaluation rencontrera un préfixe et sera chargé de fournir l’URI correspondante. Cet objet est construit à partir du document portant toutes les déclarations, mais une autre méthode pourrait être employée. Elle est sans doute plus simple, mais la jeunesse de l’implémentation la rend indisponible pour l’instant. Le programmeur pourra fournir lui-même une classe implémentant XPathNSResolver. Elle ne rendra qu’un seul service (lookupNamespaceURI(String prefix)): donner l’URI exacte correspondant au préfixe qu’elle reçoit en paramètre. Pour autant, la phase de collecte des espaces de noms ne sera pas supprimée, mais la création du document transitoire pourra être évitée puisque cette classe fournira le renseignement.

Pour conclure…

L’utilisation de XPath n’est certes pas encore complètement standard, mais l’arrivée de la recommandation du W3C risque d’accélérer encore le processus. Cela dit, il ne s’agit là que de questions d’abstraction. Si l’indépendance vis-à-vis d’une librairie particulière n’est pas rédhibitoire, vous avez un large panel d’outils déjà disponibles vous permettant d’exploiter pleinement XPath dans vos programmes. L’intégrer sera d’autant plus intéressant que le nombre de requêtes sera important. Avec une classe utilitaire (helper class), l’accès aux informations de vos fichiers XML gagne en efficacité, maintenance, compréhension. Plus de code à maintenir mais simplement des chaînes de caractères décrivant les requêtes. Voila tout l’intérêt d’utiliser XPath dans vos programmes.

Les sources de l’article