• Aucun résultat trouvé

6. XPath

6.5. Comparaisons

Les comparaisons sont un aspect important mais délicat de XPath. Elles jouent un rôle important en XPath car elles permettent d'affiner la sélection des nœuds en prenant en compte leurs contenus. Il est, par exemple, possible de sélectionner des éléments d'un document dont la valeur d'un attribut satisfait une condition comme dans les expressions item[@type='free'] et list[@length < 5]. Les comparaisons sont aussi délicates à utiliser car leur sémantique est source de pièges conduisant aisément à des programmes erronés.

Il existe deux types d'opérateurs pour effectuer des comparaisons entre valeurs. Les premiers opérateurs dits généraux datent de la première version de XPath. Ils permettent de comparer deux valeurs quelconques, y compris des listes, avec des résultats parfois inattendus. Les seconds opérateurs ont été introduits avec la version 2.0 de XPath. Ils autorisent uniquement les comparaisons entres les valeurs atomiques de même type. Ils sont plus restrictifs mais leur comportement est beaucoup plus prévisible.

Il existe aussi l'opérateur is et les deux opérateurs << et >> permettant de tester l'égalité et l'ordre de nœuds dans le document.

6.5.1. Opérateurs de comparaisons atomiques

Les opérateurs de comparaison pour les valeurs atomiques sont les opérateurs eq, ne, lt, le, gt et ge. Ils permettent respectivement de tester l'égalité, la non-égalité, l'ordre strict et l'ordre large (avec égalité) entre deux valeurs de même type. L'ordre pour les entiers et les flottants est l'ordre naturel alors que l'ordre pour les chaînes de caractères est l'ordre lexicographique du dictionnaire. Cet ordre lexicographique prend en compte les collations.

2 ne 3 donne true 2 lt 3

donne true

'chaine' ne 'string' donne true

'chaine' lt 'string' donne true

Ces opérateurs de comparaison exigent que leurs deux paramètres soient du même type. Les constantes présentes dans le programme sont automatiquement du bon type. En revanche, les contenus des éléments et les valeurs

XPath

des attibuts doivent être convertis explicitement à l'aide des fonctions de conversion lorsque le document est traité indépendamment d'un schéma. Pour tester si la valeur d'un attribut pos vaut la valeur 1, il est nécessaire d'écrire xsd:integer(@pos) eq 1 où la valeur de l'attribut pos est convertie en entier par la fonction xsd:integer. Les opérateurs généraux de comparaison évitent ces conversions fastidieuses car ils effectuent eux-mêmes des conversions implicites.

La contrainte d'égalité des types des valeurs n'est pas stricte. Il est possible de comparer une valeur d'un type avec une valeur d'un type obtenu par restriction [Section 5.9]. Il est également possible de comparer des valeurs des différents types numériques xsd:integer, xsd:decimal, xsd:float et xsd:double.

6.5.2. Opérateurs généraux de comparaisons

Les opérateurs généraux de comparaison sont les opérateurs '=', '!=', '<', '<=', '>' et '>='. Ils permettent respectivement de tester l'égalité, la non-égalité, l'ordre strict et l'ordre large de deux valeurs de types quelconques.

Les objets à comparer sont d'abord atomisés [Section 6.1.1.1], ce qui signifie que les nœuds présents dans les listes sont remplacés par leur valeur pour obtenir uniquement des valeurs atomiques. Ensuite, la comparaison est effectuée de façons différentes suivant que les objets sont des listes composées d'une seule valeur (considérées alors comme une simple valeur) ou de plusieurs valeurs.

6.5.2.1. Comparaisons de valeurs atomiques

La façon de réaliser une comparaison entre deux valeurs atomiques dépend du type [Section 6.1.4] de ces deux valeurs. Suivant les types de celles-ci, certaines conversions sont effectuées au préalable puis elles sont comparées avec l'opérateur de comparaison atomique correspondant donné par la table suivante.

Opérateur général Opérateur atomique

= eq

!= ne

< lt

<= le

> gt

>= ge

Lorsque les deux valeurs sont de type xsd:untypedAtomic, celle-ci sont comparées comme des chaînes de caractères. Lorsqu'une seule des deux valeurs est de type xsd:untypedAtomic, celle-ci est convertie dans le type de l'autre valeur avant de les comparer. Quand le type de l'autre valeur est un type numérique, la valeur de type xsd:untypedAtomic est convertie en une valeur de type xsd:double plutôt que dans le type de l'autre valeur. Ceci évite qu'une valeur décimale comme 1.2 soit convertie en entier avant d'être comparée à la valeur 1. Si les deux valeurs sont de types incompatibles, la comparaison échoue et provoque une erreur.

Pour illustrer ces comparaisons, on considère le petit document suivant.

<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>

<list>

<item type="1">1</item>

<item type="01">2</item>

<item type="03">3</item>

<item type="1.2">4</item>

<!-- Erreur car 'str' ne peut être convertie en nombre flottant -->

<item type="str">5</item>

</list>

Les expressions suivantes sont évaluées sur le document précédent en supposant, à chaque fois, que le nœud courant est l'élément racine list du document. Pour chacune des expressions, le résultat est une liste d'enfants

