Qu'est-ce qu'un "result tree fragment"? Qu'est-ce qu'un "node set"? Comment passer de l'un à l'autre?
Eric van der Vlist, Dyomedea (vdv@dyomedea.com).
mercredi 13 octobre 2004
Table des matières
Introduction
Source et résultat : deux types d'arbres très différents
Node sets et result tree fragments
Node sets
Result tree fragment
Peut-on éviter les result tree fragments?
Pour les littéraux
Pour des résultats intermédiaires
Variables conditionnelles
Conversion de result tree fragments en node sets
Et XSLT 2.0
Références
Introduction
Si vous vous posez cette question, c'est sans doute que vous avez été confronté à une erreur du type, "cannot convert to node-set" (XT), "java.lang.ClassCastException" (Xalan) ou "text copy failed" (libxslt).
Pour comprendre pourquoi vous obtenez cette erreur, il faut revenir sur la dissymétrie avec laquelle XSLT 1.0 traite les arbres source et résultat.
Source et résultat : deux types d'arbres très différents
XSLT 1.0 traite les arbres source et résultat de manière très différente.
L'arbre source, c'est à dire celui qui représente le document à transformer, est accessible en lecture seule et en accès aléatoire puisque l'on peut, grâce à XPath, accéder aux noeuds qui le composent dans l'ordre que l'on souhaite.
L'arbre résultat, c'est à dire celui qui représente le document résultant de la transformation, est au contraire accessible en écriture seule et en accès "séquentiel" puisqu'on ne peut insérer de noeud qu'à la suite de ceux qui ont déjà été insérés.
Node sets et result tree fragments
On retrouve cette distinction dans la manière dont on peut définir une variable ou un paramètre dans une transformation XSLT.
Node sets
Si j'écris :
<xsl:variable name="ma.variable" select="mon/chemin"/>
ou
<xsl:variable name="ma.variable" select="document('externe.xml')/
mon/chemin"/>
la variable "ma.variable" désigne un ensemble de noeuds présents dans un arbre source et il s'agit donc d'un "node set" (ensemble de noeuds).
L'accès à cette variable se fait donc sur le même mode (lecture seule, accès aléatoire) que l'accès à l'arbre source et ont peut accéder aux noeuds composant ce node set en utilisant des expressions XPath :
<xsl:value-of select="$ma.variable/@attribut"/>
ou
<xsl:apply-templates select="$ma.variable/autre/chemin"/>
Result tree fragment
Si j'écris:
<xsl:variable name="ma.variable">
<racine attribut="valeur">
<élément>valeur</élément>
</racine>
</xsl:variable>
ou
<xsl:variable name="ma.variable">
<xsl:apply-templates select="mon/chemin">
</xsl:variable>
la variable "ma.variable" contient au contraire un fragment d'arbre résultat (result tree fragment). Dans le premier cas, ce fragment est constitué de littéraux et dans le deuxième il est constitué dynamiquement et contient le résultat de l'application des templates.
Dans les deux cas, la variable "ma.variable" est traitée comme un fragment d'arbre résultat et elle est soumise à la même restriction : on ne peut pas accéder à son contenu au moyen de requêtes XPath et la seule opération que l'on peut faire sur ce fragment est de le recopier dans l'arbre résultat :
<xsl:copy-of select="$ma.variable"/>
Peut-on éviter les result tree fragments?
L'accès aux informations contenues dans une variable ou un paramètre de type result tree fragment étant bloqué, on aura intérêt lorsque c'est possible à éviter d'utiliser des result tree fragments.
Pour les littéraux
Lorsque la variable contient des littéraux, comme dans notre premier exemple :
<xsl:variable name="ma.variable">
<racine attribut="valeur">
<élément>valeur</élément>
</racine>
</xsl:variable>
la fonction "document()" fournit un contournement qui permet de la définir sous forme de node set plutôt que de la définir sous forme de result tree fragment. Pour cela; on ajoutera un élément appartenant à un espace de noms autre que XSLT sous la racine de la transformation, par exemple :
<xsl:transform
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:var="http://ns.xmlfr.org/variable"
version="1.0">
<var:valeur>
<racine attribut="valeur">
<élément>valeur</élément>
</racine>
</var:valeur>
Il suffit ensuite de se rappeler que document("") (avec une chaîne vide en argument) désigne la transformation XSLT elle-même et nous pouvons définir notre variable comme :
<xsl:variable name="ma.variable" select="document('')/xsl:transfo
rm/var:valeur/racine"/>
Pour des résultats intermédiaires
Lorsqu'il s'agit de placer dans une variable des résultats intermédiaires comme dans notre deuxième exemple :
<xsl:variable name="ma.variable">
<xsl:apply-templates select="mon/chemin">
</xsl:variable>
il n'y malheureusement pas de moyen d'éviter le passage par un result tree fragment et il faudra utiliser une extension pour convertir ce result tree fragment en node set.
Variables conditionnelles
Un autre cas dans lequel on ne peut pas éviter le passage par un result tree fragment est lorsque l'on doit affecter des valeurs conditionnelles à une variable.
Lorsque ces valeurs sont des chaînes de caractères, le problème ne se pose pas, mais lorsque ce sont des node set, on va être confronté à un nouveau problème.
Imaginons par exemple que nous devions absolument affecter à "ma.variable" l'élément "foo" dans un cas et l'élément "bar" dans les autres cas (ces éléments étant situés sous le noeud contexte).
Pour les raisons exposées dans la FAQ "Affectation conditionnelle de variable XSLT", si nous écrivons :
<xsl:choose>
<xsl:when test="mon.test">
<xsl:variable name="ma.variable" select="foo">
</xsl:when>
<xsl:otherwise>
<xsl:variable name="ma.variable" select="bar">
</xsl:otherwise>
</xsl:choose>
nos variables sont bien des node sets mais leur portée ne dépassant pas l'élément dans lequel elles sont définies elles sont absolument inutilisables.
Il faut donc écrire :
<xsl:variable name="ma.variable">
<xsl:choose>
<xsl:when test="mon.test">
<xsl:copy-of select="foo"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="bar"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
ce qui résout le problème de la portée de la variable mais a l'effet de transformer notre variable en result tree fragment.
Conversion de result tree fragments en node sets
Cette conversion est tellement utile que tous les processeurs XSLT proposent des extensions permettant de la réaliser.
Ces extensions sont malheureusement incompatibles et il faut vous reporter à la documentation de l'implémentation que vous utilisez pour les utiliser.
C'est le cas pour XT et MSXML.
La plupart des autres processeurs (notamment libxslt, Saxon, Xalan et 4XSLT) ont implémenté la fonction node-set() de ESXLT ce qui permet d'écrire des transformations portables entre ces implémentations.
Si nous désirons convertir la variable définie dans notre premier exemple en node set pour accéder à son attribut à l'aide de cette fonction, nous pourrons écrire :
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl"
version="1.0">
<xsl:variable name="ma.variable">
<racine attribut="valeur">
<élément>valeur</élément>
</racine>
</xsl:variable>
.../...
<xsl:value-of select="exsl:node-set($ma.variable)/racine/@attribu
t"/>
On accéderait de la même manière au contenu de résultats intermédiaires.
Et XSLT 2.0
Le problème ne se pose pas pour XSLT 2.0 qui ignore purement et simplement la différence entre ces deux types de fonctions.
Références
Copyright 2004, Eric van der Vlist
|