XSLT トランスフォーマー

スタイルシートを適用。WebAssembly に自動で切り替わります。

入力
XSLT スタイルシート
結果
待機中文書を貼り付けると検査します。入力中にそのまま検証されます。

すべてこのタブ内で実行されます。貼り付けた内容がアップロード・記録・送信されることはありません。 ネットワークパネルを開いて確認する.

左のペインに文書を、右にスタイルシートを貼り付けてください。変換はこのタブの中で実行されます。結果はテキストで返り、出力の上には、それを生み出したエンジンと所要時間が出ます。どちらの入力もまず整形式かどうかを検査するので、スタイルシートで山かっこが 1 つ足りなければ、コンパイル失敗ではなくそのままの形で報告されます。

スタイルシートが思ったとおりに動かず、素早く試したいときに使ってください。辞めた人から引き継いだ変換、本当に発火しているか自信のない照合パターン、ある入力では空のファイルしか作らないテンプレート。

このツールがこういう形をしているのは、ブラウザーの XSLT が公表された日程に沿って取り除かれつつあるからです。ネイティブのプロセッサーがあるうちはそれを使い、ないときは libxslt の WebAssembly ビルドに退き、どちらのエンジンが走ったかを明示します。

Chrome は XSLT を取り除きつつあり、日付も公表されている

Google は 2025 年に非推奨化と削除の意思表明を提出しました。理由はこうです。xml-stylesheet 処理命令が現れるのはおよそ 8000 回のページ読み込みに 1 回ほどである一方、libxslt はメモリ安全性の長い履歴を抱え、2025 年 6 月に長年のメンテナーを失いました。Mozilla と Apple はどちらも支持を示しています。日程には日付が入っています。

Firefox と Safari は自前の削除を発表していないので、XSLTProcessor は当面そこでは動き続けます。Firefox は libxslt ではなく自社の TransforMiiX プロセッサーを使っており、いくつかのスタイルシートがすでにブラウザー間で違う振る舞いをするのはそのためです。

このページは、コンストラクターが存在するかを見るのではなく、1 行の変換を走らせて動くネイティブプロセッサーがあるかを探ります。存在していて黙って失敗するもののほうが、存在しないものより厄介だからです。探りに失敗したら、Chrome 自身の移行案内が指し示すポリフィル、libxslt の WebAssembly ビルドを読み込みます。

  • Chrome 143、2025 年 12 月 2 日:正式な非推奨化。コンソールと Lighthouse に警告。
  • Chrome 145、2025 年 12 月:Canary、Dev、Beta で XSLT が既定で無効に。
  • Chrome 146、2026 年 3 月 10 日:企業ポリシーによる逃げ道。
  • Chrome 152、2026 年 8 月 25 日:オリジントライアル開始。
  • Chrome 158、2026 年 11 月 17 日:安定版で XSLT が動かなくなる。トライアルかポリシーの下を除く。
  • Chrome 176、2027 年 8 月 17 日:逃げ道が両方とも失効。XSLTProcessor と xml-stylesheet 処理命令が消えます。同じ命令の type="text/css" 形式は影響を受けません。

テンプレートは規則であって、手順書ではない

スタイルシートはテンプレート規則の集まりで、それぞれに照合パターンがあります。プロセッサーは文書のルートから始め、もっともよく合うテンプレートを見つけて実行し、そのテンプレートが xsl:apply-templates と言う場所で、選ばれた子に対して同じことを繰り返します。書いた順に走るものは何もありません。

xsl:apply-templates は制御を規則の集まりに返します。選ばれた各ノードが自分のテンプレートを見つけるので、性質の違う子たちは xsl:choose の分岐を連ねる代わりに、それぞれの規則を得ます。xsl:for-each はその場で反復し、選ばれた各ノードに 1 つの本体を当てはめます。平らな行のリストにはこちらのほうが読みやすいでしょう。どちらもコンテキストノードを変えます。「select 式が急におかしくなった」という報告のほとんどは、ここから来ています。

XSLT には、あなたが書いていない組み込みの規則もあります。要素に対する既定は子にテンプレートを適用すること、テキストノードに対する既定はテキストをそのまま通すこと。この 2 つがあるために、どのパターンも何にも合っていないスタイルシートが、それでも文字の連なったページを吐き出します。

変換が何も生み出さないとき