XPath

item de l'élément list. Il est décrit en donnant les positions de ces éléments item sélectionnés. Le résultat item[1], item[2] de la première expression signifie, par exemple, qu'elle sélectionne le premier et le deuxième enfants item.

item[@type=1]

donne item[1], item[2] car la valeur de l'attribut type est convertie en nombre flottant avant d'être comparée à 1. La valeur '01' est donc convertie en '1'.

item[@type='1']

donne item[1] car la valeur de l'attribut type est convertie en chaîne de caractères avant d'être comparée à '1'.

item[@type=.]

donne item[1] car la valeur de l'attribut type et le contenu de l'élément item sont convertis en chaînes de caractères avant d'être comparés.

item[@type=1.2]

donne item[4] car la valeur de l'attribut type est convertie en nombre flottant avant d'être comparée à 1.2. item[xsd:double(.)=xsd:double(@type)]

donne item[1], item[3] car la valeur de l'attribut type et le contenu de l'élément item sont convertis en nombres flottants avant d'être comparés.

Il faut faire attention au fait que les comparaisons peuvent échouer et provoquer des erreurs lorsque les types ne sont pas compatibles. Ce problème renforce l'intérêt de la validation des documents avant de les traiter.

6.5.2.2. Comparaisons de listes

Les comparaisons entre listes ont une sémantique très particulière qui est parfois pratique mais souvent contraire à l'intuition. Ce cas s'applique dès qu'un des deux objets comparés est une liste puisqu'une valeur est identifiée à une liste de longueur 1. La comparaison entre deux listes l1 et l2 pour un des opérateurs =, !=, <, <=, > et >=

est effectuée de la façon suivante. Chaque valeur de la liste l1 est comparée avec chaque valeur de la liste l2 pour le même opérateur, comme décrit à la section précédente. Le résultat global est égal à true dès qu'au moins une des comparaisons donne la valeur true. Il est égal à false sinon. Cette stratégie implique en particulier que le résultat est false dès qu'une des deux listes est vide quel que soit l'opérateur de comparaison.

() = ()

donne false car une des deux listes est vide.

() != ()

donne false car une des deux listes est vide.

L'exemple précédent montre que l'opérateur != n'est pas la négation de l'opérateur =, ce qui n'est pas très intuitif.

() != (1)

donne false car une des deux listes est vide.

(1) = (1)

donne true car la valeur 1 de la liste l1 est égale à la valeur 1 de la liste l2.

(1) != (1)

donne false car l'unique valeur 1 de la liste l1 n'est pas différente de l'unique valeur 1 de la liste l2. (1) = (1, 2)

donne true car la valeur 1 de la liste l1 est égale à la valeur 1 de la liste l2.

(1) != (1, 2)

donne true car la valeur 1 de la liste l1 est n'est pas égale à la valeur 2 de la liste l2.

Dès que la comparaison de deux valeurs des listes l1 et l2 échoue, la comparaison globale entre les listes l1 et l2 échoue également. L'ordre des comparaisons entre les valeurs des deux listes est laissé libre par XPath et

XPath

chaque logiciel peut les effectuer dans l'ordre qui lui convient. Lorsqu'une de ces comparaisons donne la valeur true et qu'une autre de ces comparaisons échoue, le résultat de la comparaison des deux listes est imprévisible.

Il est égal à true si une comparaison donnant true est effectuée avant toute comparaison qui échoue mais la comparaison globale échoue dans le cas contraire.

La sémantique des comparaisons de listes permet d'écrire simplement certains tests. Pour savoir si une valeur contenue, par exemple, dans une variable $n est égale à une des valeurs de la liste (2, 3, 5, 7), il suffit d'écrire $n = (2, 3, 5, 7).

6.5.3. Opérateurs de comparaisons de nœuds

L'opérateur is compare deux nœuds et retourne true s'il s'agit du même nœud. C'est donc plus un test d'identité que d'égalité. Il s'apparente plus à l'opérateur '==' de Java qu'à la méthode equals du même langage.

Les deux opérateurs '<<' et '>>' permettent de tester si un nœud se trouve avant ou après un autre nœud dans l'ordre du document [Section 6.1.2].

Pour illustrer ces trois opérateurs, on considère le document minimaliste suivant.

<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>

<list length="2">

<item type="1">1</item>

<item type="1">2</item>

</list>

Les expressions suivantes sont évaluées sur le document précédent en supposant, à chaque fois, que le nœud courant est la racine du document.

list is list/item/parent::*

donne true car il s'agit du même nœud qui est l'élément racine du document.

list/item[1]/@type is list/item[2]/@type

donne false car il s'agit de deux nœuds différents bien que les deux attributs aient même nom et même valeur.

list << list/item[1]

donne true car le père est placé avant ses enfants dans l'ordre du document.

list/@length << list/item[1]

donne true car les attributs sont placés avant les éléments enfants.

list/item[1] << list/item[2]

donne true.

Un exemple pertinent d'utilisation de l'opérateur is est donné par la feuille de style avec indexation [Section 8.12]

pour regrouper les éditeurs et supprimer leurs doublons dans le document bibliography.xml.