Copyright © 2003 Yves Mettier
L'article original se trouve sur http://ymettier.free.fr/articles_lmag/.
Article publié dans le numéro 51 (juin 2003) de GNU/Linux France Magazine
Table des matières
XML sans XPath est un peu ce que serait un SGBD relationnel sans le langage SQL. XPath est un langage, qui au contraire de nombreux langages relatifs à XML, n'est pas XML. Sa syntaxe n'a rien à voir avec XML. Mais son rôle est de permettre d'identifier des bouts de XML, comme un élément, un texte, un attribut... Ainsi, comme nous l'avons vu dans les autres articles, XSLT sélectionne grâce à XPath un bout de XML pour y appliquer une transformation.
Sous ce titre de paragraphe se cache la liste des types de noeuds que peut sélectionner XPath. Les voici:
Le noeud racine (attention: ce noeud contient tout le document: un noeud est bien plus qu'un élément);
les noeuds éléments;
les noeuds attributs;
les noeuds textes;
les noeuds de commentaires;
les noeuds d'instructions de traitement;
les noeuds d'espace de nom.
Pour tester les expressions XPath que vous voulez créer, nous verrons un bout de code en C avec libxml2 dans les briques de base en C, mais voici un moyen plus simple de faire:
1 <?xml version="1.0" encoding="ISO-8859-1"?> 2 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 3 <xsl:output method="text" /> 4 5 <xsl:template match="@*|node()"> 6 <xsl:value-of select='substring("XSLT XPath XML",6,5)'/> 7 <xsl:text>
</xsl:text> 8 </xsl:template> 9 10 </xsl:stylesheet>
Vous voyez que l'expression XPath doit être placée dans <xsl:value-of select='Expression XPath ici'/>
. Et pour le test, ayez un fichier XML sous la main, peu importe son contenu, ou au contraire, avec le contenu de votre choix si vous faites un test sur un jeu de noeuds, pour effectuer:
$ xsltproc test_xpath.xsl fichier.xml
Le résultat pour l'exemple précédent est:
XPath
Un document XML a une forme arborescente. Pour sélectionner l'élément <title> qui se trouve dans un document XHTML, voici un chemin XPath: /html/head/title
. Si cela vous fait penser à une arborescence de fichier Unix, c'est parce que l'on a aussi une arborescence dans le système de fichiers. L'analogie peut encore être poussée un peu plus loin, car suivant l'endroit où l'on est dans l'arborescence, on peut sélectionner un bout de XML simplement avec son nom, comme title
, ou même avec un chemin relatif head/title
. Mais l'analogie s'arrête là.
Nous avons vu comment sélectionner un élément. Pour sélectionner son contenu, il faut utiliser une localisation spéciale: text(). De la même manière, pour un commentaire, c'est comment() et pour une instruction de traitement processing-instruction(). Pour récupérer le texte du titre et non pas l'élément <title>, voici quelques exemples: /html/head/title/text()
ou title/text()
ou ... Pour les commentaires, le code suivant remplace tout commentaire par le commentaire comme quoi le précédent commentaire a été supprimé:
1 <xsl:template match="comment()"> 2 <xsl:comment>Commentaire supprimé</xsl:comment> 3 </xsl:template>
Pour sélectionner un attribut, par exemple la taille du bord d'un tableau défini par <table border="0">, il faut faire précéder le nom de l'attribut par un @. Cela donne dans ce cas: table/@border
qui doit contenir 0.
Toujours pour l'exemple précédent, et en guise d'illustration, nous pouvons rajouter une légende indiquant la taille du bord:
1 <xsl:template match="table"> 2 <!-- copier ici la table ou en faire ce qu'on veut --> 3 La taille de la bordure de la table est <xsl:value-of select="@border"/> 4 </xsl:template>
Il existe plusieurs manières d'effectuer une sélection multiple. L'une est d'utiliser le symbole | comme dans les expressions rationnelles. Pour sélectionner n'importe quel sous-titre XHTML par exemple, utilisez h1|h2|h3|h4|h5|h6
. Mais il existe aussi des jokers, un peu comme le point et l'étoile des expressions rationnelles. N'importe quel noeud d'élément, peu importe son nom, se sélectionne avec l'étoile. Ainsi, /html/body/*
sélectionnera tous les noeuds fils du noeud body dans l'arborescence d'un document XHTML. Cependant, l'étoile ne sélectionne que les noeuds d'éléments. Pour sélectionner n'importe quel noeud d'attribut, il faut utiliser @*
. Et s'il s'agit de sélectionner n'importe quel noeud, qu'il soit d'élément, d'attribut, de texte, de commentaire... de n'importe quel noeud, utilisez node()
.
Explication du chemin @*|node()
que l'on rencontre souvent: nous sommes en train de traiter le noeud courant. Si nous sélectionnons juste node()
, nous aurons tous les noeuds, attributs compris, mais fils. Nous n'aurons pas les attributs du noeud courant. C'est pour cela que nous ajoutons @*
pour sélectionner aussi les attributs du noeud courant.
Enfin, pour créer un chemin, nous avons vu / dont la signification est triviale. Mais il y a aussi . et .. dont la signification est triviale aussi: le noeud courant pour le point, et le noeud parent pour le double point. Par contre // est moins trivial! Cela permet de sélectionner parmi tous les déscendants, pas uniquement les fils du noeud qui précède le double slash. Dans un exemple précédent, nous sélectionnions tous les noeuds fils du noeud body avec /html/body/*
. Pour sélectionner non seulement les noeuds fils, mais les fils des fils et ainsi de suite, voici le chemin: /html/body//*
.
Dans les chemins précédents, nous avons utilisé l'axe par défaut, child
, qui peut être omis. Si on ne l'omet pas, voici ce que donne /html/body
: /child::html/child::body
. Nous avons vu aussi l'axe opposé, parent
, dans sa forme abrégée: ..
. Il en existe 13 en tout, que vous retrouvez dans le tableau suivant:
Tableau 1. Les axes
Axe | Forme abrégée | Explication |
---|---|---|
self | . | Noeud courant (. et self::node() sont similaires) |
parent | .. | Noeud parent (.. et parent::node() sont similaires) |
ancestor | n/a | Tout noeud contenant le noeud courant, parent et racine inclus |
ancestor-or-self | n/a | Comme ancestor , mais avec le noeud courant inclus |
child | Noeud fils (nom et child::nom sont similaires) | |
descendant | n/a | Noeuds fils et tous les noeuds dont le noeud courant est le père |
descendant-or-self | // | Comme descendant , mais avec le noeud courant inclus (//nom et descendant-or-self::nom()/ sont similaires |
preceding-sibling | n/a | Les noeuds frères précédent le noeud courant, ou plus précisément tous les noeuds pécédent le noeud courant qui ont le même noeud parent |
preceding | n/a | Comme preceding-sibling , mais sans les noeuds d'attributs et d'espace de noms |
following-sibling | n/a | Les noeuds frères suivant le noeud courant, ou plus précisément tous les noeuds suivant le noeud courant qui ont le même noeud parent |
following | n/a | Comme following-sibling , mais sans les noeuds d'attributs et d'espace de noms |
attribute | @ | Tous les noeuds d'attributs du noeud courant (@nom et attribute::nom sont similaires) |
namespace | n/a | Tous les espaces de noms du noeud courant |
Vous pouvez remarquer ainsi que sur les 13 axes possibles, seuls 5 ont une forme abrégée. Pour les 8 autres, c'est la forme complète qu'il faut prendre! Mais cela n'est pas génant, car à l'image des autres articles, l'utilisation des 8 axes qui n'ont pas de forme abrégée est plus rare.
Un prédicat est une expression booléenne, qui si elle est fausse, retire le noeud sélectionné par le chemin XPath de la liste. Cette expression se place entre crochets. Nous avons vu un chemin XPath assez simple lors de la création du site web: ./text[@lang=$lang]
. $lang est une variable, et @lang la valeur de l'attribut lang. Le chemin ./text
correspondant aux éléments fils du noeud courant dont le nom est text (à ne pas confondre avec text() s'il vous plait), est donc restreint avec [@lang=$lang] à ces même noeuds, mais que s'ils ont un attribut lang égal à la variable $lang.
Les prédicats font grand usage des fonctions XPath que nous allons voir après. En effet, les prédicats nécessitent souvent des calculs, et les fonctions XPath sont là pour cela.
Avant de commencer avec les fonctions, les VRAI et FAUX booléens qui sont habituellement des valeurs dans d'autres langages sont ici des fonctions: true()
et false()
.
La première fonction que nous allons voir est la fonction position()
qui renvoie un nombre, la position du noeud actuel dans la liste des noeuds courants.
Nous avons vu cette fonction avec les nouvelles, car on ne voulait en afficher qu'un nombre limité. Pour cela, le prédicat était [$nb_news >= position()]
.
Nous avons vu aussi dans les autres articles la fonction lang()
, qui renvoie une valeur booléenne vraie si l'argument fourni à la fonction lang() correspond à la langue du noeud courant. Cette langue s'exprime dans le noeud courant avec l'attribut xml:lang
.
not()
comme son nom l'indique, et comme dans de nombreux langages, inverse la valeur de l'argument qui lui est fourni en paramètre. Il nous a servi aussi dans les autres articles.
Nous pouvons manier les chaînes de caractères avec XPath, avec quelques fonctions:
concat()
concatène les chaînes de caractères fournies en argument et retourne le résultat. Le nombre d'arguments est quelconque;
contains()
renvoie VRAI si le second argument est une sous-chaîne de la chaîne fournie en premier argument;
normalize-space()
remplace tous les groupes de caractères blancs par une espace unique, et supprime les caractères blancs en début et fin de la chaîne fournie en argument;
start-with()
renvoie VRAI si la chaîne de caractères fournie en second argument correspond au début de la chaîne fournie en premier argument;
string()
convertit l'argument qui lui est fourni en une chaîne de caractères qu'elle renvoie;
substring()
renvoie la sous-chaîne de la chaîne de caractères fournie en premier argument, qui démarre au caractère dont l'index est fourni en second argument, et de longueur le troisième argument qui est par ailleurs facultatif
substring-after()
recherche la chaîne de caractères fournie en second argument dans celle fournie en premier argument. Si elle est trouvée, la fonction renvoie tout ce qui suit. Sinon, elle renvoie une chaîne vide;
substring-before()
recherche la chaîne de caractères fournie en second argument dans celle fournie en premier argument. Si elle est trouvee, la fonction renvoie tout ce qui précède. Sinon, elle renvoie une chaîne vide;
string-length
ne renvoie pas une chaîne de caratères, mais la longueur de celle fournie en argument;
translate()
remplace dans la chaîne fournie en premier argument tous les caractères se trouvant dans la chaîne fournie en second argument, par le caractères situé en même position dans la chaîne fournie en troisième argument. Un exemple sera plus expressif: translate("Je suis un warlordz","aelo","4310")
renvoie "J3 suis un w4r10rdz".
Les nombres, entieres ou réèls, ont aussi droit à leurs fonctions:
ceiling()
retourne le plus petit entier supérieur au nombre fourni en argument;
floor()
retourne le plus grand entier inférieur ou égal au nombre fourni en argument;
round()
retourne l'entier le plus proche du nombre fourni en argument;
number()
convertit son argument en nombre, ou en NaN si cela n'est pas un nombre;
sum()
convertit tous les noeuds fournis en arguments en nombre de la même manière que number()
, puis ajoute ces nombres, et renvoie le résultat;
count()
renvoie le nombre de noeuds dans le jeu de noeuds fourni en argument;
id()
renvoie un jeu de noeuds correspondant aux ID spécifiés en argument;
La liste de fonctions ci-dessus n'est pas exhaustive, et surtout chaque processeur XSLT peut implémenter des fonctions qui lui sont propres. Avec xsltproc, xsltproc --dumpextensions
vous affichera les extensions supportées, dont par exemple tokenize()
pour les chaînes de caractères, ou sin()
et cos()
pour les nombres.
Je voudrais revenir sur la fonction XPath qui nous a permis de retrouver toutes les langues d'un document. Cette expression était: //span[not(@xml:lang=preceding::span/@xml:lang)]/@xml:lang
. Cette expression est construite ainsi: nous recherchons tous les attributs xml:lang
des éléments span
. Ceci pourrait suffire à établir la liste des langues, mais nous nous retrouverions à coup sur avec des doublons. Il s'agit donc de supprimer les doublons avec ce que contient le prédicat. Ce prédicat teste si l'attribut courant xml:lang
est égal à un attribut xml:lang
d'un élément span
faisant partie de l'ensemble des noeuds précédent: precedent::span/@xml:lang
. Si le test renvoie vrai, c'est que nous avons un doublon. Pour avoir le bon prédicat, nous inversons donc le résultat du test avec not()
, ce qui donne le prédicat que vous retrouvez dans l'article vous montrant comment générer la page des nouvelles.
Un autre chemin XPath intéressant est celui que l'on trouve dans les briques en C pour tester les permissions:
/authorizations/group[@name=/authorizations/action[@name='visiter' and @object='planete']/authorization/@grantedto]/user/text()[.='toto']
. J'ai remplacé les %s
que l'on trouve dans l'article par des valeurs plus concrètes ici. Voyez l'article pour le format du fichier XML des permissions. Ici, voici comment la requête est construite. Nous voulons un utilisateur qui s'appelle toto: /authorizations/group/user/text()[.='toto']
. Nous voulons aussi que cet utilisateur se trouve dans un groupe dont le nom est un nom que l'on retrouve dans les actions autorisées. Nous remplaçons group
par group[@name=/authorizations/action/authorization/@grantedto]
. Enfin, ce n'est pas n'importe quelle action, mais l'action dont le nom est 'visiter' et l'objet est 'planete'. Nous remplaçons encore action
par action[@name='visiter' and @object='planete']
. Et nous aboutissons à la requète voulue. Cette requète nous renvoie donc le nom de l'utilisateur qui a justement ce nom, s'il a les permissions, et rien s'il n'y a pas de tel utilisateur. Dans l'exemple des briques en C, il n'y a pas de tel utilisateur pour effectuer une telle action, donc le résultat est vide!
XML, donc XSLT, sans XPath, n'est rien. Avec XPath, vous pouvez spécifier une partie d'un document d'un document XML qui vous intéresse, pour l'extraire comme nous le verrons avec les briques en C sur libxml2, ou pour le transformer comme nous avons vu dans les articles qui expliquent comment faire le site web.
© 2003 Yves Mettier