Substituir o XSLT do navegador: transformar XML no servidor com PHP, Python ou Node.js

O Chrome deixa de aplicar folhas XSL em 17 de novembro de 2026. Se o seu site serve XML com uma folha de estilo XSL, a solução mais duradoura é fazer a transformação no servidor e enviar HTML. Testamos os exemplos em Python e Node.js abaixo; o de PHP segue o manual e não foi executado.

O que substitui o XSLT do navegador no Chrome?

O XSLT no servidor: o seu site faz a transformação XSL antes de responder e envia HTML comum ao navegador. Em PHP, quem faz isso é a classe XSLTProcessor da extensão XSL; em Python, a classe XSLT do lxml; em Node.js, um pacote como saxon-js ou xslt-processor. Cada um recebe o seu arquivo XML e o seu arquivo XSL e devolve uma string HTML que você envia como text/html. É o único caminho em que o resultado deixa de depender do que o navegador suporta.

O XSLT para de funcionar no Chrome 158, em 17 de novembro de 2026. Sites inscritos no teste de origem (origin trial) e navegadores gerenciados por política empresarial o mantêm até o Chrome 176, em 17 de agosto de 2027. Nossa página sobre o cronograma de remoção do XSLT lista as páginas afetadas e todas as opções para visitantes.

O anúncio do Chrome descreve estes caminhos para donos de sites:

  1. Fazer o processamento XSLT no servidor e enviar o HTML gerado.
  2. Adicionar o script xslt-polyfill ao documento XML, para que a folha de estilo volte a ser aplicada no navegador.
  3. Servir JSON em vez de XML e montar a página com JavaScript.
  4. Executar XSLT na página com uma biblioteca JavaScript. O anúncio cita a da Saxonica como a maior delas, com suporte completo ao XSLT 3.0.

O polyfill é a opção que exige menos mudanças. Assim como uma biblioteca JavaScript, ele roda no navegador de cada visitante. Transformar no servidor dá mais trabalho, mas o navegador passa a receber uma página HTML comum, sem script nem extensão. A maior parte deste artigo trata desse caminho.

Se você precisa de mais tempo, o teste de origem do Chrome mantém o XSLT funcionando no seu site até o Chrome 176, em agosto de 2027. Você inscreve o site no teste e inclui o token recebido nas respostas das suas páginas. Como uma página XML não tem head HTML onde colocar uma tag meta, use o cabeçalho de resposta HTTP Origin-Trial, descrito no guia do Chrome sobre testes de origem. Se não tiver nenhum acesso ao servidor, resta o polyfill.

Devo converter meu sitemap ou meu feed RSS em HTML?

Não. Mecanismos de busca e leitores de feed baixam esses arquivos e precisam encontrar XML nesse endereço.

A documentação do Google sobre sitemaps lista os formatos aceitos: sitemaps XML, RSS, mRSS e Atom 1.0, e arquivos de texto. HTML não está entre eles. Converter o /sitemap.xml em HTML resolve um problema de exibição que só afeta leitores humanos e, em troca, estraga o arquivo para os mecanismos de busca. Nosso artigo sobre como o Google lê um sitemap do WordPress mostra que o layout não pesa em nada nessa leitura.

Deixe o XML onde está. Se os visitantes precisarem de uma versão legível, publique o HTML em outro endereço e coloque um link para ele. Para feeds, existe ainda uma opção mais leve: estilizar feeds com CSS continua funcionando no Chrome.

A transformação no servidor faz mais sentido para páginas XML feitas só para leitura humana, como um catálogo de produtos formatado com XSL.

Por que meu XSLT devolve uma tabela vazia para um sitemap?

Porque os elementos de um sitemap pertencem a um namespace, e a folha precisa se referir a eles com um prefixo. Os exemplos deste artigo usam dois arquivos para gerar uma cópia legível de um sitemap, servida em um endereço próprio, como /sitemap.html, enquanto o /sitemap.xml continua como está. O XML é um sitemap curto:

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

A folha de estilo, sitemap.xsl, transforma esse XML em uma tabela 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>

O XSLT 1.0 usa as expressões de caminho definidas na recomendação XML Path Language (XPath) 1.0 do W3C, em que um nome sem prefixo só corresponde a elementos sem namespace; o namespace padrão do documento é ignorado. Por isso a folha declara o prefixo s e escreve s:urlset/s:url. Com urlset/url, a tabela sairia vazia.

