Transformateur XSLT

Applique une feuille de style, avec repli sur WebAssembly.

Entrée
Feuille de style XSLT
Résultat
En attenteCollez un document pour le vérifier. La validation se fait à la frappe.

Tout s'exécute dans cet onglet. Rien de ce que vous collez n'est envoyé, journalisé ni transmis où que ce soit. Ouvrez votre panneau réseau et vérifiez.

Collez un document dans le volet de gauche et une feuille de style dans celui de droite : la transformation s’exécute dans cet onglet. Le résultat revient sous forme de texte, avec au-dessus le moteur qui l’a produit et le temps qu’il a pris. Les deux entrées sont d’abord contrôlées quant à leur bonne formation, si bien qu’un chevron manquant dans la feuille de style est signalé comme tel et non comme un échec de compilation.

On y a recours quand une feuille de style ne fait pas ce qu’on attend et qu’on veut une boucle rapide : une transformation héritée de quelqu’un qui est parti, un motif de correspondance dont on n’est pas sûr qu’il se déclenche, un modèle qui produit un fichier vide sur une entrée et pas sur une autre.

L’outil a cette forme parce que le XSLT des navigateurs est retiré selon un calendrier publié. Il utilise le processeur natif tant qu’il y en a un et se rabat sur une compilation WebAssembly de libxslt quand il n’y en a plus, en nommant le moteur qui a tourné.

Chrome retire XSLT, et les dates sont publiées

Google a déposé une intention de déprécier et de retirer en 2025. Le raisonnement : l’instruction de traitement xml-stylesheet apparaît dans environ un chargement de page sur huit mille, tandis que libxslt traîne un long passif de sûreté mémoire et a perdu son mainteneur de longue date en juin 2025. Mozilla et Apple ont tous deux indiqué leur soutien. Le calendrier est daté :

Firefox et Safari n’ont annoncé aucun retrait de leur côté, donc XSLTProcessor y fonctionne encore pour l’instant. Firefox utilise son propre processeur TransforMiiX plutôt que libxslt, ce qui explique qu’une poignée de feuilles de style se comportent déjà différemment d’un navigateur à l’autre.

Cette page teste la présence d’un processeur natif fonctionnel en lançant une transformation d’une ligne plutôt qu’en vérifiant si le constructeur existe : un constructeur qui existe et échoue en silence est pire qu’un constructeur absent. Si le test échoue, elle charge une compilation WebAssembly de libxslt, le polyfill que désigne le guide de migration de Chrome lui-même.

  • Chrome 143, 2 décembre 2025 : dépréciation officielle, avertissements en console et dans Lighthouse.
  • Chrome 145, décembre 2025 : XSLT désactivé par défaut dans Canary, Dev et Beta.
  • Chrome 146, 10 mars 2026 : échappatoire par politique d’entreprise.
  • Chrome 152, 25 août 2026 : ouverture de l’essai d’origine.
  • Chrome 158, 17 novembre 2026 : XSLT cesse de fonctionner en version stable, sauf sous l’essai ou la politique.
  • Chrome 176, 17 août 2027 : les deux échappatoires expirent. XSLTProcessor et l’instruction de traitement xml-stylesheet disparaissent. La forme type="text/css" de cette instruction n’est pas touchée.

Les modèles sont des règles, pas un script

Une feuille de style est un ensemble de règles de modèle, chacune avec un motif de correspondance. Le processeur part de la racine du document, trouve le modèle qui correspond le mieux, l’exécute, et là où ce modèle dit xsl:apply-templates il recommence pour les enfants sélectionnés. Rien ne s’exécute dans l’ordre où vous l’avez écrit.

xsl:apply-templates rend la main à l’ensemble des règles : chaque nœud sélectionné trouve son propre modèle, donc des enfants hétérogènes obtiennent chacun leur règle au lieu d’une enfilade de branches xsl:choose. xsl:for-each itère sur place, en appliquant un seul corps à chaque nœud sélectionné, ce qui se lit mieux pour une liste plate de lignes. Les deux changent le nœud de contexte, d’où viennent la plupart des signalements d’expression select soudain fausse.

