🥅

[XSLT 3.0]xsl:try/xsl:catchでできること

に公開

https://adventar.org/calendars/11635
XSLT 3.0デバッグまわり Advent Calendar 2025 の6日目。
別に1人アドカレとかではないです。

XSLTにおいてエラーをキャッチして処理を継続するということ

一般的なプログラミング言語で、try-catchの機構は異常な箇所のハンドリングを経て正常系に復帰したり、そのままプログラムが死ぬ前にデータ整理の処理を行ったりするのに使われます。
XSLT 3.0で導入されたxsl:try/xsl:catchもそれに倣うものではありますが、正常系への復帰で何を行うのかについては、より限定された文脈となります。
というのもXSLTは基本的に木構造の変換を行い、最終的に木構造の出力を結果とするからです。
この辺りについて触れているのが仕様のRecovery of Result Treesというセクションです。後で出てきます。
動的エラーについては、(xsl:tryのような機構を使わない限り、)発生した時点で処理が失敗し、結果となる文書は出力されません。
言語や実装の方針としてそれはそれで結構なのですが、リアルワールドだと「よく分からない失敗箇所については後で直すから一先ず最後まで出力してくれ」ということはままあります。
「途中で処理失敗したとき、出力文書をどのようにするか」に介入できるのがxsl:try/xsl:catchというわけです。

xsl:try, xsl:catchの基本形

xsl:tryの中にエラーを吐く可能性のある処理を書きます。
例によって@selectでも<xsl:try>要素の中のどちらで書いても構いません。
xsl:tryの中には、最低1つxsl:catchが必要です。
xsl:tryの中でエラーが生じると、xsl:catchの中身が実行されるわけですね。

……最低1つと書いたのは、エラー種類に応じて別のxsl:catchを書けるからです。
これは後述します。

<xsl:try select="some:function(...)">
  <xsl:catch>
  <!-- tryの内容がエラーを出力したらxsl:catchの内容が実行される -->
  </xsl:catch>
</xsl:try>

xsl:catchの中でエラーの中身を確認する

突然ですが、処理系が吐くエラーにも名前空間が用意されています。
普通errのprefixを使います。

xmlns:err="http://www.w3.org/2005/xqt-errors"

この名前空間では、各種エラーと、その一部を確認するための語彙があります。

err:code
: QName型
: エラーコード
err:description
: string?型
: エラーの説明。つまり無ければ無い
err:value
: item()*型
: 何が入っているかはエラーによるし、何のエラーか分かっているとき以外使うのは難しい
err:module
: string?型
: エラーを起こしたスタイルシートのモジュールのURIまたはsystem ID。利用できなければemptyが返ってくるとあり、どこまで使えるか分からない
err:line-number, err:column-number
: integer?型
: エラーを起こしたスタイルシートのモジュールの行、列位置。err:module同様、利用できなければempty。さらに値はおおよその位置かもしれない。

前半の3つはfn:error()を作るときに設定できるやつですね。

後半は「利用できなければ~」がどういうときに有りそうかというと、たとえばJavaで実装した関数の独自エラーであったりするとスタイルシートからは取れないのではないか。これは検証できていません。

これを踏まえて、xsl:catchでエラーがあったときに情報を加工して報告するようにすると次。

<xsl:try select="some:function(...)">
  <xsl:catch>
  <!-- tryの内容がエラーを出力したらxsl:catchの内容が実行される -->
  <xsl:message  select="'Error of ' || $err:code || ':' || $err:description" />
  <xsl:message
  select="if (fn:string-length($err:module) > 0) 
          then ($err:module || ',line:' || $err:line-number 
                            || ',col:' || $err:column-number )"/>
  </xsl:catch>
</xsl:try>

エラーのときの回復方法 xsl:try/@rollback-output

扨、Recovery of Result Treesの話です。
別にxsl:tryを挟まなくてもエラー報告はされます。エラーがあったときに報告以外の何かをしたいからxsl:catchをするんですね。
で、エラーが起こったとき、xsl:tryの中で途中まで進めてた処理をどうするのかが@rollback-outputです。

xsl:try/@rollback-outputですが、既定はyesです。失敗したらxsl:tryの中の処理を捨てて(rollbackして)xsl:catchに移行します。

もしシーケンスの作成途中で処理が失敗したら、不正なXMLとなって出力が成立しない可能性もあります。しかし、整形式のXMLの状態で失敗していたら、出力が不正なXMLとはなりません。
まあ賭けとしては分が悪いので、出力がtextのときであればやや許容ぐらいか。

エラーの種類ごとに対処する xsl:catch/@errors

はい。ここでxsl:messageのエラーコードを書き換えたり独自エラーを定義したりする機能追加が活きるんですねえ。

<xsl:try><!--絶対エラーになるxsl:try-->
  <xsl:message select="'絶対エラーにするマン'" terminate="yes" error-code="my:err"/>
  <xsl:catch errors="my:err">
      <error>message error</error>
  </xsl:catch>
  <xsl:catch><!-- 他のエラーハンドリングでキャッチされなかった場合 -->
      <error>other</error>
  </xsl:catch>
</xsl:try>

xsl:messageの場合@terminate="yes"がないとエラーが上がらないのですが、xsl:catchされると処理が続いてしまうので不思議な感じがしますね。

参考資料

https://www.w3.org/TR/xslt-30/#recovery

https://www.w3.org/TR/xslt-30/#dt-standard-error-namespace

https://toshi-xt500.hatenablog.com/entry/7833886

組版・ドキュメンテーション勉強会

Discussion