Sostituire l'XSLT nel browser: trasformare l'XML sul server con PHP, Python o Node.js
Dal 17 novembre 2026 Chrome smette di applicare i fogli XSL. Se un sito serve XML con un foglio di stile XSL, la soluzione più duratura è eseguire la trasformazione sul server e inviare HTML al browser. Abbiamo eseguito gli esempi in Python e Node.js qui sotto; quello in PHP si basa sul manuale.
Cosa usare al posto dell’XSLT di Chrome?
L’XSLT lato server: il server esegue la trasformazione XSL e invia al browser del semplice HTML. In PHP se ne occupa la classe XSLTProcessor dell’estensione XSL; in Python, la classe XSLT di lxml; in Node.js, un pacchetto come saxon-js o xslt-processor. In tutti e tre i casi si passano il file XML e il file XSL e si ottiene una stringa HTML da inviare come text/html. È l’unica strada in cui il risultato non dipende più da ciò che il browser supporta.
XSLT smette di funzionare in Chrome 158, il 17 novembre 2026. I siti iscritti alla prova dell’origine (origin trial) e i browser gestiti con un criterio aziendale lo mantengono fino a Chrome 176, il 17 agosto 2027. La nostra pagina sul calendario di rimozione di XSLT elenca le pagine coinvolte e tutte le soluzioni per chi visita i siti.
L’annuncio di Chrome propone queste soluzioni a chi gestisce un sito:
- Eseguire l’elaborazione XSLT sul server e inviare l’HTML prodotto.
- Aggiungere lo script xslt-polyfill al documento XML, così che il foglio di stile torni a essere applicato nel browser.
- Servire JSON al posto dell’XML e mostrarlo con JavaScript.
- Eseguire XSLT nella pagina con una libreria JavaScript. L’annuncio cita quella di Saxonica come di gran lunga la più grande, con supporto completo a XSLT 3.0.
Il polyfill è l’intervento più leggero. Come le librerie JavaScript, però, gira nel browser di ogni visitatore. La trasformazione sul server richiede più lavoro, ma il browser riceve poi una normale pagina HTML, senza script né estensioni. Il resto dell’articolo è dedicato soprattutto a questa soluzione.
Se serve più tempo, la prova dell’origine di Chrome mantiene XSLT attivo sul sito fino a Chrome 176, ad agosto 2027. Bisogna iscrivere il sito alla prova e aggiungere il token alle risposte delle pagine. Una pagina XML non ha un elemento head in cui inserire un tag meta, quindi si usa l’intestazione di risposta HTTP Origin-Trial, descritta nella guida di Chrome sulle prove dell’origine. Se il server non è modificabile in alcun modo, resta il polyfill.
Conviene convertire la sitemap o il feed RSS in HTML?
No. Motori di ricerca e lettori di feed li scaricano, e a quell’indirizzo si aspettano XML.
La documentazione di Google sulle sitemap elenca i formati accettati: sitemap XML; RSS, mRSS e Atom 1.0; file di testo. L’HTML non è tra questi. Sostituire /sitemap.xml con HTML significa renderla inutilizzabile per i motori di ricerca pur di risolvere un problema di visualizzazione che riguarda solo chi la apre nel browser. Il nostro articolo su cosa legge Google in una sitemap mostra che, nel caso di WordPress, la grafica non ha alcun peso.
L’XML va lasciato dov’è. Se serve una versione leggibile, conviene pubblicare l’HTML a un altro indirizzo e collegarlo con un link. Per i feed esiste anche un’opzione più leggera: la formattazione con CSS funziona ancora in Chrome.
La trasformazione sul server è adatta alle pagine XML pensate solo per essere lette da persone, come un catalogo prodotti formattato con XSL.
Perché l’XSLT restituisce una tabella vuota per una sitemap?
Perché gli elementi di una sitemap appartengono a uno spazio dei nomi, e il foglio deve indicarli con un prefisso. Gli esempi di questo articolo usano due file che producono una copia leggibile di una sitemap, servita a un indirizzo separato, per esempio /sitemap.html, mentre /sitemap.xml resta com’è. L’XML è una breve sitemap:
<?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>
Il foglio di stile, sitemap.xsl, la trasforma in una tabella 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 usa le espressioni di percorso definite nella raccomandazione XML Path Language 1.0 del W3C, secondo cui un nome senza prefisso corrisponde solo a elementi senza spazio dei nomi: lo spazio dei nomi predefinito del documento viene ignorato. Per questo il foglio dichiara il prefisso s e scrive s:urlset/s:url. Con urlset/url la tabella uscirebbe vuota.
L’attributo exclude-result-prefixes="s" evita che quella dichiarazione di spazio dei nomi finisca nell’HTML. Senza questo attributo, nella nostra prova con lxml il risultato cominciava con <html xmlns:s="...">.
Con libxslt, la libreria su cui si basano sia l’esempio PHP sia quello Python, il risultato non ha DOCTYPE: nella nostra prova con lxml cominciava con <html><body>. MDN spiega che una pagina ha bisogno di un doctype come <!doctype html> per essere visualizzata in modalità standard e non in modalità quirks. Aggiungendo a xsl:output l’attributo doctype-system="about:legacy-compat" (la stringa legacy che lo standard HTML prevede per i generatori di questo tipo), lxml ha scritto <!DOCTYPE html>. L’attributo indent="yes", invece, si limita a consentire al processore di aggiungere spazi, come farebbe un formattatore XML: libxslt è andato a capo tra la maggior parte degli elementi, ma non ha aggiunto rientri.
Come eseguire XSLT in PHP, Python o Node.js?
L’estensione XSL di PHP e lxml in Python si basano entrambe su libxslt, che secondo l’annuncio di Chrome è la libreria XSLT usata dai browser basati su Chromium. Per Node.js serve un pacchetto esterno.
L’annuncio di Chrome motiva la rimozione con la sicurezza: librerie come libxslt sono codice C/C++ complesso e datato, soggetto a vulnerabilità di sicurezza della memoria. Sul proprio server, invece, si decide che cosa elaborare: meglio applicare all’XML del sito solo fogli XSL scritti in casa, mai un foglio inviato da un utente, e aggiornare libxslt insieme al resto del sistema.
PHP
Si usa la classe XSLTProcessor dell’estensione XSL di PHP. Secondo il manuale, l’estensione è inclusa in PHP ma va attivata in fase di compilazione con l’opzione --with-xsl: prima di tutto, quindi, va verificato che php -m elenchi xsl. Nel PHP 8.3 che avevamo a disposizione, quello dell’immagine Docker ufficiale di WordPress, l’estensione non c’era e la classe XSLTProcessor non esisteva.
<?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 restituisce il risultato come stringa, oppure false in caso di errore, quindi il codice lo controlla prima di inviare qualsiasi intestazione. Visto che l’estensione XSL mancava, non abbiamo potuto eseguire questo esempio, che si basa sul manuale.
Python
lxml si installa con pip install lxml. Secondo la documentazione, supporta XSLT 1.0 tramite libxml2 e libxslt.
from lxml import etree
doc = etree.parse("sitemap.xml")
transform = etree.XSLT(etree.parse("sitemap.xsl"))
result = transform(doc)
html = str(result)
Il risultato viene serializzato come indica l’elemento xsl:output del foglio. Secondo la documentazione di lxml, per ottenere una stringa Python con str(result) quell’elemento deve dichiarare la codifica UTF-8, come fa il nostro. Eseguito con Python 3.14.3 e lxml 6.0.2, l’esempio ha prodotto la tabella HTML, con due righe e la cella della data vuota per la seconda pagina.
Node.js
Node.js non ha un processore XSLT tra i moduli integrati. Abbiamo provato due pacchetti.
SaxonJS. Il pacchetto saxon-js, di Saxonica, è un processore XSLT 3.0 per Node.js. Questa versione dello standard prevede una modalità di compatibilità per i fogli dichiarati version="1.0", e il nostro ha funzionato senza modifiche. SaxonJS.transform() usa un foglio già compilato, cioè un file SEF, che si genera una volta sola con il comando xslt3:
npm install saxon-js xslt3
npx xslt3 -xsl:sitemap.xsl -export:sitemap.sef.json -nogo
La trasformazione si lancia poi da un modulo ES, per esempio un file chiamato 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;
Il README di saxon-js presenta SaxonJS come gratuito, con una licenza che non è open source.
xslt-processor. Questo pacchetto è scritto in JavaScript, senza dipendenze native. Il suo README mostra la chiamata: i due file passano per la classe XmlParser e poi per il metodo xsltProcess di un oggetto Xslt, che restituisce una promise con il risultato. Lo stesso documento avverte che mancano alcune funzioni richieste dagli standard e che il pacchetto riconosce gli elementi XSLT dal prefisso xsl:, ignorando le dichiarazioni di spazio dei nomi. Il nostro esempio ha funzionato; prima di affidarsi al pacchetto, è bene provarlo con i propri fogli.
Quando basta lo script xslt-polyfill?
Quando si possono modificare i file XML, ma non il codice del server che li invia. Il polyfill applica il foglio di stile nel browser, con JavaScript: l’XSL resta com’è e all’XML si aggiunge solo una riga di script, riportata nella nostra pagina sul calendario di rimozione di XSLT. Il README di xslt-polyfill aggiunge tre indicazioni pratiche:
- lo script è pubblicato su npm come
xslt-polyfill, e il file va servito dal proprio sito; - applica solo un foglio che il documento collega già con una riga
xml-stylesheet; - finché l’XSLT nativo è attivo, il browser trasforma il documento prima che parta qualsiasi script, quindi il polyfill va provato con XSLT disattivato in
chrome://flags/#xslt.
Conta anche il tipo di contenuto. Nella nostra prova con XSLT disattivato, un elemento script inserito in un feed RSS è stato eseguito quando il feed era inviato come application/xml, ma non come application/rss+xml, perché in quel caso Chrome mostrava il feed come testo semplice. L’annuncio di Chrome dice che aggiungere la riga a un feed non dovrebbe impedire ai lettori di feed di analizzare l’XML. La documentazione di Google sulle sitemap non dice nulla in proposito: se si aggiunge la riga a una sitemap, conviene poi controllare il rapporto Sitemap di Search Console.
Lo svantaggio è che ogni visitatore deve eseguire uno script per vedere la pagina, mentre con la trasformazione sul server riceve HTML già pronto.
Quale Content-Type deve avere l’HTML generato?
text/html. MDN indica text/html come tipo MIME dei documenti HTML, quindi l’intestazione da inviare è Content-Type: text/html; charset=utf-8, come nell’esempio PHP. Così la pagina funziona in qualsiasi browser, con o senza XSLT.
Chi si imbatte in una pagina non trasformata può già installare l’estensione ufficiale XSLT Polyfill; XMLNest, un visualizzatore XML per Chrome è disponibile anche nel Chrome Web Store. Nell’articolo Perché ho creato XMLNest racconto come è nata l’estensione.
Fonti
- Chrome for Developers: Removing XSLT for a more secure browser (verificata il 14 ottobre 2026)
- Chrome for Developers: Get started with origin trials (verificata il 14 ottobre 2026)
- XSLT Polyfill, README di mfreed7/xslt_polyfill su GitHub (verificata il 14 ottobre 2026)
- W3C: XSL Transformations (XSLT) Version 1.0 (verificata il 14 ottobre 2026)
- W3C: XML Path Language Version 1.0, Node Tests (verificata il 14 ottobre 2026)
- W3C: XSL Transformations (XSLT) Version 3.0, XSLT 1.0 Compatibility Mode (verificata il 14 ottobre 2026)
- HTML Standard: The DOCTYPE (verificata il 14 ottobre 2026)
- MDN: Modalità quirks e modalità standard (verificata il 14 ottobre 2026)
- Manuale PHP: XSLTProcessor::transformToXml (verificata il 14 ottobre 2026)
- Manuale PHP: estensione XSL, installazione (verificata il 14 ottobre 2026)
- Manuale PHP: opzioni della riga di comando (verificata il 14 ottobre 2026)
- lxml: XSLT (verificata il 14 ottobre 2026)
- Documentazione di Node.js: indice delle API (verificata il 14 ottobre 2026)
- npm: saxon-js (verificata il 14 ottobre 2026)
- npm: xslt-processor (verificata il 14 ottobre 2026)
- Google Search Central: Creare e inviare una sitemap (verificata il 14 ottobre 2026)
- MDN: Tipi MIME comuni (verificata il 14 ottobre 2026)
- Test di XMLNest : Eseguito da XMLNest il 14 ottobre 2026 con i file di esempio di questo articolo: Python 3.14.3 con lxml 6.0.2 (libxslt 1.1.39) ha prodotto la tabella HTML con due righe, che cominciava con <html><body> senza DOCTYPE, e con <!DOCTYPE html> dopo l'aggiunta di doctype-system="about:legacy-compat"; Node.js 24.20.0 con saxon-js 2.7.0 e xslt3 2.7.0, e poi con xslt-processor 5.1.2, ha prodotto la stessa tabella. In Chromium 156.0.8078.4 con la funzione XSLT disattivata, un elemento script dello spazio dei nomi XHTML inserito in un feed RSS è stato eseguito quando il feed era inviato come application/xml, e non come application/rss+xml. PHP 8.3.35 dell'immagine Docker ufficiale wordpress:7.1.3-php8.3-apache non aveva l'estensione XSL, quindi l'esempio PHP si basa sul manuale PHP e non è stato eseguito (verificata il 14 ottobre 2026)