[XPath 2.0][XSLT 3.0]コンテキストアイテム(ノード)構造上の位置を表示しよう fn:trace()とその限界
XSLT 3.0デバッグまわり Advent Calendar 2025 9日目わよ。
2026-03-01 fn:path()追記
fn:trace()
「表示結果や処理結果がおかしいけれど、入力のどの部分を処理したのかいまいち分からない」 こういう事態は割と起こります。 基本的にはtransoformは元の構造を潰す方向で行うためであるのと、 基本的にmatchテンプレートでlocation step指定に階層位置を省きがちなためです。 ex. html:p→fo:block, html:h2 -> fo:blockみたいな感じで。
ex match="p" であってmatch="/body/div/p | body/p"などのように一々構造位置を指定しない。
fn:trace()は、現在のコンテキストノードについて、構造をトレースして表示してくれます。……ログ出力の方に。ちなみに返り値は入力をそのまま返すぞ!
入力がこれだとする。
<doc>
<title>aa</title>
<section>
<title>bb</title>
<section>
<title>ccc</title>
</section>
<section>
<title>ddd</title>
</section>
</section>
</doc>
で、コメントにtrace()を使うとする。
<xsl:template match="title">
<xsl:comment select="fn:trace(.)"/>
<xsl:copy><xsl:apply-templates/></xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:comment select="fn:trace(.)"/>
...
</xsl:template>
結果はたとえば次のようになる。
<!-aa-->
<title>aa</title>
match="child::title"のコンテキストノードはtext()「aa」となるという。一方ログ側は
* [1]: element(doc, xs:untyped): /doc
* [1]: element(title, xs:untyped): /doc/title[1]
* [1]: element(section, xs:untyped): /doc/section[1]
* [1]: element(title, xs:untyped): /doc/section[1]/title[1]
* [1]: element(section, xs:untyped): /doc/section[1]/section[1]
* [1]: element(title, xs:untyped): /doc/section[1]/section[1]/title[1]
* [1]: element(section, xs:untyped): /doc/section[1]/section[2]
* [1]: element(title, xs:untyped): /doc/section[1]/section[2]/title[1]
わあい。
ちなみに、左側の部分はカスタムしたラベルにできます。
<xsl:comment select="fn:trace(., 'Trace on ')"/>
Trace on [1]: element(doc, xs:untyped): /doc
Trace on [1]: element(title, xs:untyped): /doc/title[1]
Trace on [1]: element(section, xs:untyped): /doc/section[1]
Trace on [1]: element(title, xs:untyped): /doc/section[1]/title[1]
Trace on [1]: element(section, xs:untyped): /doc/section[1]/section[1]
Trace on [1]: element(title, xs:untyped): /doc/section[1]/section[1]/title[1]
Trace on [1]: element(section, xs:untyped): /doc/section[1]/section[2]
Trace on [1]: element(title, xs:untyped): /doc/section[1]/section[2]/title[1]
出力に構造位置を出したい
ポジションは出ないけど、簡易的にはこういうので出せます。
<xsl:comment select="fn:string-join(ancestor-or-self::*/fn:name(), '/') "/>
出力に構造位置を出したい fn:path()
2026-03-01 追記。
XPath 3.1では函数ありました。fn:path()というズバリなものが。
名前空間について俗にいうJames Clark記法(Q{http://www.w3.org/1999/XSL/Format}のようにQ{}で囲う記法)となるため、不要な場合はfn:replace()などで削除しましょう。
<xsl:comment select="fn:path(.) => fn:replace('Q\{.*\}','')"/>
応用例
XMLをXSLTでFOにしてAntenna House FormatterでPDFを出したい、というケースで、PDF注釈にこのパス結果を格納して、見た目の変更を抑えたデバッグ情報を付加してみましょう。
<xsl:template match="p">
<fo:block>
<xsl:if test="$is.debug">
<xsl:attribute name="axf:annotation-type" select="'Text'"/>
<xsl:attribute name="axf:annotation-contents" select="fn:path()">
</xsl:if>
<xsl:apply-templates mode="#current"/>
</fo:block>
</xsl:template>
参考資料
Discussion