XSLT a aussi des règles intégrées que vous n’avez pas écrites : celle des éléments applique les modèles à leurs enfants, et celle des nœuds de texte recopie le texte tel quel. C’est ce couple qui fait qu’une feuille de style dont aucun motif ne correspond produit quand même une page de texte agglutiné.

Quand la transformation ne produit rien

Un résultat vide est le deuxième problème XSLT le plus signalé après le texte agglutiné, et il a une cause dominante : les espaces de noms. Une feuille de style écrite pour du XML sans espace de noms ne correspondra pas à des éléments qui sont dans un espace de noms, même si les noms paraissent identiques dans les deux fichiers. Si la racine de votre source porte xmlns="http://example.com/orders", <xsl:template match="order"> demande {}order alors que le document contient {http://example.com/orders}order. Aucun modèle ne se déclenche, et XSLT ne considère pas cela comme une erreur.

Déclarez l’espace de noms dans la feuille de style sous un préfixe de votre choix et utilisez-le dans chaque match et chaque select. XSLT 1.0 partage la limitation d’XPath 1.0, le préfixe n’est donc pas facultatif, et xpath-default-namespace n’aide pas puisqu’il relève d’XSLT 2.0 et que les navigateurs l’ignorent.

Une feuille de style déclarant version="2.0" ou "3.0" est signalée avant l’exécution, car les deux moteurs sont en 1.0 et les constructions plus récentes peuvent échouer en silence, donnant une sortie subtilement fausse plutôt qu’absente. Et si la racine de la feuille de style n’est pas dans http://www.w3.org/1999/XSL/Transform, rien ne marche du tout : cet URI est identique pour les trois versions d’XSLT, et mal orthographié il compile en une feuille de style qui ne contient aucune règle.

Ce qu’aucun moteur XSLT de navigateur ne sait faire

XSLT 1.0 uniquement. Aucun navigateur n’a jamais livré 2.0 ni 3.0, et libxslt ne les implémente pas davantage, donc le repli WebAssembly ne relève pas le plafond. Cela exclut xsl:function, xsl:for-each-group (le regroupement reste la technique muenchienne avec key()), xsl:analyze-string et xsl:result-document. Saxon-JS est la seule voie vers 2.0 ou 3.0 dans un navigateur.

Les ressources externes sont l’autre limite dure. document(), xsl:include et xsl:import d’URL distantes relèvent de la politique de même origine : une feuille de style qui tire une table de correspondance par HTTP échouera. Rien ici ne récupère quoi que ce soit pour vous et il n’y a pas de proxy. Quelques lacunes propres à chaque moteur piègent aussi les gens :

  • disable-output-escaping n’est pas implémenté dans Firefox : TransforMiiX produit un arbre plutôt qu’une chaîne sérialisée, et la spécification permet au processeur de se rattraper en traitant l’attribut comme no. Utilisez plutôt une référence de caractère numérique telle que &#160;.
  • xsl:namespace-alias n’est pas pris en charge dans Firefox, pas plus que l’axe namespace:: d’XPath.
  • xsl:output est largement indicatif quand le processeur est piloté depuis un script, car le résultat est un DOM : indent, encoding et les propriétés de doctype n’ont guère d’effet. Cette page y lit method pour choisir entre texte et balisage sérialisé.
  • La couverture d’EXSLT diffère. libxslt, utilisé par Chrome, Safari et le repli WebAssembly, en implémente l’essentiel. Firefox en implémente un sous-ensemble.
  • transformToDocument et transformToFragment renvoient null en cas d’échec au lieu de lever une exception, et la vraie raison n’apparaît que dans la console.

Le faire en code

Une feuille de style est une entrée exécutable. Chacun des moteurs ci-dessous peut lire des fichiers, ouvrir des connexions réseau ou écrire sur disque via document(), xsl:import ou une fonction d’extension : chaque exemple coupe donc cela. Traitez une feuille de style que vous n’avez pas écrite comme un script que vous n’avez pas écrit.

// Native first, WebAssembly fallback. A probe, not a feature check:
// after Chrome 158 the constructor is gone, but a constructor that
// exists and fails is the case that actually misleads you.
function nativeXsltWorks() {
  if (typeof XSLTProcessor === 'undefined') return false;
  try {
    const p = new DOMParser();
    const xsl = p.parseFromString(
      '<xsl:stylesheet version="1.0" ' +
      'xmlns:xsl="http://www.w3.org/1999/XSL/Transform">' +
      '<xsl:template match="/"><ok/></xsl:template></xsl:stylesheet>',
      'application/xml');
    const proc = new XSLTProcessor();
    proc.importStylesheet(xsl);
    const out = proc.transformToDocument(
      p.parseFromString('<a/>', 'application/xml'));
    return out?.documentElement?.nodeName === 'ok';
  } catch {
    return false;
  }
}

async function transform(xmlText, xslText) {
  // xslt-polyfill is libxslt compiled to WebAssembly. Importing it
  // installs a replacement XSLTProcessor on globalThis.
  if (!nativeXsltWorks()) await import('xslt-polyfill');

  const p = new DOMParser();
  const xml = p.parseFromString(xmlText, 'application/xml');
  const xsl = p.parseFromString(xslText, 'application/xml');
  if (xml.querySelector('parsererror') || xsl.querySelector('parsererror')) {
    throw new Error('one of the two inputs is not well-formed');
  }

  const proc = new XSLTProcessor();
  proc.importStylesheet(xsl);
  proc.setParameter(null, 'lang', 'en');   // (namespaceURI, name, value)

  // Returns null rather than throwing. Do not skip this check.
  const out = proc.transformToDocument(xml);
  if (!out) throw new Error('no template matched the document root');

  return new XMLSerializer().serializeToString(out);
}
from lxml import etree

# lxml wraps libxslt, the same engine Chrome and Safari use.
parser = etree.XMLParser(resolve_entities=False, no_network=True, load_dtd=False)
xml = etree.fromstring(xml_bytes, parser)
xsl = etree.fromstring(xsl_bytes, parser)

# DENY_ALL blocks document(), xsl:include, xsl:import and every EXSLT
# file or network operation. Without it a stylesheet is a file-read
# primitive that runs whatever its author wrote.
transform = etree.XSLT(xsl, access_control=etree.XSLTAccessControl.DENY_ALL)

try:
    result = transform(xml, lang="'en'")   # params are XPath: quote strings twice
except etree.XSLTApplyError as e:
    raise SystemExit(f'transform failed: {e}')

# xsl:message output and warnings land here, not in the result.
for entry in transform.error_log:
    print(f'{entry.line}: {entry.message}')

print(str(result))   # honours xsl:output method, encoding and indent
import javax.xml.XMLConstants;
import javax.xml.transform.*;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

TransformerFactory factory = TransformerFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);