O atributo exclude-result-prefixes="s" impede que essa declaração de namespace vá parar no HTML. Sem ele, no nosso teste com o lxml, a saída começava com <html xmlns:s="...">.

Com a libxslt, usada nos exemplos em PHP e Python, o resultado sai sem DOCTYPE: no nosso teste com o lxml, a saída começava com <html><body>. A MDN explica que uma página precisa de um doctype como <!doctype html> para ser exibida no modo padrão, e não no modo quirks. Acrescente doctype-system="about:legacy-compat" ao elemento xsl:output. É a string legada que o padrão HTML prevê para geradores como a libxslt; com ela, o lxml passou a escrever <!DOCTYPE html>. Já o atributo indent="yes" só permite ao processador acrescentar espaços, como faria um formatador de XML: a libxslt pôs quebras de linha entre a maioria dos elementos, mas nenhum recuo.

Como executar XSLT em PHP, Python ou Node.js?

A extensão XSL do PHP e o lxml do Python usam a libxslt, que, segundo o anúncio do Chrome, é a biblioteca XSLT dos navegadores Chromium. O Node.js precisa de um pacote.

Para justificar a remoção, o anúncio do Chrome lembra que bibliotecas como a libxslt são código C/C++ antigo e complexo, sujeito a falhas de segurança de memória. No seu servidor, é você quem controla o que é processado: aplique apenas as suas folhas ao seu XML, nunca uma folha enviada por um usuário, e mantenha a libxslt atualizada junto com o sistema.

PHP

Use a classe XSLTProcessor da extensão XSL do PHP. Segundo o manual, a extensão vem com o PHP e é ativada com a opção --with-xsl na compilação; por isso, confira primeiro se php -m lista xsl. No PHP 8.3 que tínhamos à mão, o da imagem oficial do WordPress para Docker, a extensão não aparecia, e o XSLTProcessor não existia.

<?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 devolve o resultado como string, ou false em caso de erro, por isso o código verifica o retorno antes de enviar qualquer cabeçalho. Como a extensão XSL não estava instalada, não executamos este exemplo, que segue o manual.

Python

Instale o lxml com pip install lxml. Segundo a documentação, o lxml suporta XSLT 1.0 por meio da libxml2 e da libxslt.

from lxml import etree

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

O resultado é serializado do jeito que o elemento xsl:output da folha define. Segundo a documentação do lxml, para obter uma string do Python com str(result), esse elemento deve declarar UTF-8, como faz o nosso. Rodamos o código com Python 3.14.3 e lxml 6.0.2 e obtivemos a tabela HTML, com duas linhas e a célula de data vazia na segunda página.

Node.js

O Node.js não traz um processador XSLT entre os módulos nativos. Testamos dois pacotes.

SaxonJS. O pacote saxon-js, da Saxonica, é um processador XSLT 3.0 para Node.js. O XSLT 3.0 define um modo de compatibilidade para folhas marcadas com version="1.0", e a nossa folha rodou sem mudanças. SaxonJS.transform() espera uma folha já compilada, no formato SEF; por isso, compile a sua uma vez com o comando xslt3:

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

Depois, rode a transformação a partir de um módulo ES, por exemplo um arquivo chamado 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;

O README do saxon-js apresenta o SaxonJS como gratuito, mas com uma licença que não é de código aberto.

xslt-processor. Este pacote é escrito em JavaScript, sem dependências nativas. O README mostra a chamada: os dois arquivos passam pela classe XmlParser e depois pelo método xsltProcess de um objeto Xslt, que devolve uma promise com o resultado. O mesmo README avisa que faltam alguns recursos exigidos pelos padrões e que o pacote reconhece os elementos XSLT pelo prefixo xsl:, ignorando as declarações de namespace. Nosso exemplo funcionou; teste as suas próprias folhas antes de adotar o pacote.

Quando o script xslt-polyfill basta?

