XSLT 2.0で便利になった機能(35) コンテキストアイテム

コンテキストアイテムについての話です.どちらかといえば便利になったというよりは、拡張と制約の話ですが.
 
コンテキストアイテムは"."で表されます.XSLT1.0を使っておられる方でしたら、"."は何かと問われれば間違いなくカレントノードと答えるでしょう.そのとおりで、例えば
 
<xsl:template match="year">
  <xsl:if test=".='2010'">
    ...
  </xsl:if>
<xsl:template>
 
とあれば、間違いなく"."はマッチしたカレントノードのyearです.
 
あとXSLT1.0ではコンテキストノードという呼び方があって、XPath式のpredicate("["と"]"で囲まれたノードの絞込み条件)で使われると、それはその条件が適用されるノードを指し示します.例えば
 
<xsl:if test="book[./publisher='翔泳社']">
 
という場合、コンテキストノードは子要素のpublisherのテキストが"翔泳社"であることがチェックされるbook要素です.
 
XPath 2.0ではもはやカレントノードという言い方もコンテキストノードという言い方も使われなくなりました.代わりにコンテキストアイテムという言い方に変わっています.これは、上記のカレントノードやカレントコンテキストに加えて、"."がノードだけでなく、例えばxs:integerやxs:stringにもなりえることを示します.本当にそんなことはあるのでしょうか?
 
あります.例えば次のようなコードは、
 
<xsl:variable name="month" select="('January','February','March','April','May','June','July','August','September','October','November','December')" as="xs:string+"/>
<xsl:for-each select="$month">
    <xsl:message select="concat(position(),'月=','''',.,'''')"/>
</xsl:for-each>
 
1月='January'
2月='February'
3月='March'
4月='April'
5月='May'
6月='June'
7月='July'
8月='August'
9月='September'
10月='October'
11月='November'
12月='December'
 
という出力をしてくれますが、"concat(position(),'月=','''',.,'''')"の中の"."はxs:stringです.決してノードではありません.ですからコンテキストアイテムという言い方がふさわしいのです.
 
このように、XPath 2.0では、"."の意味も拡張されました.
 
あとXSLT1.0では、"."は必ず「存在」しました.しかしXSLT 2.0では、"."が未定義の場合も存在します.例えば、xsl:functionの中では、"."は使えません.たいてい一度は引っかかるエラーですが、例えば以下のようなコードを書いたとします.(ちょっと冗長ですが...)
 
<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:tmf="http://www.tmakita.com/Function"
    exclude-result-prefixes="xs" >
...
    <xsl:apply-templates select="chapter[tmf:isToc()]"/>
...
    <xsl:function name="tmf:isToc" as="xs:boolean">
        <xsl:choose>
            <xsl:when test="not(./@toc)">
                <xsl:sequence select="false()"/>
            </xsl:when>
            <xsl:when test="./@toc='no'">
                <xsl:sequence select="false()"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:sequence select="true()"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:function>
 
このコードでは、xsl:functionの中で、カレントコンテキストを「期待」しています.でもその期待は見事に砕かれて、まず <xsl:when test="not(./@toc)"> の行が
 
 XPDY0002: The context item is undefined at this point
 
のエラーとなります.
正解は次のとおりです.
 
    <xsl:apply-templates select="chapter[tmf:isToc(.)]"/>
 
    <xsl:function name="tmf:isToc" as="xs:boolean">
        <xsl:param name="currentContext" as="node()"/>
        <xsl:choose>
            <xsl:when test="$currentContext/@toc">
                <xsl:sequence select="false()"/>
            </xsl:when>
            <xsl:when test="$currentContext/@toc='no'">
                <xsl:sequence select="false()"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:sequence select="true()"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:function>
 
xsl:functionの中では、コンテキストアイテムは未定義となることは覚えておくべきでしょう.