// An empty string means "no protocol is permitted", which blocks
// document(), xsl:import and xsl:include of anything outside the
// stylesheet you handed in. Neither is restricted by default.
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");

// Without a listener, compilation warnings go to stderr and are lost.
factory.setErrorListener(new ErrorListener() {
    public void warning(TransformerException e) {
        System.err.println("warning: " + e.getMessageAndLocation());
    }
    public void error(TransformerException e) throws TransformerException {
        throw e;
    }
    public void fatalError(TransformerException e) throws TransformerException {
        throw e;
    }
});

// Templates is compiled once and is thread-safe; Transformer is not.
Templates templates = factory.newTemplates(new StreamSource(stylesheetStream));
Transformer transformer = templates.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setParameter("lang", "en");
transformer.transform(new StreamSource(xmlStream), new StreamResult(System.out));

// The JDK's built-in processor is Xalan, XSLT 1.0. For 2.0 or 3.0 put
// Saxon-HE on the classpath; it registers itself as the factory.
using System.Xml;
using System.Xml.Xsl;

var readerSettings = new XmlReaderSettings
{
    DtdProcessing = DtdProcessing.Prohibit,
    XmlResolver = null,
};

var transform = new XslCompiledTransform();

// XsltSettings.Default disables both the document() function and
// embedded <msxsl:script>. XsltSettings.TrustedXslt enables both and
// should never be used on a stylesheet from outside your codebase.
// The null resolver blocks xsl:import and xsl:include as well.
using (var xslReader = XmlReader.Create(stylesheetPath, readerSettings))
{
    transform.Load(xslReader, XsltSettings.Default, null);
}