Quando você consegue editar os arquivos XML, mas não o código que os serve. O polyfill aplica a folha de estilo no navegador, com JavaScript: o XSL fica como está, e o XML só ganha uma linha de script, mostrada na nossa página sobre o cronograma de remoção do XSLT. O README do xslt-polyfill traz três pontos práticos:

  • o script é publicado no npm como xslt-polyfill, e você hospeda o arquivo no seu próprio site;
  • ele só aplica uma folha que o documento já indica com uma linha xml-stylesheet;
  • enquanto o XSLT nativo está ativo, o navegador transforma o documento antes de qualquer script rodar; por isso, teste o polyfill com o XSLT desativado em chrome://flags/#xslt.

O tipo de conteúdo também conta. No nosso teste com o XSLT desativado, um elemento script inserido em um feed RSS rodou quando o arquivo era enviado como application/xml, mas não como application/rss+xml, caso em que o Chrome o mostrava como texto puro. O anúncio do Chrome diz que acrescentar a linha a um feed não deve impedir que os leitores de feed analisem o XML. A documentação do Google sobre sitemaps não trata desse caso: se colocar a linha em um sitemap, confira depois o relatório correspondente no Search Console.

Em troca, cada visitante passa a executar um script para ver a página. Com a transformação no servidor, ele recebe o HTML pronto.

Qual Content-Type o HTML gerado deve usar?

text/html. A MDN indica text/html como tipo MIME de documentos HTML; por isso, envie Content-Type: text/html; charset=utf-8, como no exemplo em PHP. A página passa a funcionar em qualquer navegador, com ou sem XSLT.

Quem abrir uma página sem transformação já pode instalar a extensão oficial XSLT Polyfill; o XMLNest, visualizador de XML para Chrome também está disponível na Chrome Web Store. Em Por que eu criei o XMLNest, conto a história da extensão.

Fontes

  1. Chrome for Developers: Removing XSLT for a more secure browser (consultada em 14 de outubro de 2026)
  2. Chrome for Developers: Get started with origin trials (consultada em 14 de outubro de 2026)
  3. XSLT Polyfill, README de mfreed7/xslt_polyfill no GitHub (consultada em 14 de outubro de 2026)
  4. W3C: XSL Transformations (XSLT) Version 1.0 (consultada em 14 de outubro de 2026)
  5. W3C: XML Path Language Version 1.0, Node Tests (consultada em 14 de outubro de 2026)
  6. W3C: XSL Transformations (XSLT) Version 3.0, XSLT 1.0 Compatibility Mode (consultada em 14 de outubro de 2026)
  7. HTML Standard: The DOCTYPE (consultada em 14 de outubro de 2026)
  8. MDN: Modo quirks e modo padrão (consultada em 14 de outubro de 2026)
  9. Manual do PHP: XSLTProcessor::transformToXml (consultada em 14 de outubro de 2026)
  10. Manual do PHP: extensão XSL, instalação (consultada em 14 de outubro de 2026)
  11. Manual do PHP: opções da linha de comando (consultada em 14 de outubro de 2026)
  12. lxml: XSLT (consultada em 14 de outubro de 2026)
  13. Documentação do Node.js: índice da API (consultada em 14 de outubro de 2026)
  14. npm: saxon-js (consultada em 14 de outubro de 2026)
  15. npm: xslt-processor (consultada em 14 de outubro de 2026)
  16. Google Search Central: Criar e enviar um sitemap (consultada em 14 de outubro de 2026)
  17. MDN: Tipos MIME comuns (consultada em 14 de outubro de 2026)
  18. Teste do XMLNest : Executado pelo XMLNest em 14 de outubro de 2026 com os arquivos de exemplo deste artigo: Python 3.14.3 com lxml 6.0.2 (libxslt 1.1.39) gerou a tabela HTML com duas linhas, começando com <html><body> sem DOCTYPE, e com <!DOCTYPE html> depois de acrescentado doctype-system="about:legacy-compat"; Node.js 24.20.0 com saxon-js 2.7.0 e xslt3 2.7.0, e depois com xslt-processor 5.1.2, gerou a mesma tabela. No Chromium 156.0.8078.4 com o XSLT desativado, um elemento script do namespace XHTML colocado em um feed RSS rodou quando o feed era enviado como application/xml, e não como application/rss+xml. O PHP 8.3.35 da imagem oficial do Docker wordpress:7.1.3-php8.3-apache não tinha a extensão XSL, então o exemplo em PHP segue o manual do PHP e não foi executado (consultada em 14 de outubro de 2026)