Transformador XSLT

Aplica uma folha de estilo, com WebAssembly como reserva.

Entrada
Folha de estilo XSLT
Resultado
AguardandoCole um documento para verificá-lo. A validação roda enquanto você digita.

Tudo roda nesta aba. Nada do que você colar é enviado, registrado ou transmitido para lugar nenhum. Abra o painel de rede e confira.

Cole um documento no painel esquerdo e uma folha de estilos no direito, e a transformação roda nesta aba. O resultado volta como texto, com o motor que o produziu e o tempo que levou acima da saída. As duas entradas são verificadas antes quanto à boa formação, então um sinal de maior faltando na folha de estilos é relatado como tal, e não como falha de compilação.

Você recorre a ela quando uma folha de estilos não faz o que você espera e quer um ciclo rápido: uma transformação herdada de alguém que saiu, um padrão de correspondência que você não sabe se dispara, um template que produz um arquivo vazio numa entrada e não em outra.

A ferramenta tem o formato que tem porque o XSLT dos navegadores está sendo removido segundo um calendário publicado. Ela usa o processador nativo enquanto houver um e recua para uma compilação WebAssembly do libxslt quando não houver, dizendo qual motor rodou.

O Chrome está removendo o XSLT, e as datas estão publicadas

O Google protocolou uma intenção de depreciar e remover em 2025. O raciocínio: a instrução de processamento xml-stylesheet aparece em cerca de um carregamento de página a cada oito mil, enquanto o libxslt carrega um longo histórico de segurança de memória e perdeu seu mantenedor de longa data em junho de 2025. Mozilla e Apple manifestaram apoio. O calendário tem datas:

Firefox e Safari não anunciaram remoção própria, então o XSLTProcessor continua funcionando lá por ora. O Firefox usa o próprio processador TransforMiiX em vez do libxslt, e é por isso que um punhado de folhas de estilos já se comporta de forma diferente entre navegadores.

Esta página sonda se existe um processador nativo funcionando rodando uma transformação de uma linha, em vez de checar se o construtor existe: um que existe e falha em silêncio é pior que um ausente. Se a sondagem falha, ela carrega uma compilação WebAssembly do libxslt, o polyfill para o qual a própria orientação de migração do Chrome aponta.

  • Chrome 143, 2 de dezembro de 2025: depreciação oficial, avisos no console e no Lighthouse.
  • Chrome 145, dezembro de 2025: XSLT desligado por padrão no Canary, Dev e Beta.
  • Chrome 146, 10 de março de 2026: escapatória por política empresarial.
  • Chrome 152, 25 de agosto de 2026: abre o teste de origem.
  • Chrome 158, 17 de novembro de 2026: o XSLT deixa de funcionar no estável, exceto sob o teste ou a política.
  • Chrome 176, 17 de agosto de 2027: as duas escapatórias expiram. O XSLTProcessor e a instrução de processamento xml-stylesheet somem. A forma type="text/css" dessa instrução não é afetada.

Templates são regras, não um roteiro

Uma folha de estilos é um conjunto de regras de template, cada uma com um padrão de correspondência. O processador começa na raiz do documento, encontra o template que melhor casa, executa-o, e onde esse template diz xsl:apply-templates ele repete o processo para os filhos selecionados. Nada roda na ordem em que você escreveu.

xsl:apply-templates devolve o controle ao conjunto de regras: cada nó selecionado encontra o próprio template, então filhos heterogêneos ganham regras próprias em vez de uma corrente de ramos xsl:choose. xsl:for-each itera ali mesmo, aplicando um único corpo a cada nó selecionado, o que se lê melhor para uma lista plana de linhas. Os dois mudam o nó de contexto, de onde vem a maioria dos relatos de uma expressão select subitamente errada.

O XSLT também tem regras embutidas que você não escreveu: a padrão para elementos aplica templates aos filhos, e a padrão para nós de texto copia o texto adiante. É esse par que faz uma folha de estilos cujos padrões não casam com nada ainda assim produzir uma página de texto todo emendado.

Quando a transformação não produz nada

