🤖

[XPath 2.0][XSLT 3.0]コンテキストアイテム(ノード)構造上の位置を表示しよう fn:trace()とその限界

に公開
2026/03/01

https://adventar.org/calendars/11635
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>

参考資料

https://www.w3.org/2006/xpath-functions/

https://www.w3.org/TR/xpath-functions-31/#diagnostics

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

Discussion