XSLT 2.0で便利になった機能(36) ">>"演算子

まず使うことはないと思っていた">>"演算子ですがたまたま使う機会がありましたので紹介します.
 
少し話がややこしいのですが、DITAを使っているお客さんがいます.<ol>の出力要求仕様があって、<ol>の入れ子レベルによって、1. 2. 3. と a. b. c. と i. ii. iii.というように番号付きリストのラベルを分けてつけるというものです.それまでの入れ子のレベルの計算は、
 
<xsl:template match="*[contains(@class,' topic/ol ')]/*[contains(@class,' topic/li ')]">
 
のテンプレートで、
 
<xsl:variable name="nestLevel" select="count(ancestor::*[contains(@class,' topic/ol ')])" as="xs:integer">
 
というごく簡単で幸せなものでした.入れ子レベルが計算できれば、それを3で割って剰余を求め、その値に従って、1. 2. 3. 、a. b. c.、i. ii. iii.を割り当てます.
 
ところが、これではおかしいというメールが届きました.添付されていたデータを見ると、<note>要素の内と外に<ol>が配置されています.<note>要素は警告や注意書きを記述する要素で、その内外では入れ子レベルの計算を別にしなければいけないということなのです.あとよくよく考えてみたら<note>だけでなくテーブルのセル<entry>も同じことでした.
 
そこでこのnestLevel変数の計算を考え直しました.やれば良いことは、カレントコンテキスト(ol/li)からancestor軸で直近のentryかnoteまでの<ol>の数をカウントすることです.これは以下のようにしました.
 
<xsl:variable name="nestLevel" as="xs:integer">
    <xsl:variable name="ancestorNote" select="ancestor::*[contains(@class,' topic/note ')][1]" as="element()?"/>
    <xsl:variable name="ancestorCell" select="ancestor::*[contains(@class,' topic/entry ')][1]" as="element()?"/>
    <xsl:choose>
        <xsl:when test="empty($ancestorNote) and empty($ancestorCell)">
            <xsl:sequence select="count(ancestor::*[contains(@class,' topic/ol ')])"/>
        </xsl:when>
        <xsl:when test="exists($ancestorNote) and empty($ancestorCell)">
            <xsl:sequence select="[ol/liから$ancestorNoteまでのolの数]"/>
        </xsl:when>
        <xsl:when test="empty($ancestorNote) and exists($ancestorCell)">
            <xsl:sequence select="[ol/liから$ancestorCellまでのolの数]"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:choose>
                <xsl:when test="$ancestorNote &gt;&gt; $ancestorCell">
                    <xsl:sequence select="[ol/liから$ancestorNoteまでのolの数]"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:sequence select="[ol/liから$ancestorCellまでのolの数]"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:otherwise>
    </xsl:choose>
</xsl:variable>
 
ancestor軸に<note>か<entry>のいずれかがある場合は問題ないのですが、両方がある場合には、<note>か<entry>か「近い」方までの<ol>の数をカウントしなければなりません.この「近い」を判断するのに">>"演算子がまさにはまり役でした.
 
test="$ancestorNote &gt;&gt; $ancestorCell"
 
で、<note>の方が<entry>よりドキュメントオーダーで後に位置することが判定できます.つまり「近い」のです.
 
あと、[ol/liから$ancestorNoteまでのolの数][ol/liから$ancestorCellまでのolの数]と書いた箇所、一発のXPathでできるでしょうか?私はDITA-OT付属のSaxon 9.1.0.5Jではどうしても出来ませんでした.xsl:functionでカウントしています.もし出来るXpathがありましたらどなたか教えてください.
 
[2011/01/11 追加]
失礼しました.このコードでは拡張性がありません.note, entryの他に独自のナンバリングを行わねばならない要素が追加されると、xsl:whenがパンクしてしまいます.案の定お客さんからfn(footnote)の中でも<ol>を使うという連絡です.以下のように"|"オペレータを使って書き直してみました.$nearestElemのコードを御覧ください."($ancestorNote|$ancestorCell|$ancestorFn)[position()=last()]"で直近の要素を求めることができます.またカレントから$nearestElem直前までのolも"<<"演算子を使って求められます.
 
<xsl:variable name="nestLevel" as="xs:integer">
    <xsl:variable name="ancestorNote" select="ancestor::*[contains(@class,' topic/note ')][1]" as="element()?"/>
    <xsl:variable name="ancestorCell" select="ancestor::*[contains(@class,' topic/entry ')][1]" as="element()?"/>
    <xsl:variable name="ancestorFn" select="ancestor::*[contains(@class,' topic/fn ')][1]" as="element()?"/>
    <xsl:variable name="nearestElem"  select="($ancestorNote|$ancestorCell|$ancestorFn)[position()=last()]" as="element()?"/>
    <xsl:choose>
        <xsl:when test="empty($nearestElem)">
            <xsl:sequence select="count(ancestor::*[contains(@class,' topic/ol ')])"/>
        </xsl:when>
        <xsl:otherwise>
            <!-- カレントから$nearestElem直前までのolを数える -->
            <xsl:sequence select="count(ancestor::*[contains(@class,' topic/ol ')][$nearestElem &gt;&gt; .])"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:variable>