position()とコンテキストノードセット

position()を使えばコンテキストノードリストの中のコンテキストノードの位置を得ることができます.position()を使用する際には,「コンテキストノードリスト内の位置を返す」のであり,「テンプレートに合致したノードセット内の位置を返す」のではない点に注意が必要です.例として,以下のXMLとXSLTを考えます.

<!-- sam.xml -->
<?xml version="1.0" encoding="utf-8"?>
<root>
  <item>a</item>
  <item>b</item>
  <item>c</item>
</root>

<!-- sam.xsl -->
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml"/>
  
  <xsl:template match="/root">
    <xsl:apply-templates/>
  </xsl:template>

  <xsl:template match="item">
    <xsl:value-of select="position()"/>
    <xsl:copy-of select="self::node()"/>
  </xsl:template>
</xsl:stylesheet>
  

XTを使ってsam.xmlにsam.xslを適応すると,このような結果になりました.

$ xt sam.xml sam.xsl
<?xml version="1.0" encoding="utf-8"?>

  2<item>a</item>
  4<item>b</item>
  6<item>c</item>
  

1,2,3と番号を振ると思っていましたが,結果は2,4,6となりました.

これは,/rootに記述している<apply-templates/>に原因があります.この<apply-templates/>にはselect属性の指定が何もないので,rootの子要素全てがコンテキストノードセットとなります.一見するとrootの子要素はitemしかないように見えますが,テキストノードとしての改行もコンテキストノードセットに含みます.つまり,

<!-- sam.xsl -->
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml"/>
  
  <xsl:template match="/root">
  <xsl:apply-templates/> <!-- ここでコンテキストノードセットを作る -->
  </xsl:template>

  <xsl:template match="item"> <!-- コンテキストノードセットの中のitem要素に合致 -->
  <xsl:value-of select="position()"/> <!-- コンテキストノードセットの中のコンテキストノードの位置を返す -->
    <xsl:copy-of select="self::node()"/>
  </xsl:template>
</xsl:stylesheet>

<!-- sam.xml -->
<?xml version="1.0" encoding="utf-8"?>
<root> <!-- rootのすぐ後の改行がコンテキストノードセットの1番目 -->
  <item>a</item> <!-- itemが2番目,itemの後ろの改行が3番目 -->
  <item>b</item> <!-- itemが4番目,itemの後ろの改行が5番目 -->
  <item>c</item> <!-- itemが6番目,itemの後ろの改行が7番目 -->
</root>
  

となります.<apply-templates/>で作成したコンテキストノードリストに<template match="item"/>を適応するので,各itemのposition()は2,4,6となるわけです.position()<apply-templates/>で作成したコンテキストノードセット内の位置を取得するもので,<template match="item"/>で抽出したノードセット内の位置を返すのではない点に注意してください.

改行以外のitem要素だけでコンテキストノードセットを作る場合は,<apply-templates/>select属性を指定します.

<!-- sam.xsl -->
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml"/>
  
  <xsl:template match="/root">
  <xsl:apply-templates select="item"/> <!-- item要素だけのコンテキストノードセットを作る -->
  </xsl:template>

  <xsl:template match="item">
    <xsl:value-of select="position()"/>
    <xsl:copy-of select="self::node()"/>
  </xsl:template>
</xsl:stylesheet>

<!-- sam.xml -->
<?xml version="1.0" encoding="utf-8"?>
<root>
  <item>a</item>
  <item>b</item>
  <item>c</item>
</root>

$ xt sam.xml sam.xsl
<?xml version="1.0" encoding="utf-8"?>
1<item>a</item>2<item>b</item>3<item>c</item>
  

ちなみに,sam.xmlに改行を入れず,以下のように変えると,select属性を指定しない<apply-templates/>を使っても1,2,3と番号を振ります.

<!-- sam.xml -->
<?xml version="1.0" encoding="utf-8"?>
<root><item>a</item><item>b</item><item>c</item></root>

<!-- sam.xsl -->
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml"/>
  
  <xsl:template match="/root">
  <xsl:apply-templates/> <!-- rootの子要素でコンテキストノードセットを作る.改行を入れていないので,itemだけ -->
  </xsl:template>

  <xsl:template match="item">
    <xsl:value-of select="position()"/>
    <xsl:copy-of select="self::node()"/>
  </xsl:template>
</xsl:stylesheet>

$ xt sam.xml sam.xsl
<?xml version="1.0" encoding="utf-8"?>
1<item>a</item>2<item>b</item>3<item>c</item>