Passer de XSLT 1.0 à 2.0 sans tout casser
Changer la version déclarée ne suffit pas et ne casse rien tout de suite. Les incompatibilités se manifestent sur les données, pas à la compilation.
Passer version="1.0" à version="2.0" compile presque toujours. C'est le piège de cette
migration : rien ne signale l'incompatibilité au moment où on la crée. Elle attend les données.
Quatre différences changent le résultat sans lever la moindre erreur :
| Point | 1.0 | 2.0 |
|---|---|---|
xsl:value-of | premier nœud seulement | toute la séquence, séparée par un espace |
| comparaison de deux valeurs non typées | conversion en nombre | comparaison de chaînes |
| arbres résultat | fragments non navigables | séquences ordinaires |
| division | div seule, flottante | idiv distinct de div |
Les deux premières sont responsables de la quasi-totalité des régressions. Les traiter dans cet ordre est ce qui rend la migration finie en un temps prévisible.
1. xsl:value-of sur une séquence
<xsl:value-of select="ligne/montant"/>
En 1.0, cette instruction sort le premier montant et ignore les autres — silencieusement.
En 2.0, elle les sort tous, séparés par un espace. Une sortie qui contenait 12 contient
soudain 12 34 56.
🔴 C'est la régression la plus fréquente, et la plus difficile à imputer : la feuille n'a pas
changé, la donnée n'a pas changé, et le résultat n'est plus le même. Quand le comportement 1.0 était
voulu, il s'écrit explicitement — select="ligne[1]/montant" — et le lecteur suivant sait enfin ce
qui était réellement demandé.
⚠️ Quand il n'était pas voulu, on vient de découvrir un défaut qui existait depuis des années : la feuille jetait des données sans le dire.
2. Les comparaisons qui changent de sens
@quantite < @seuil
Sur deux attributs valant 10 et 9, sans schéma :
- en 1.0,
<convertit les deux opérandes en nombres :10 < 9est faux ; - en 2.0, deux valeurs non typées se comparent comme des chaînes :
"10" < "9"est vrai.
🔴 Le résultat s'inverse, et aucune erreur n'est levée. Une condition de filtrage qui gardait
les bonnes lignes garde désormais les autres. Le correctif est de dire ce qu'on compare :
number(@quantite) < number(@seuil), ou xs:integer(@quantite).
⚠️ Une comparaison entre types réellement incompatibles, elle, échoue franchement. C'est le seul cas de cette liste qui se signale — et c'est pourquoi il ne coûte presque rien.
3. Les fragments d'arbre deviennent navigables
En 1.0, une variable à contenu produit un result tree fragment qu'il fallait convertir avec
exsl:node-set() avant de pouvoir y naviguer. En 2.0, c'est un nœud document ordinaire, et
exsl:node-set() devient inutile.
⚠️ Cette différence est une bonne nouvelle qui crée quand même des surprises : le nœud document introduit un niveau que le chemin XPath doit franchir. Les conséquences exactes sont décrites dans une variable XSLT vide : les trois causes qu'on ne voit pas.
Le levier qui rend la migration progressive
🔴 La version se déclare aussi instruction par instruction. Dans une feuille en version="2.0",
un version="1.0" posé sur un élément rétablit le comportement 1.0 pour lui et ses descendants :
<xsl:template match="facture">
<resume version="1.0">
<xsl:value-of select="ligne/montant"/>
</resume>
</xsl:template>
C'est ce qui permet de migrer une feuille de mille lignes sans la migrer d'un coup : on bascule
l'ensemble, on isole les blocs qui régressent, et on les traite un par un. ⚠️ Chaque
version="1.0" restant est une dette qu'il faut inscrire quelque part, sans quoi elle devient
permanente.
Ce qui ne se voit qu'en comparant les sorties
Aucune de ces différences ne se relit à l'œil sur une feuille longue. La seule méthode fiable est de produire la sortie avant et après, puis de comparer — non pas les octets, mais les arbres, pour la raison exposée dans comparer deux sorties XML sans se noyer dans le diff.
Un filet de tests posé avant la bascule vaut mieux qu'un diff joué après : XSpec en pratique montre le format minimal.
Ce que cet article ne couvre pas
- XSLT 3.0 :
xsl:mode, les paquets et le mode flux forment une autre migration ; - les feuilles conscientes du schéma, où le typage change encore les comparaisons ;
- les extensions du processeur — une feuille qui appelait des fonctions propres à un moteur 1.0 ne migre pas, elle se réécrit.
Le chapitre de référence du manuel liste les fonctions dont la sémantique a changé entre les deux versions.