Jintrick.netagenda2002年06月アーカイブ → 2002年06月30日

カレントノードについて(XSLT)

丁度良い事例なので鳩丸更新履歴から引用しますが:

ちなみに foo要素と foo要素の間にある CRLF も一つのノードとして扱われていて、position() はそれもカウントした値を返している模様。

「HTML鳩丸倶楽部」更新履歴(2001-10-04) より

position()関数が改行を数えてしまう原因は、「カレントノードリスト」に改行が含まれてしまっているからです。

<xsl:template match="foo">
 <xsl:if test="position() mod 4 = 2">
  ……
 </xsl:if>
 ……
</xsl:template>

このテンプレートが処理されている時、カレントノード(処理中のノード)は一つのfoo要素です。ではカレントノードリストはなんでしょうか。

実は、この例を見ただけでは分かりません。一般に、この「テンプレート」はxsl:apply-templates要素経由で処理が開始されます、その場合、そのxsl:apply-templates要素のselect属性が、カレントノードリストを決定します。例えば:

<xsl:template match="bar">
    <xsl:apply-templates />
</xsl:template>

上のxsl:apply-templates要素のselect属性は指定されていないのでデフォルト値「child::node()」です。カレントノードはbar要素ですから、bar要素の子ノード全てを意味します。従ってカレントノードリストは、「bar要素の子ノード全て」となります。

よく見かける解説では、xsl:template要素のmatch属性に指定されたノード集合が、カレントノードリストになる、と説明されていますが、match属性値は単なるパターンであり、処理すべきノード集合を指定するものではありませんから、これは間違いです。(某書籍はXSLTの勘違いを撒き散らしているような気がします。)

プロセッサの処理順序を考えてみると分かります。 select="child::node()"という属性をもつxsl:apply-templates要素の処理に入った時点で、child::node()で指定された「リスト」を元に、そのリストに含まれるそれぞれのノードについて、マッチするテンプレートを見つけだし、そのテンプレートの処理に入ります。xsl:template要素のmatch属性は、マッチするか否かをプロセッサに判断させる基準に過ぎません。次の例を考えると、さらに明確になると思います。

XMLは以下の例を使います(断片)。

<document>
    <foo></foo>
    <bar></bar>
    <foo></foo>
</document>

XSLTは以下の例を用います(断片)。

<xsl:template match="document">
    <xsl:apply-templates select="child::*" />
    <!-- select属性はデフォルト値 -->
</xsl:template>
<xsl:template match="foo">
   some template
</xsl:template>

プロセッサが、document要素の処理に入っている段階から考えます。xsl:apply-templates要素が現われたので、select属性に指定されている通り、document要素の子要素ノード達(child::*)について、マッチするテンプレートを探し始めます。最初は、第一番目のfoo要素です。さてこのとき、カレントノードリストがmatch属性に指定されている「foo」になっているとしたら、次に処理するのは第三番目のfoo要素ということになってしまいます。なぜならカレントノードリストとは、現在処理中のノード(カレントノード)のリストを意味するからです。ところが実際には、第一番目のfoo要素の処理を終えたプロセッサは、第二番目のbaz要素の処理にかかります。これは多くの解説書等にもきちんと説明されているとおりです。match属性に指定された「foo」に、baz要素が含まれる余地はありませんから、これは矛盾していることになります。

従ってこの例からも、xsl:template のmatch属性がカレントノードリストを変更しないことが分かります。その辺の参考書に騙されないないよう、ご注意ください。

ちなみに現時点で私が知る限り、カレントノードリストを変更することができるのは、xsl:apply-templates要素のselect属性、それから、xsl:for-each要素のselect属性の二つです。

さて、position()関数は(XSLTにおいては)、カレントノードリスト内の、カレントノードの位置を返す関数です。従って改行を数えて欲しくない場合、カレントノードリストに、改行を含めなければ良いだけです。

鳩丸更新履歴で取り上げられていた例を(ちょっと変更しつつ)紹介します。

<?xml version="1.0" encoding="Shift_JIS"?>
<document>
<foo>1</foo>
<foo>2</foo>
<foo>3</foo>
<foo>4</foo>
<foo>5</foo>
</document>

このようなXMLを、次ようにグループ化するにはどうすれば良いか、という問題です。

<?xml version="1.0" encoding="Shift_JIS"?>
<document>
<bar>
<foo>1</foo>
<foo>2</foo>
</bar>
<bar>
<foo>3</foo>
<foo>4</foo>
</bar>
<bar>
<foo>5</foo>
</bar>
</document>

これは、次のようなXSLTで可能です。(一例に過ぎません)

<?xml version="1.0" encoding="Shift_JIS"?>
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output indent="yes" />

<xsl:template match="/">
<document>
    <xsl:apply-templates />
</document>
</xsl:template>

<xsl:template match="document">
  <xsl:for-each select="foo">
    <xsl:if test="(position() mod 2) != 0">
      <bar>
        <foo><xsl:value-of select="self::node()" /></foo>
           <xsl:if test="following-sibling::foo[1]">
           <foo><xsl:value-of select="following-sibling::foo[1]" /></foo>
        </xsl:if>
      </bar>
    </xsl:if>
  </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

ここに登場するposition()関数が扱っている、カレントノード及びカレントノードリストを考えてみます。

処理開始時点では、カレントノードリスト及びカレントノードは共にルートノードです。そしてカレントノードであるルートノードにマッチするテンプレートを探し始めます(デフォルトの挙動です)。これは7行目〜11行目の<xsl:template match="/"></xsl:templates>に最優先でマッチします。

次に、このテンプレートの中に再びxsl:apply-templates要素が登場します。この要素にはselect属性が指定されていないようですが、デフォルトで、select="child::node()"が指定されているものと見なされます(重要)。従ってこの時点で、カレントノードリストはルートノードの子ノード全てということになります。ここは勘違いポイントなのですが、ルートノードはdocument要素ではありません。document要素のさらに上に、親としてルートノードが存在しているものと見なされます。というわけでこの時点で、カレントノードリスト及びカレントノードはdocument要素(XPathでは、/child::document)ただ一つとういことになり、マッチするテンプレートを探し始めます。

さてそのdocument要素にマッチするテンプレート<xsl:template match="document"></xsl:template>内に、select属性を持ったxsl:for-each要素が現われます。これはカレントノードリストを変更します。select="foo"と指定されていますが、select="child::foo"の省略形です。従ってカレントノードリストは、XPathの絶対パスでいうところの、/child::document/child::fooに変更されました。これらのfoo要素達を順次処理してゆきますが、その時処理しているfoo要素がカレントノードです。

position()関数が登場するノードはxsl:if要素のtest属性内ですが、これは仕様書にも明示されているように、カレントノードを一切変更しません。従って、position()関数が返すのは、「/child::document/child::fooというノードリストの中で、現在処理しているfoo要素の位置」ということになります。


webmaster@jintrick.net
公開: 2002年06月30日
カテゴリ: XSLT