Trasformatore XSLT

Applica un foglio di stile, con WebAssembly come riserva.

Input
Foglio di stile XSLT
Risultato
In attesaIncolla un documento per controllarlo. La convalida gira mentre scrivi.

Tutto viene eseguito in questa scheda. Nulla di ciò che incolli viene caricato, registrato o inviato da qualche parte. Apri il pannello di rete e verifica.

Incolla un documento nel riquadro di sinistra e un foglio di stile in quello di destra: la trasformazione viene eseguita in questa scheda. Il risultato torna come testo, con sopra il motore che lo ha prodotto e il tempo impiegato. Entrambi gli ingressi vengono prima controllati per la buona formazione, così una parentesi angolare mancante nel foglio di stile viene segnalata per quello che è e non come un errore di compilazione.

Ci si ricorre quando un foglio di stile non fa quello che ti aspetti e vuoi un ciclo rapido: una trasformazione ereditata da qualcuno che se n’è andato, un pattern di corrispondenza di cui non sei sicuro che scatti, un template che su un ingresso produce un file vuoto e su un altro no.

Lo strumento ha questa forma perché l’XSLT dei browser viene rimosso secondo un calendario pubblicato. Usa il processore nativo finché ce n’è uno e ripiega su una build WebAssembly di libxslt quando non c’è, dicendo quale motore ha girato.

Chrome sta rimuovendo XSLT, e le date sono pubblicate

Google ha depositato un’intenzione di deprecare e rimuovere nel 2025. La motivazione: l’istruzione di elaborazione xml-stylesheet compare all’incirca in un caricamento di pagina su ottomila, mentre libxslt si porta dietro un lungo curriculum in fatto di sicurezza della memoria e ha perso il suo storico manutentore nel giugno 2025. Mozilla e Apple hanno entrambe espresso sostegno. Il calendario ha date precise:

Firefox e Safari non hanno annunciato una rimozione propria, quindi lì XSLTProcessor per ora continua a funzionare. Firefox usa il proprio processore TransforMiiX invece di libxslt, ed è per questo che una manciata di fogli di stile si comporta già in modo diverso da un browser all’altro.

Questa pagina verifica la presenza di un processore nativo funzionante eseguendo una trasformazione di una riga, invece di controllare se il costruttore esiste: uno che esiste e fallisce in silenzio è peggio di uno assente. Se la verifica fallisce, carica una build WebAssembly di libxslt, il polyfill a cui punta la stessa guida alla migrazione di Chrome.

  • Chrome 143, 2 dicembre 2025: deprecazione ufficiale, avvisi in console e in Lighthouse.
  • Chrome 145, dicembre 2025: XSLT disattivato per impostazione predefinita in Canary, Dev e Beta.
  • Chrome 146, 10 marzo 2026: via di fuga tramite criterio aziendale.
  • Chrome 152, 25 agosto 2026: apre l’origin trial.
  • Chrome 158, 17 novembre 2026: XSLT smette di funzionare sul canale stabile, salvo sotto il trial o il criterio.
  • Chrome 176, 17 agosto 2027: entrambe le vie di fuga scadono. XSLTProcessor e l’istruzione di elaborazione xml-stylesheet spariscono. La forma type="text/css" di quell’istruzione non è toccata.

I template sono regole, non uno script

Un foglio di stile è un insieme di regole template, ciascuna con un pattern di corrispondenza. Il processore parte dalla radice del documento, trova il template che corrisponde meglio, lo esegue, e dove quel template dice xsl:apply-templates ripete il procedimento per i figli selezionati. Nulla viene eseguito nell’ordine in cui l’hai scritto.

xsl:apply-templates restituisce il controllo all’insieme delle regole: ogni nodo selezionato trova il proprio template, quindi figli eterogenei ottengono regole proprie invece di una catena di rami xsl:choose. xsl:for-each itera sul posto, applicando un solo corpo a ciascun nodo selezionato, cosa che si legge meglio per un elenco piatto di righe. Entrambi cambiano il nodo di contesto, ed è da lì che nasce la maggior parte delle segnalazioni di un’espressione select improvvisamente sbagliata.

XSLT ha anche regole incorporate che non hai scritto tu: quella predefinita per gli elementi applica i template ai loro figli, e quella per i nodi di testo ricopia il testo così com’è. È questa coppia a far sì che un foglio di stile i cui pattern non corrispondono a nulla produca comunque una pagina di testo tutto attaccato.

Quando la trasformazione non produce nulla

