丁度良い事例なので鳩丸更新履歴から引用しますが:
ちなみに 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要素の位置」ということになります。