var args = new XsltArgumentList();
args.AddParam("lang", "", "en");

using var xmlReader = XmlReader.Create(inputPath, readerSettings);
using var writer = XmlWriter.Create(Console.Out, transform.OutputSettings);
transform.Transform(xmlReader, args, writer);

// XslCompiledTransform is XSLT 1.0. Microsoft never shipped 2.0 for
// .NET; Saxonica's Saxon-HE has a .NET build if you need it.
<?php
// Requires ext-xsl, which wraps libxslt.
$xml = new DOMDocument();
$xml->loadXML($xmlSource, LIBXML_NONET);

$xsl = new DOMDocument();
$xsl->loadXML($xslSource, LIBXML_NONET);

$proc = new XSLTProcessor();

// Block every filesystem and network operation a stylesheet can reach
// for. Only the write prefs are blocked by default.
$proc->setSecurityPrefs(
    XSL_SECPREF_READ_FILE
    | XSL_SECPREF_WRITE_FILE
    | XSL_SECPREF_CREATE_DIRECTORY
    | XSL_SECPREF_READ_NETWORK
    | XSL_SECPREF_WRITE_NETWORK
);

$proc->importStylesheet($xsl);
$proc->setParameter('', 'lang', 'en');

$result = $proc->transformToXml($xml);
if ($result === false) {
    fwrite(STDERR, "transformation failed\n");
    exit(1);
}
echo $result;
# xsltproc ships with libxslt: the same engine Chrome and Safari use,
# and the same one this page falls back to in WebAssembly.
# --nonet stops it fetching anything the stylesheet references.
xsltproc --nonet style.xsl doc.xml > out.html

# --stringparam passes a string. --param takes an XPath expression, so
# a literal string needs its own quotes inside the shell quotes.
xsltproc --nonet --stringparam lang en style.xsl doc.xml
xsltproc --nonet --param year "'2026'" style.xsl doc.xml

# xsl:message output goes to stderr, so separate it before piping.
xsltproc --nonet style.xsl doc.xml 2> messages.log | xmllint --format -

# XSLT 2.0 and 3.0 need Saxon. libxslt will never implement them.
java -cp saxon-he.jar net.sf.saxon.Transform \
  -s:doc.xml -xsl:style.xsl -o:out.html lang=en

La syntaxe des paramètres diffère dans chacun d’eux, et pas de façon cosmétique : dans lxml, dans xsltproc --param et dans Saxon, une valeur de paramètre est une expression XPath, donc la chaîne en doit s’écrire "'en'" sous peine d’être lue comme un pas de chemin sélectionnant les éléments nommés en. setParameter en JavaScript et XsltArgumentList en .NET prennent de simples chaînes.

Questions fréquentes

Cet outil cessera-t-il de fonctionner quand Chrome retirera XSLT ?

Non. Chrome 158, le 17 novembre 2026, fait cesser XSLT en version stable, et Chrome 176 le 17 août 2027 retire purement et simplement XSLTProcessor et l’instruction de traitement xml-stylesheet. Un outil bâti sur le seul processeur natif casserait à ces dates.

Cette page lance d’abord une transformation de test d’une ligne. Tant que votre navigateur a un moteur natif fonctionnel, c’est lui qui sert ; quand le test échoue, une compilation WebAssembly de libxslt est chargée à la place et le panneau nomme le moteur qui a tourné. Le repli n’est récupéré qu’au besoin, donc les utilisateurs de Firefox et de Safari ne le téléchargent jamais.

Puis-je exécuter XSLT 2.0 ou 3.0 ici ?

Non, et aucun navigateur ne le peut non plus. Chrome et Safari utilisent libxslt, Firefox son propre TransforMiiX, et tous trois implémentent 1.0. Le repli WebAssembly est lui aussi libxslt, il ne change donc pas le plafond.