Un risultato vuoto è il secondo problema XSLT più segnalato dopo il testo tutto attaccato, e ha una causa dominante: i namespace. Un foglio di stile scritto per XML senza namespace non corrisponderà a elementi che stanno in un namespace, anche se i nomi sembrano identici nei due file. Se la radice della tua sorgente porta xmlns="http://example.com/orders", <xsl:template match="order"> chiede {}order mentre il documento contiene {http://example.com/orders}order. Nessun template scatta, e XSLT non lo considera un errore.

Dichiara il namespace nel foglio di stile sotto un prefisso a tua scelta e usalo in ogni match e in ogni select. XSLT 1.0 condivide il limite di XPath 1.0, quindi il prefisso non è facoltativo, e xpath-default-namespace non aiuta perché appartiene a XSLT 2.0 e i browser lo ignorano.

Un foglio di stile che dichiara version="2.0" o "3.0" viene segnalato prima dell’esecuzione, perché entrambi i motori sono 1.0 e i costrutti successivi possono fallire in silenzio, dando un risultato sottilmente sbagliato anziché assente. E se la radice del foglio di stile non sta in http://www.w3.org/1999/XSL/Transform non funziona proprio nulla: quell’URI è identico per tutte e tre le versioni di XSLT, e sbagliato a scriverlo compila in un foglio di stile che non contiene alcuna regola.

Ciò che nessun motore XSLT da browser sa fare

Solo XSLT 1.0. Nessun browser ha mai rilasciato 2.0 o 3.0, e neanche libxslt li implementa, quindi il ripiego WebAssembly non alza il tetto. Restano fuori xsl:function, xsl:for-each-group (il raggruppamento è ancora la tecnica muenchiana con key()), xsl:analyze-string e xsl:result-document. Saxon-JS è l’unica strada verso 2.0 o 3.0 in un browser.

Le risorse esterne sono l’altro limite duro. document(), xsl:include e xsl:import di URL remoti sono soggetti alla same-origin policy, quindi un foglio di stile che tira dentro una tabella di lookup via HTTP fallirà. Qui nulla scarica per conto tuo e non c’è alcun proxy. Anche qualche lacuna specifica dei singoli motori frega la gente:

  • disable-output-escaping non è implementato in Firefox: TransforMiiX produce un albero anziché una stringa serializzata, e la specifica permette al processore di riprendersi trattando l’attributo come no. Usa invece un riferimento numerico di carattere come &#160;.
  • xsl:namespace-alias non è supportato in Firefox, e nemmeno l’asse namespace:: di XPath.
  • xsl:output è in gran parte consultivo quando il processore è pilotato da script, perché il risultato è un DOM: indent, encoding e le proprietà del doctype hanno poco effetto. Questa pagina ne legge method per scegliere fra testo e markup serializzato.
  • La copertura di EXSLT è diversa. libxslt, usato da Chrome, Safari e dal ripiego WebAssembly, ne implementa gran parte. Firefox ne implementa un sottoinsieme.
  • transformToDocument e transformToFragment restituiscono null in caso di errore anziché sollevare un’eccezione, e il motivo vero compare solo in console.

Farlo da codice

Un foglio di stile è input eseguibile. Ognuno dei motori qui sotto può leggere file, aprire connessioni di rete o scrivere su disco tramite document(), xsl:import o una funzione di estensione, quindi ogni esempio disattiva tutto questo. Tratta un foglio di stile che non hai scritto tu come uno script che non hai scritto tu.

// 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 sintassi dei parametri è diversa in ognuno di questi, e non per questioni estetiche: in lxml, in xsltproc --param e in Saxon il valore di un parametro è un’espressione XPath, quindi la stringa en va scritta "'en'" altrimenti viene letta come un passo di percorso che seleziona gli elementi di nome en. setParameter in JavaScript e XsltArgumentList in .NET prendono semplici stringhe.

Domande frequenti

Questo strumento smetterà di funzionare quando Chrome rimuoverà XSLT?

No. Chrome 158, il 17 novembre 2026, fa smettere XSLT sul canale stabile, e Chrome 176 il 17 agosto 2027 rimuove del tutto XSLTProcessor e l’istruzione di elaborazione xml-stylesheet. Uno strumento costruito solo sul processore nativo si romperebbe in quelle date.

Questa pagina esegue prima una trasformazione di prova di una riga. Finché il tuo browser ha un motore nativo funzionante è quello che viene usato; quando la prova fallisce, al suo posto viene caricata una build WebAssembly di libxslt e il pannello dice quale motore ha girato. Il ripiego viene scaricato solo quando serve, quindi chi usa Firefox e Safari non lo scarica mai.

Posso eseguire XSLT 2.0 o 3.0 qui?

No, e non può farlo nessun browser. Chrome e Safari usano libxslt, Firefox il proprio TransforMiiX, e tutti e tre implementano 1.0. Anche il ripiego WebAssembly è libxslt, quindi non cambia il tetto.

Se il tuo foglio di stile dichiara version="2.0" o "3.0", questa pagina avvisa prima di eseguire anziché dopo, perché un processore 1.0 non rifiuta di netto un foglio 2.0: esegue ciò che riconosce e maltratta in silenzio il resto. Saxon è la risposta pratica, Saxon-JS in un browser e Saxon-HE sulla JVM o su .NET.

Perché il mio foglio di stile produce un risultato vuoto?

Quasi sempre una discrepanza di namespace. Se la tua sorgente dichiara un namespace predefinito sulla radice, come xmlns="http://example.com/orders", ogni elemento senza prefisso al suo interno appartiene a quel namespace. Un template scritto <xsl:template match="order"> chiede un elemento order in nessun namespace, quindi non scatta alcuna regola e l’output è vuoto. XSLT non lo considera un errore.

Dichiara il namespace nel foglio di stile con un prefisso, per esempio xmlns:o="http://example.com/orders", poi scrivi match="o:order" e select="o:line" ovunque. Altre due cause da escludere: una radice del foglio di stile che non sta in http://www.w3.org/1999/XSL/Transform, e nessun template che corrisponda alla radice del documento.

Il mio XML e il mio foglio di stile vengono caricati da qualche parte?

Né l’uno né l’altro. Entrambi i riquadri restano in questa scheda: il controllo di buona formazione gira in un Web Worker, e la trasformazione gira sul processore incorporato del tuo browser oppure su un modulo WebAssembly caricato nella pagina. Qui non c’è alcun server a riceverli.

È una domanda più affilata per XSLT che per quasi ogni altro strumento, perché spesso la metà di valore è il foglio di stile: una trasformazione che mappa lo schema di un partner sul tuo interno codifica regole di business, nomi di campo e talvolta endpoint. Una conseguenza dell’esecuzione locale è che il risultato viene mostrato come testo e mai inserito nella pagina come markup, dato che un foglio di stile può generare elementi script.

Perché il mio output contiene tutto il testo del documento senza markup?

Sei incappato nelle regole template incorporate di XSLT. La specifica definisce comportamenti predefiniti che esistono che tu li scriva o no: la regola per gli elementi applica i template ai loro figli, e la regola per i nodi di testo ricopia il testo direttamente.

Quindi, quando nessuno dei tuoi template corrisponde, il processore non si ferma. Percorre l’intero albero con quei valori predefiniti e stampa ogni nodo di testo che trova, rientri della sorgente compresi. La causa di solito è un namespace. La diagnosi rapida è aggiungere <xsl:template match="text()"/>, che sopprime il predefinito del testo: se l’output diventa vuoto, nessuna tua regola stava scattando.

Qual è la differenza fra apply-templates e for-each?

xsl:for-each itera sul posto. Seleziona un elenco di nodi ed esegue il suo corpo una volta per nodo in ordine di documento, e quel corpo è fisso, quindi ogni nodo viene trattato allo stesso modo. Si legge come un ciclo e si presta a un elenco piatto e omogeneo, per esempio trasformare ogni elemento row in una riga di tabella.

xsl:apply-templates restituisce il controllo all’insieme delle regole, lasciando che ogni nodo selezionato trovi il template che gli corrisponde meglio, così tipi di elemento diversi ricevono trattamenti diversi senza condizionali. Accetta inoltre un attributo mode, quindi lo stesso nodo può essere elaborato due volte sotto regole diverse, una per un indice e una per il corpo. for-each non ha un equivalente.

Il foglio di stile può caricare un altro file con document() o xsl:import?

Qui no. document(), xsl:include e xsl:import risolvono tutti una URL, e in un browser quei caricamenti sono soggetti alla same-origin policy, quindi tutto ciò che è remoto viene bloccato. Questa pagina inoltre non ha un proxy di scaricamento, e di proposito: fare da proxy significherebbe che il documento di destinazione, e la URL stessa, passano per un server controllato da questo sito.

Incorpora il secondo documento nella tua sorgente e selezionalo con normale XPath, sposta la tabella di lookup in un parametro del foglio di stile, oppure esegui la trasformazione in locale con xsltproc, dove document() si risolve rispetto al tuo filesystem.

Strumenti correlati

Approfondimenti