実装の不具合かと思ったのですが、preceding-sibling軸でカレントノードリストを変更すると、各カレントノードのコンテクストポジションは文書順になります。次のHTML文書の断片をご覧下さい:
<p>テキストノード[1]<em>em要素[2]</em>テキストノード[3]<br />テキストノード</p>
このp要素にマッチするテンプレート内で、子のbr要素より前に登場する「弟ノード達」をカレントノードリストにしてみます。尚、簡略化の為このHTMLの各ノードは名前空間を持たないことにしておきます。
<xsl:for-each select="child::br[position() = 1]/preceding-sibling::node()">
<!-- テンプレート -->
[<xsl:value-of select="string(position())" />]
</xsl:for-each>
「<!-- テンプレート -->」にはノードをそのままコピーするものを適用するとして、これは次のような結果を構築します。
ソース文書と同じ順番にインスタンス化されています。私はこれに驚きました。カレントノードリストは、XPathのロケーションパスで指定されたノードリストを文書順に並び替えたものになるということですから。
というロケーションパスで指定されたノードリスト内の各ノードは次の順番で並んでいます。正確には、この順番に応じたコンテクストポジションを持っています:
ところが、XSLTプロセッサはこれを文書順に並べ替えたものをカレントノードリストにしてしまいます。
「してしまいます」というか、まあ有り難い場面は多いのですが、XPathユーザ的に若干不自然な挙動ではあります。
因みにカレントノードリストを、「コンテクストポジションが1である弟ノード」にしてみますと:
<xsl:for-each select="child::br[position() = 1]/preceding-sibling::node()[position() = 1]">
<!-- ノードコピー用テンプレート -->
[<xsl:value-of select="string(position())" />]
</xsl:for-each>
結果は次のようになります。
先程の例と比べれば、並べ替えが行われていることは明らかです。
カレントノードリスト内のノードの順番を、ロケーションパスで指定されたノードリストと同じにするにはどうすれば良いでしょうか。
軸がpreceding軸、preceding-sibling軸、ancestor軸、ancestor-or-self軸の場合、コンテクストポジションを見て、降順(数値の大きい順)にソートしてやれば解決しました。というか文書順になるだろうと思って最初にこれを試したら逆だったので驚いたわけですが。
<xsl:for-each select="child::br[position() = 1]/preceding-sibling::node()">
<xsl:sort order="descending" data-type="number" select="position()" />
<!-- テンプレート -->
[<xsl:value-of select="string(position())" />]
</xsl:for-each>
結果は次のようになります:
ロケーションステップが一回であり、軸がchild軸、descendant軸、descendant-or-self軸、following軸、following-sibling軸の場合は特に気遣う必要はありません。ロケーションステップが2回以上行われ、これらの軸以外で構成されるものが出てくる際には場合によっては分割する必要があります。for-each要素を入れ子にします。
以上、br要素からl要素(line要素)を生成 (agenda)における、br要素で分割されたノードリストを、それぞれXHTML 2.0のl要素(line要素)としてグループ化するという試みの最中に出くわしたケースについて書いてみました。要点を纏めると次のようになります: