Remplacer XSLT côté client : transformer le XML sur le serveur en PHP, Python ou Node.js

Chrome cesse d'appliquer les feuilles XSL le 17 novembre 2026. Si votre site envoie du XML avec une feuille XSL, la solution la plus durable consiste à faire la transformation sur votre serveur et à envoyer du HTML. Les exemples Python et Node.js ci-dessous ont été exécutés ; l'exemple PHP suit le manuel.

Par quoi remplacer XSLT côté client dans Chrome ?

Par du XSLT côté serveur : votre serveur fait la transformation XSL et envoie au navigateur du HTML ordinaire. En PHP, la classe XSLTProcessor de l’extension XSL s’en charge ; en Python, la classe XSLT de lxml ; en Node.js, un paquet comme saxon-js ou xslt-processor. Chacun reçoit votre fichier XML et votre fichier XSL, et renvoie une chaîne HTML que vous envoyez en text/html. C’est la seule voie dont le résultat ne dépend plus de ce que le navigateur sait faire.

XSLT cesse de fonctionner dans Chrome 158, le 17 novembre 2026. Les sites inscrits à l’essai d’origine (origin trial) et les navigateurs gérés par une règle d’entreprise le gardent jusqu’à Chrome 176, le 17 août 2027. Notre page sur le calendrier de suppression de XSLT liste les pages touchées et toutes les solutions côté visiteur.

L’annonce de Chrome décrit ces voies pour les propriétaires de sites :

  1. Faire le traitement XSLT sur le serveur et envoyer le HTML produit.
  2. Ajouter le script xslt-polyfill au document XML, pour que la feuille de style soit de nouveau appliquée dans le navigateur.
  3. Servir du JSON au lieu du XML et l’afficher avec JavaScript.
  4. Exécuter XSLT dans la page avec une bibliothèque JavaScript. D’après l’annonce, la plus importante est de loin celle de Saxonica, qui prend en charge tout XSLT 3.0.

Le polyfill demande le moins de changements. Comme la bibliothèque JavaScript de la voie 4, il s’exécute dans le navigateur de chaque visiteur. La transformation sur le serveur demande plus de travail, mais le navigateur reçoit ensuite une page HTML normale, sans script ni extension. L’essentiel de cet article porte sur cette voie.

S’il vous faut plus de temps, inscrivez votre site à l’essai d’origine et ajoutez son jeton aux réponses de vos pages. Une page XML n’a pas de head HTML où placer une balise meta : utilisez donc l’en-tête de réponse HTTP Origin-Trial que décrit le guide de Chrome sur les essais d’origine. Si vous ne pouvez pas du tout toucher au serveur, le polyfill reste la solution.

Faut-il transformer mon sitemap ou mon flux RSS en HTML ?

Non. Les moteurs de recherche et les lecteurs de flux les récupèrent, et ils ont besoin de XML à cette adresse.

La documentation de Google sur les sitemaps liste les formats acceptés : sitemaps XML, RSS, mRSS et Atom 1.0, et fichiers texte. Le HTML n’en fait pas partie. Remplacer /sitemap.xml par du HTML, c’est le casser pour les moteurs de recherche afin de régler un souci d’affichage qui ne concerne que les humains. Notre article sur la lecture d’un sitemap par Google montre que la mise en page n’y joue aucun rôle pour un sitemap WordPress.

Laissez le XML où il est. Si vos visiteurs veulent une version lisible, publiez le HTML à une autre adresse et ajoutez un lien vers cette page. Pour les flux, il existe aussi une solution plus légère : la mise en forme CSS fonctionne toujours dans Chrome.

La transformation sur le serveur convient aux pages XML destinées uniquement à des lecteurs humains, comme un catalogue de produits mis en forme avec XSL.

Pourquoi ma feuille XSLT renvoie-t-elle un tableau vide pour un sitemap ?

Parce que les éléments d’un sitemap appartiennent à un espace de noms, et que la feuille doit les désigner avec un préfixe. Les exemples de cet article partent de deux fichiers pour produire une copie lisible d’un sitemap, servie à sa propre adresse (par exemple /sitemap.html), tandis que /sitemap.xml reste tel quel. Voici le premier, un sitemap très court :

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url><loc>https://example.com/</loc><lastmod>2026-10-14</lastmod></url>
  <url><loc>https://example.com/about/</loc></url>
</urlset>

La feuille de style, sitemap.xsl, le transforme en tableau HTML :

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" exclude-result-prefixes="s"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:s="http://www.sitemaps.org/schemas/sitemap/0.9">
  <xsl:output method="html" encoding="UTF-8" indent="yes"/>
  <xsl:template match="/">
    <html>
      <body>
        <h1>Sitemap</h1>
        <table>
          <xsl:for-each select="s:urlset/s:url">
            <tr>
              <td><xsl:value-of select="s:loc"/></td>
              <td><xsl:value-of select="s:lastmod"/></td>
            </tr>
          </xsl:for-each>
        </table>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