空の結果は、文字が連なって出る問題に次いで 2 番目に多く報告される XSLT の問題で、支配的な原因が 1 つあります。名前空間です。名前空間なしの XML のために書かれたスタイルシートは、名前空間の中にある要素には合いません。両方のファイルで名前は同じに見えるのに、です。ソースのルートが xmlns="http://example.com/orders" を持っているなら、<xsl:template match="order"> は {}order を求め、文書のほうは {http://example.com/orders}order を持っています。どのテンプレートも発火せず、XSLT はそれをエラーとみなしません。

好きな接頭辞でスタイルシートに名前空間を宣言し、すべての match とすべての select でそれを使ってください。XSLT 1.0 は XPath 1.0 の制限を共有するので接頭辞は省けませんし、xpath-default-namespace は XSLT 2.0 のもので、ブラウザーは無視するので助けになりません。

version="2.0" や "3.0" を宣言したスタイルシートは、実行前に指摘します。どちらのエンジンも 1.0 で、後の版の構文は黙って失敗しうるため、出力が「ない」のではなく「微妙に間違っている」ことになるからです。そして、スタイルシートのルートが http://www.w3.org/1999/XSL/Transform になければ、そもそも何も動きません。この URI は XSLT の 3 つの版すべてで同一で、綴りを誤ると規則が 1 つも入っていないスタイルシートにコンパイルされます。

どのブラウザーの XSLT エンジンにもできないこと

XSLT 1.0 だけです。2.0 や 3.0 を出荷したブラウザーは一度もなく、libxslt もそれらを実装していないので、WebAssembly の代替でも天井は上がりません。つまり xsl:function、xsl:for-each-group(グループ化は今も key() を使うミューニック法)、xsl:analyze-string、xsl:result-document は使えません。ブラウザーで 2.0 や 3.0 に至る道は Saxon-JS だけです。

外部リソースがもう 1 つの固い限界です。document() と、リモート URL の xsl:include・xsl:import は同一生成元ポリシーの支配下にあるので、HTTP で参照表を引き込むスタイルシートは失敗します。ここでは何もあなたの代わりに取得しませんし、プロキシもありません。エンジンごとの穴も、いくつか人を引っかけます。

  • disable-output-escaping は Firefox で実装されていません。TransforMiiX は直列化された文字列ではなく木を作り、仕様はプロセッサーがこの属性を no とみなして回復することを許しています。代わりに &#160; のような数値文字参照を使ってください。
  • xsl:namespace-alias は Firefox で未対応で、XPath の namespace:: 軸も同様です。
  • xsl:output は、プロセッサーをスクリプトから動かす場合、ほぼ助言にとどまります。結果が DOM なので、indent、encoding、doctype 関連の属性はほとんど効きません。このページは method だけを読み、テキストか直列化されたマークアップかを決めます。
  • EXSLT の対応範囲は異なります。Chrome、Safari、WebAssembly の代替が使う libxslt はほとんどを実装し、Firefox は一部だけです。
  • transformToDocument と transformToFragment は失敗時に例外を投げず null を返し、本当の理由はコンソールにしか出ません。

コードで行う

スタイルシートは実行される入力です。下のどのエンジンも、document()、xsl:import、あるいは拡張関数を通じてファイルを読み、ネットワーク接続を開き、ディスクに書けます。ですから各サンプルはそれを切ります。自分が書いていないスタイルシートは、自分が書いていないスクリプトと同じように扱ってください。

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

パラメーターの書き方はこれらのどれでも違い、それも見た目だけの違いではありません。lxml、xsltproc の --param、Saxon では、パラメーター値は XPath 式です。ですから文字列 en は "'en'" と書かねばならず、そうしないと en という名前の要素を選ぶパスのステップとして読まれます。JavaScript の setParameter と .NET の XsltArgumentList はただの文字列を取ります。

よくある質問

Chrome が XSLT を削除したら、このツールは動かなくなりますか。

いいえ。2026 年 11 月 17 日の Chrome 158 で安定版の XSLT は動かなくなり、2027 年 8 月 17 日の Chrome 176 で XSLTProcessor と xml-stylesheet 処理命令が完全に取り除かれます。ネイティブのプロセッサーだけで作られたツールなら、その日に壊れます。

このページはまず 1 行の変換で探りを入れます。あなたのブラウザーに動くネイティブエンジンがあるうちはそれを使い、探りに失敗したら代わりに libxslt の WebAssembly ビルドを読み込み、パネルにどちらが走ったかを出します。代替は必要なときだけ取得するので、Firefox や Safari の利用者が落とすことはありません。

ここで XSLT 2.0 や 3.0 を動かせますか。

できませんし、どのブラウザーでもできません。Chrome と Safari は libxslt、Firefox は自社の TransforMiiX を使い、三者とも 1.0 の実装です。WebAssembly の代替も libxslt なので、天井は変わりません。

スタイルシートが version="2.0" や "3.0" を宣言していれば、このページは実行の後ではなく前に警告します。1.0 のプロセッサーは 2.0 のスタイルシートをきっぱり拒むわけではなく、分かるところだけ実行して残りを黙って取り違えるからです。現実的な答えは Saxon、ブラウザーなら Saxon-JS、JVM や .NET なら Saxon-HE です。

スタイルシートの結果が空になるのはなぜですか。

ほとんどの場合は名前空間の食い違いです。ソースがルートで既定名前空間、たとえば xmlns="http://example.com/orders" を宣言していれば、その中の接頭辞なしの要素はすべてその名前空間に属します。<xsl:template match="order"> と書いたテンプレートは名前空間なしの order 要素を求めるので、どの規則も発火せず出力は空になります。XSLT はそれをエラーとして扱いません。

スタイルシートで接頭辞つきに名前空間を宣言し(たとえば xmlns:o="http://example.com/orders")、どこでも match="o:order"、select="o:line" と書いてください。除外しておきたい原因があと 2 つ。スタイルシートのルートが http://www.w3.org/1999/XSL/Transform にない場合と、文書ルートに合うテンプレートがない場合です。

XML とスタイルシートはアップロードされますか。

どちらもされません。両方のペインはこのタブに留まります。整形式の検査は Web Worker で走り、変換はブラウザー内蔵のプロセッサーか、ページに読み込んだ WebAssembly モジュールで走ります。ここには受け取るサーバーがありません。

これは多くのツールより XSLT でこそ鋭い問いです。価値のある半分はしばしばスタイルシートのほうだからです。取引先のスキーマを自社のものへ写す変換には、業務規則、フィールド名、ときにはエンドポイントが書き込まれています。ローカルで走ることの結果として、出力はテキストとして表示し、決してマークアップとしてページに差し込みません。スタイルシートは script 要素を生成できるからです。

出力が、文書のテキスト全部でマークアップなし、になるのはなぜですか。

XSLT の組み込みテンプレート規則に当たっています。仕様は、あなたが書こうが書くまいが存在する既定を定めています。要素に対する規則は子にテンプレートを適用し、テキストノードに対する規則はテキストをそのまま通します。

ですから、あなたのテンプレートがどれも合わないとき、プロセッサーは止まりません。その既定を使って木全体を歩き、見つけたテキストノードを、ソースの字下げごと、すべて印字します。原因はたいてい名前空間です。手早い切り分けは <xsl:template match="text()"/> を足すことです。テキストの既定を抑えるので、これで出力が空になるなら、あなたの規則は 1 つも発火していなかったということです。

apply-templates と for-each の違いは何ですか。

xsl:for-each はその場で反復します。ノードのリストを選び、文書順に 1 ノードにつき 1 回本体を走らせます。本体は固定なので、どのノードも同じに扱われます。ループのように読め、row 要素をすべて表の行にするような、平らで同質のリストに向きます。

xsl:apply-templates は制御を規則の集まりに返し、選ばれた各ノードが自分にいちばん合うテンプレートを見つけられるようにします。ですから要素の型が違えば、条件分岐なしに扱いも変わります。さらに mode 属性を取るので、同じノードを違う規則で二度、目次用に一度、本文用に一度、処理できます。for-each に相当するものはありません。

スタイルシートから document() や xsl:import で別のファイルを読み込めますか。

ここではできません。document()、xsl:include、xsl:import はいずれも URL を解決し、ブラウザーではその読み込みが同一生成元ポリシーの対象になるので、リモートのものはすべて遮られます。このページには取得用のプロキシも、意図的に、ありません。プロキシするということは、対象の文書も URL 自体も、このサイトが管理するサーバーを通るということだからです。

2 つ目の文書をソースに埋め込んで普通の XPath で選ぶか、参照表をスタイルシートのパラメーターに移すか、あるいは xsltproc でローカルに変換を走らせてください。そこでは document() がファイルシステムに対して解決します。

関連ツール

関連する解説