XSLT 2.0で便利になった機能(29) xsl:numberを使う

xsl:numberについて紹介したいと思います.xsl:numberはXSLT2.0になっても大きな変更はありませんでした.select属性が加わったことだけの違いです.しかし、この違いは大きいです.従来は、カレントノードからしかカウントができませんでした.select属性を指定することにより、任意のノードからカウントができるようになりました.またこれは、カレントノードが存在しないxsl:functionの中でもxsl:numberが使えるようになったことを意味します.
 
例を考えてみましょう.例えば、テーブルのタイトル行に「表N-M 変換コード表」などと出力したいとします.ここでNは章の番号、Mは章内の連番です.このような時、部、章内のtable要素をカウントしなければなりません.
DocBookの場合でしたら文書はsetやbookをトップにした完全なツリー型なので、それほど問題はないと思われます.
 
 
<xsl:template match="db:table">
  <!-- chapterを数える-->
  <xsl:variable name="chapterCount" select="string(count(ancestor::db:chapter/preceding-sibling::db:chapter) + 1)" as="xs:string"/>
 
  <!-- 自分のchapter内の位置を求める-->
  <xsl:number select="."
            level="any"
            count="db:table"
            from="db:chapter"/>
 
</xsl:template>
 
しかし、DITAの場合中間ファイルが次のようなヤクザな形式をしているために、到底DocBookのようにはゆきません.DITAの文書はtopicと呼ばれる小規模なXML文書の集合で構成されます.これをDITA Open Toolkitで処理すると次のような中間ファイルが出来上がります.文書の構成は前半のmap部分で定義され、個々の内容は後半のtopic部分に格納されます.
 
<!-- DITAの中間ファイル:模式的な図-->
<dita-merge>
  <bookmap>
    ...
    <!-- map部:partもchapterもすべてtopicrefで表される-->
    <topicref href="#unique_1" ..../>
    <topicref href="#unique_1" ..../>
    ...
  </bookmap>
  <topic id="unique_1">
    <!-- 実際のtopicの内容-->
  </topic>
  <topic id="unique_2">
    ...
  </topic>
</dita-merge>
 
DITAで章内のテーブルのカウントを求めるには以下のようにします.
 
テーブルの章内のカウント= そのtopicまでのカウント + そのtopic内のカウント
 
そのtopicまでのカウントは、あらかじめカウントしてテンポラリツリーに結果を格納しておきます.以下は関数でxsl:numberを使う例として御覧ください.
 
 
<!-- dita-merge要素 -->
<xsl:variable name="top" select="/*[1]" as="element()"/>
 
<xsl:function name="tmf:getTableCount" as="xs:string">
    <xsl:param name="prmTopicRef" as="element()"/>
    <xsl:param name="prmTable" as="element()"/>
    <xsl:variable name="tablePreviousAmount" as="xs:integer">
        <!--ここはあらかじめカウントしてある結果から、tableが含まれる
            topicまでの集計値を得る.
         -->
    </xsl:variable>
    <xsl:variable name="tableCurrentAmount" as="xs:integer">
        <xsl:number select="$prmTable"
                    level="any"
                    count="*[contains(@class,' topic/table ')]"
                    from="*[contains(@class, ' topic/topic ')][parent::* is $top]"/>
    </xsl:variable>
    <xsl:variable name="tableNumber" select="$tablePreviousAmount + $tableCurrentAmount" as="xs:integer"/>
    <xsl:sequence select="string($tableNumber)"/>
</xsl:function>
 
xsl:numberの使用例を見ていると、xsl:numberはtext()を返すので、直接結果ツリーに反映させるようなものが多いです.しかしXSLT2.0では、text()は容易にxs:stringやxs:integrにキャストできますので、もっとxsl:numberは活用できるのではないかと思います.