XSLT 1.0 emprunte ses expressions de chemin à XPath 1.0, la recommandation du W3C où un nom sans préfixe ne désigne que des éléments sans espace de noms : l’espace de noms par défaut du document est ignoré. D’où le préfixe s, déclaré dans la feuille et repris dans s:urlset/s:url. Avec urlset/url, le tableau sortirait vide.

L’attribut exclude-result-prefixes="s" évite que cette déclaration d’espace de noms se retrouve dans le HTML. Sans lui, lors de notre essai avec lxml, le résultat commençait par <html xmlns:s="...">.

Avec libxslt, qu’utilisent les exemples PHP et Python, le résultat n’a pas de DOCTYPE : avec lxml, il commençait directement par <html><body>. MDN explique qu’une page a besoin d’un doctype comme <!doctype html> pour être affichée en mode standard plutôt qu’en mode quirks. Ajoutez à xsl:output l’attribut doctype-system="about:legacy-compat", valeur de compatibilité que le standard HTML prévoit pour les outils qui ne savent pas écrire le doctype court : lxml a alors écrit <!DOCTYPE html>. L’attribut indent="yes", lui, autorise seulement le processeur à ajouter des espaces, comme le ferait un formateur XML : libxslt a mis des retours à la ligne entre la plupart des éléments, sans indentation.

Comment exécuter XSLT en PHP, Python ou Node.js ?

L’extension XSL de PHP et lxml en Python reposent toutes deux sur libxslt, la bibliothèque XSLT des navigateurs Chromium selon l’annonce de Chrome. Node.js, lui, a besoin d’un paquet.

L’annonce de Chrome justifie le retrait par la sécurité : des bibliothèques comme libxslt sont du code C/C++ complexe et ancien, exposé aux failles de sécurité mémoire. Sur votre serveur, c’est vous qui choisissez les entrées : n’appliquez que vos feuilles à votre XML, jamais une feuille envoyée par un utilisateur, et tenez libxslt à jour en installant les mises à jour du système.

PHP

Utilisez la classe XSLTProcessor de l’extension XSL de PHP. D’après le manuel, cette extension est incluse dans PHP et s’active avec l’option --with-xsl à la compilation : vérifiez d’abord que php -m affiche xsl. Sur le PHP 8.3 dont nous disposions, celui de l’image Docker officielle de WordPress, ce n’était pas le cas, et XSLTProcessor manquait.

<?php
$xml = new DOMDocument();
$xml->load('sitemap.xml');

$xsl = new DOMDocument();
$xsl->load('sitemap.xsl');

$proc = new XSLTProcessor();
$proc->importStylesheet($xsl);

$html = $proc->transformToXml($xml);
if ($html === false) {
    http_response_code(500);
    exit;
}
header('Content-Type: text/html; charset=utf-8');
echo $html;

transformToXml renvoie le résultat sous forme de chaîne, ou false en cas d’erreur : le code le vérifie avant d’envoyer le moindre en-tête. Faute d’extension XSL, cet exemple suit le manuel et n’a pas été exécuté.

Python

Installez lxml avec pip install lxml. Sa documentation indique que lxml prend en charge XSLT 1.0 grâce à libxml2 et libxslt.

from lxml import etree

doc = etree.parse("sitemap.xml")
transform = etree.XSLT(etree.parse("sitemap.xsl"))
result = transform(doc)
html = str(result)

Le résultat est sérialisé comme le demande l’élément xsl:output de la feuille. D’après la documentation de lxml, pour obtenir une chaîne de texte Python avec str(result), cet élément doit déclarer UTF-8, comme le nôtre. Nous avons exécuté ce code avec Python 3.14.3 et lxml 6.0.2 : le résultat était le tableau HTML, avec deux lignes et une cellule de date vide pour la deuxième page.

Node.js

Node.js n’a pas de processeur XSLT parmi ses modules intégrés. Nous avons testé deux paquets.

SaxonJS. Le paquet saxon-js, de Saxonica, est un processeur XSLT 3.0 pour Node.js. XSLT 3.0 définit un mode de compatibilité pour les feuilles marquées version="1.0", et notre feuille a fonctionné sans modification. SaxonJS.transform() attend une feuille déjà compilée, au format SEF : compilez la vôtre une fois avec la commande xslt3.

npm install saxon-js xslt3
npx xslt3 -xsl:sitemap.xsl -export:sitemap.sef.json -nogo

Puis lancez la transformation depuis un module ES, par exemple un fichier nommé transform.mjs :

import SaxonJS from 'saxon-js';

const output = await SaxonJS.transform({
  stylesheetFileName: 'sitemap.sef.json',
  sourceFileName: 'sitemap.xml',
  destination: 'serialized',
}, 'async');
const html = output.principalResult;

Le README de saxon-js présente SaxonJS comme gratuit, avec une licence qui n’est pas open source.

xslt-processor. Ce paquet est écrit en JavaScript, sans dépendance native. Son README montre l’appel : analysez les deux fichiers avec sa classe XmlParser, puis passez-les à la méthode xsltProcess d’un objet Xslt, qui renvoie le résultat sous forme de promesse. Le README prévient que certaines fonctions exigées par les normes ont été laissées de côté, et que le paquet reconnaît les éléments XSLT à leur préfixe xsl: sans tenir compte des déclarations d’espaces de noms. Notre exemple a fonctionné avec lui ; testez vos propres feuilles avant de vous y fier.