Um resultado vazio é o segundo problema de XSLT mais relatado depois do texto emendado, e tem uma causa dominante: espaços de nomes. Uma folha de estilos escrita para XML sem espaço de nomes não vai casar com elementos que estão num espaço de nomes, mesmo que os nomes pareçam idênticos nos dois arquivos. Se a raiz da sua fonte traz xmlns="http://example.com/orders", <xsl:template match="order"> pede {}order enquanto o documento tem {http://example.com/orders}order. Nenhum template dispara, e o XSLT não considera isso um erro.

Declare o espaço de nomes na folha de estilos sob um prefixo à sua escolha e use-o em cada match e cada select. O XSLT 1.0 compartilha a limitação do XPath 1.0, então o prefixo não é opcional, e xpath-default-namespace não ajuda porque é do XSLT 2.0 e os navegadores o ignoram.

Uma folha de estilos que declara version="2.0" ou "3.0" é sinalizada antes de rodar, porque os dois motores são 1.0 e construções posteriores podem falhar em silêncio, dando uma saída sutilmente errada em vez de ausente. E se a raiz da folha de estilos não estiver em http://www.w3.org/1999/XSL/Transform, nada funciona: essa URI é idêntica para as três versões do XSLT, e uma grafada errado compila para uma folha de estilos sem nenhuma regra dentro.

O que nenhum motor XSLT de navegador consegue fazer

Só XSLT 1.0. Nenhum navegador jamais entregou 2.0 ou 3.0, e o libxslt também não os implementa, então o recuo para WebAssembly não levanta o teto. Isso exclui xsl:function, xsl:for-each-group (agrupar continua sendo a técnica muenchiana com key()), xsl:analyze-string e xsl:result-document. O Saxon-JS é o único caminho para 2.0 ou 3.0 num navegador.

Recursos externos são o outro limite duro. document(), xsl:include e xsl:import de URLs remotas são regidos pela política de mesma origem, então uma folha de estilos que puxa uma tabela de consulta por HTTP vai falhar. Aqui nada busca nada por você e não há proxy. Algumas lacunas específicas de cada motor também pegam as pessoas:

  • disable-output-escaping não é implementado no Firefox: o TransforMiiX produz uma árvore em vez de uma cadeia serializada, e a especificação permite ao processador se recuperar tratando o atributo como no. Use no lugar uma referência numérica de caractere como &#160;.
  • xsl:namespace-alias não é suportado no Firefox, assim como o eixo namespace:: do XPath.
  • xsl:output é em boa parte consultivo quando o processador é dirigido por script, porque o resultado é um DOM: indent, encoding e as propriedades de doctype têm pouco efeito. Esta página lê dele o method para decidir entre texto e marcação serializada.
  • A cobertura de EXSLT difere. O libxslt, usado por Chrome, Safari e pelo recuo WebAssembly, implementa quase tudo. O Firefox implementa um subconjunto.
  • transformToDocument e transformToFragment devolvem null ao falhar em vez de lançar, e o motivo verdadeiro só aparece no console.

Fazer isso em código

Uma folha de estilos é entrada executável. Todo motor abaixo pode ler arquivos, abrir conexões de rede ou escrever em disco por meio de document(), xsl:import ou de uma função de extensão, então cada exemplo desliga isso. Trate uma folha de estilos que você não escreveu como um script que você não escreveu.

// 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

A sintaxe de parâmetros difere em cada um deles, e não de forma cosmética: em lxml, no --param do xsltproc e no Saxon, o valor de um parâmetro é uma expressão XPath, então a cadeia en precisa ser escrita "'en'" ou é lida como um passo de caminho que seleciona elementos chamados en. setParameter em JavaScript e XsltArgumentList em .NET aceitam cadeias simples.

Perguntas frequentes

Esta ferramenta vai parar de funcionar quando o Chrome remover o XSLT?

Não. O Chrome 158, em 17 de novembro de 2026, faz o XSLT parar de funcionar no estável, e o Chrome 176, em 17 de agosto de 2027, remove o XSLTProcessor e a instrução de processamento xml-stylesheet de vez. Uma ferramenta construída só sobre o processador nativo quebraria nessas datas.

Esta página roda antes uma transformação de sondagem de uma linha. Enquanto o seu navegador tiver um motor nativo funcionando, é ele que é usado; quando a sondagem falha, uma compilação WebAssembly do libxslt é carregada no lugar e o painel diz qual motor rodou. O recuo só é baixado quando preciso, então quem usa Firefox e Safari nunca o baixa.

Posso rodar XSLT 2.0 ou 3.0 aqui?

Não, e nenhum navegador pode. Chrome e Safari usam o libxslt, o Firefox usa o próprio TransforMiiX, e os três implementam 1.0. O recuo WebAssembly também é libxslt, então não muda o teto.

Se a sua folha de estilos declara version="2.0" ou "3.0", esta página avisa antes de rodar, e não depois, porque um processador 1.0 não rejeita de saída uma folha 2.0: ele roda o que reconhece e trata o resto errado em silêncio. O Saxon é a resposta prática, Saxon-JS no navegador e Saxon-HE na JVM ou no .NET.

Por que minha folha de estilos produz um resultado vazio?

Quase sempre uma divergência de espaço de nomes. Se a sua fonte declara um espaço de nomes padrão na raiz, como xmlns="http://example.com/orders", todo elemento sem prefixo lá dentro pertence a esse espaço. Um template escrito como <xsl:template match="order"> pede um elemento order em nenhum espaço de nomes, então nenhuma regra dispara e a saída sai vazia. O XSLT não trata isso como erro.

Declare o espaço de nomes na folha de estilos com um prefixo, digamos xmlns:o="http://example.com/orders", e então escreva match="o:order" e select="o:line" em todo lugar. Duas outras causas a descartar: uma raiz de folha de estilos que não está em http://www.w3.org/1999/XSL/Transform, e nenhum template casando com a raiz do documento.

Meu XML e minha folha de estilos são enviados?

Nem um nem outra. Os dois painéis ficam nesta aba: a verificação de boa formação roda num Web Worker, e a transformação roda no processador embutido do seu navegador ou num módulo WebAssembly carregado na página. Não há servidor aqui para recebê-los.

Essa é uma pergunta mais aguda para o XSLT do que para a maioria das ferramentas, porque a folha de estilos costuma ser a metade valiosa: uma transformação que mapeia o esquema de um parceiro no seu interno codifica regras de negócio, nomes de campos e às vezes endpoints. Uma consequência de rodar localmente é que a saída é mostrada como texto e nunca inserida na página como marcação, já que uma folha de estilos pode gerar elementos script.

Por que minha saída traz todo o texto do documento sem marcação nenhuma?

Você esbarrou nas regras de template embutidas do XSLT. A especificação define padrões que existem quer você os escreva ou não: a regra para elementos aplica templates aos filhos, e a regra para nós de texto copia o texto direto.

Então, quando nenhum dos seus templates casa, o processador não para. Ele percorre a árvore inteira com esses padrões e imprime cada nó de texto que encontra, indentação da fonte incluída. A causa costuma ser um espaço de nomes. O diagnóstico rápido é acrescentar <xsl:template match="text()"/>, que suprime o padrão do texto: se a saída ficar vazia, nenhuma regra sua estava disparando.

Qual é a diferença entre apply-templates e for-each?

xsl:for-each itera ali mesmo. Ele seleciona uma lista de nós e roda o corpo uma vez por nó em ordem de documento, e esse corpo é fixo, então todo nó é tratado igual. Lê-se como um laço e serve a uma lista plana e homogênea, como transformar cada elemento row numa linha de tabela.

xsl:apply-templates devolve o controle ao conjunto de regras, deixando cada nó selecionado encontrar o template que melhor lhe casa, então tipos de elemento diferentes recebem tratamento diferente sem condicionais. Ele também aceita um atributo mode, de modo que o mesmo nó pode ser processado duas vezes sob regras distintas, uma para um sumário e outra para o corpo. for-each não tem equivalente.

A folha de estilos pode carregar outro arquivo com document() ou xsl:import?

Aqui não. document(), xsl:include e xsl:import todos resolvem uma URL, e num navegador esses carregamentos estão sujeitos à política de mesma origem, então qualquer coisa remota é bloqueada. Esta página também não tem proxy de busca, e isso é deliberado: fazer de proxy significaria o documento de destino, e a própria URL, passando por um servidor que este site controla.

Embuta o segundo documento na sua fonte e selecione-o com XPath comum, mova a tabela de consulta para um parâmetro da folha de estilos, ou rode a transformação localmente com o xsltproc, onde document() resolve contra o seu sistema de arquivos.

Ferramentas relacionadas

Leitura complementar