Si votre feuille de style déclare version="2.0" ou "3.0", cette page avertit avant l’exécution plutôt qu’après, car un processeur 1.0 ne rejette pas franchement une feuille 2.0 : il exécute ce qu’il reconnaît et malmène le reste en silence. Saxon est la réponse pratique, Saxon-JS dans un navigateur et Saxon-HE sur la JVM ou .NET.

Pourquoi ma feuille de style produit-elle un résultat vide ?

Presque toujours une discordance d’espace de noms. Si votre source déclare un espace de noms par défaut sur sa racine, comme xmlns="http://example.com/orders", chaque élément sans préfixe à l’intérieur appartient à cet espace. Un modèle écrit <xsl:template match="order"> demande un élément order dans aucun espace de noms : aucune règle ne se déclenche et la sortie est vide. XSLT ne traite pas cela comme une erreur.

Déclarez l’espace de noms dans la feuille de style avec un préfixe, disons xmlns:o="http://example.com/orders", puis écrivez match="o:order" et select="o:line" partout. Deux autres causes à écarter : une racine de feuille de style qui n’est pas dans http://www.w3.org/1999/XSL/Transform, et aucun modèle correspondant à la racine du document.

Mon XML et ma feuille de style sont-ils téléversés ?

Ni l’un ni l’autre. Les deux volets restent dans cet onglet : le contrôle de bonne formation tourne dans un Web Worker, et la transformation tourne soit sur le processeur intégré de votre navigateur, soit sur un module WebAssembly chargé dans la page. Il n’y a pas de serveur ici pour les recevoir.

La question est plus vive pour XSLT que pour la plupart des outils, car la feuille de style est souvent la moitié qui a de la valeur : une transformation qui projette le schéma d’un partenaire sur le vôtre encode des règles métier, des noms de champs et parfois des points d’accès. Une conséquence de l’exécution locale est que la sortie est affichée comme du texte et jamais insérée dans la page comme du balisage, puisqu’une feuille de style peut engendrer des éléments script.

Pourquoi ma sortie contient-elle tout le texte de mon document sans balisage ?

Vous êtes tombé sur les règles de modèle intégrées d’XSLT. La spécification définit des règles par défaut qui existent que vous les écriviez ou non : celle des éléments applique les modèles à leurs enfants, et celle des nœuds de texte recopie le texte directement.

Donc, quand aucun de vos propres modèles ne correspond, le processeur ne s’arrête pas. Il parcourt tout l’arbre avec ces règles par défaut et imprime chaque nœud de texte rencontré, indentation de la source comprise. La cause est en général un espace de noms. Le diagnostic rapide consiste à ajouter <xsl:template match="text()"/>, qui supprime la règle par défaut du texte : si la sortie devient vide, aucune de vos règles ne se déclenchait.

Quelle est la différence entre apply-templates et for-each ?

xsl:for-each itère sur place. Il sélectionne une liste de nœuds et exécute son corps une fois par nœud dans l’ordre du document, et ce corps est fixe : tous les nœuds sont donc traités pareil. Cela se lit comme une boucle et convient à une liste plate et homogène, par exemple transformer chaque élément row en ligne de tableau.

xsl:apply-templates rend la main à l’ensemble des règles, laissant chaque nœud sélectionné trouver le modèle qui lui correspond le mieux : des types d’éléments différents reçoivent donc des traitements différents sans conditionnelles. Il accepte de plus un attribut mode, si bien qu’un même nœud peut être traité deux fois sous des règles distinctes, une fois pour un sommaire et une fois pour le corps. for-each n’a pas d’équivalent.

La feuille de style peut-elle charger un autre fichier avec document() ou xsl:import ?

Pas ici. document(), xsl:include et xsl:import résolvent tous une URL, et dans un navigateur ces chargements sont soumis à la politique de même origine : tout ce qui est distant est bloqué. Cette page n’a pas non plus de proxy de récupération, délibérément : servir de proxy signifierait que le document visé, et l’URL elle-même, passent par un serveur que ce site contrôle.

Intégrez le second document dans votre source et sélectionnez-le avec du XPath ordinaire, déplacez la table de correspondance dans un paramètre de la feuille de style, ou exécutez la transformation en local avec xsltproc, où document() se résout sur votre système de fichiers.

Outils associés

Pour aller plus loin