Quand le script xslt-polyfill suffit-il ?

Quand vous pouvez modifier les fichiers XML, mais pas le code qui les sert. Le polyfill applique la feuille de style dans le navigateur, en JavaScript : le XSL reste tel quel, et on n’ajoute au XML qu’une ligne de script, que montre notre page sur le calendrier de suppression de XSLT. Le README de xslt-polyfill ajoute trois points pratiques :

  • le script est publié sur npm sous le nom xslt-polyfill, et vous servez le fichier depuis votre propre site ;
  • il n’applique qu’une feuille que le document désigne déjà par une ligne xml-stylesheet ;
  • tant que le XSLT natif est actif, le navigateur transforme le document avant l’exécution de tout script : testez donc le polyfill avec XSLT désactivé dans chrome://flags/#xslt.

Le type de contenu compte aussi. Dans notre test, XSLT désactivé, un élément script placé dans un flux RSS s’est exécuté quand le flux était envoyé en application/xml, mais pas en application/rss+xml : Chrome affichait alors le flux en texte brut. L’annonce de Chrome indique qu’ajouter la ligne à un flux ne devrait pas empêcher les lecteurs de flux d’analyser le XML. La documentation de Google sur les sitemaps n’en dit rien : après l’avoir ajoutée à un sitemap, vérifiez le rapport Sitemaps de la Search Console.

En contrepartie, chaque visiteur exécute désormais un script pour voir votre page, alors qu’avec la transformation sur le serveur, il reçoit du HTML déjà prêt.

Quel Content-Type utiliser pour le HTML produit ?

text/html, le type MIME que MDN donne pour les documents HTML. Envoyez donc Content-Type: text/html; charset=utf-8, comme dans l’exemple PHP. La page fonctionne alors dans tous les navigateurs, avec ou sans XSLT.

Les visiteurs qui tombent sur une page non transformée peuvent déjà installer l’extension officielle XSLT Polyfill. XMLNest, un XML viewer pour Chrome, est disponible sur le Chrome Web Store. Pourquoi j’ai créé XMLNest raconte l’histoire de l’extension.

Sources

  1. Chrome for Developers : Removing XSLT for a more secure browser (vérifiée le 14 octobre 2026)
  2. Chrome for Developers : Get started with origin trials (vérifiée le 14 octobre 2026)
  3. XSLT Polyfill, README de mfreed7/xslt_polyfill sur GitHub (vérifiée le 14 octobre 2026)
  4. W3C : XSL Transformations (XSLT) Version 1.0 (vérifiée le 14 octobre 2026)
  5. W3C : XML Path Language Version 1.0, Node Tests (vérifiée le 14 octobre 2026)
  6. W3C : XSL Transformations (XSLT) Version 3.0, XSLT 1.0 Compatibility Mode (vérifiée le 14 octobre 2026)
  7. HTML Standard : The DOCTYPE (vérifiée le 14 octobre 2026)
  8. MDN : Mode quirks et mode standard (vérifiée le 14 octobre 2026)
  9. Manuel PHP : XSLTProcessor::transformToXml (vérifiée le 14 octobre 2026)
  10. Manuel PHP : extension XSL, installation (vérifiée le 14 octobre 2026)
  11. Manuel PHP : options de la ligne de commande (vérifiée le 14 octobre 2026)
  12. lxml : XSLT (vérifiée le 14 octobre 2026)
  13. Documentation Node.js : index de l'API (vérifiée le 14 octobre 2026)
  14. npm : saxon-js (vérifiée le 14 octobre 2026)
  15. npm : xslt-processor (vérifiée le 14 octobre 2026)
  16. Google Search Central : Créer et envoyer un sitemap (vérifiée le 14 octobre 2026)
  17. MDN : Types MIME courants (vérifiée le 14 octobre 2026)
  18. Test fait par XMLNest : Exécuté par XMLNest le 14 octobre 2026 sur les fichiers d'exemple de cet article : Python 3.14.3 avec lxml 6.0.2 (libxslt 1.1.39) a produit le tableau HTML de deux lignes, commençant par <html><body> sans DOCTYPE, puis par <!DOCTYPE html> une fois doctype-system="about:legacy-compat" ajouté ; Node.js 24.20.0 avec saxon-js 2.7.0 et xslt3 2.7.0, puis avec xslt-processor 5.1.2, a produit le même tableau. Dans Chromium 156.0.8078.4, fonction XSLT désactivée, un élément script de l'espace de noms XHTML placé dans un flux RSS s'est exécuté quand le flux était envoyé en application/xml, et pas en application/rss+xml. PHP 8.3.35 de l'image Docker officielle wordpress:7.1.3-php8.3-apache n'avait pas l'extension XSL : l'exemple PHP suit donc le manuel PHP sans avoir été exécuté (vérifiée le 14 octobre 2026)