XSLT 2.0で便利になった機能(20) sequence関係の関数

今回はsequence関係の関数です.XSLT2.0から取り入れられたsequenceの概念ですが、それを操作したり、情報を取得するための様々な関数が用意されています.これらを一通り紹介します.次回は、sequenceの使い方のアイディアを書きたいと思います.
 
■ count()
sequenceをパラメータにとり、そのsequenceの項目数を返します.
例えばcount*1は7を返します.
 
■ empty()、exists()
sequenceをパラメータにとり、empty()は、そのsequenceが空の場合true()を返します.exists()はそのsequenceが空でないときtrue()を返します.
 
例えばempty*2はfalse()を返します.
 
DITA→XSL-FOのスタイルシートを作っていたのですが、topicref/@hrefからtopicをkey関数で求めます.結果をチェックするのにこの2つの関数は毎回のごとくお世話になりました.
 
■ index-of()
この関数は以前データチェックに利用するときに紹介しました.indexof(sequence,value)のように使い、sequence中のvalueの位置を返します.
例えば
<xsl:variable name="dayOfWeek" as="xs:string+" select="('Sun','Mon','Tue','Wed','Thu','Fri','Sat')"/>の時、indexof($dayOfWeek, 'Sat')は7を返します.
 
■ deep-equal()
deep-equal()は2つのsequenceをdeep-compareして結果をboolean()で返します.以前、sequenceの比較演算子"="で次のような比較をしました.
 
<xsl:if test="'Sun'=$dayOfWeek">
 
sequenceの比較演算子"="は、1つでも一致するとtrue()を返してくれました.(おおらかな比較です.)しかし、deep-equal('Sun',$dayOfWeek)は、無慈悲にfalse()を返します.sequenceの一項目づつを比較するからです.sequenceがnode()で構成されている場合も、すべての要素・属性がチェックされます.
 
例えば
    <xsl:variable name="tempTree" as ="element()*">
        <doc>
            <chapter name="Introduction" print="yes"/>
            <chapter name="First XSLT Code" print="yes"/>
        </doc>
    </xsl:variable>
    <xsl:variable name="tempTreeCopy" as="element()*">
        <xsl:copy-of select="$tempTree"/>
    </xsl:variable>
 
としたとき、deep-equal($tempTree,$tempTreeCopy)はtrue()を返します.
 
    <xsl:variable name="tempTreeCopy" as="element()*">
        <xsl:copy-of select="$tempTree"/>
        <redundantElement/>
    </xsl:variable>
 
とすれば、deep-equal($tempTree,$tempTreeCopy)はちゃんとfalse()を返します.
でもdeep-equal()でnode()の比較する人いますかね?比較するだけだったら、2つのXML文書・スタイルシートでも一発でコンペアできるので便利ではありますが...
 
■ unordered()
unordered()はちょっと変な関数で、与えられたsequenceを「テキトー」な順で返す(?)関数です.実際のところは、XPathを処理するのに「わざわざdocument orderにしなくて良くてお任せだからね」と知らせてやるのが役割のようです.例を見てみましょう.
 
    <xsl:variable name="tempTree1" as="document-node()">
        <xsl:document>
            <person>おばあちゃん
                <person>おかあさん
                    <person>わたし
                    </person>
                </person>
            </person>
        </xsl:document>
    </xsl:variable>
 
とやって、
 
    <xsl:variable name="testSeq" as="text()*">
        <xsl:sequence select="$tempTree1/descendant::person[position()=last()]/ancestor-or-self::*/text()"/>
    </xsl:variable>
 
とやると$testSeqはdocumet orderの「おばあちゃん」「おかあさん」「わたし」の順となりますが、
 
    <xsl:variable name="testSeq" as="text()*">
        <xsl:sequence select="unordered($tempTree1/descendant::person[position()=last()]/ancestor-or-self::*/text())"/>
    </xsl:variable>
 
とやると$testSeqは「わたし」「おかあさん」「おばあちゃん」の順になります.処理系おまかせの順になるわけですね.
 
■ distinct-values()
なにやら、関数名もSQLっぽくなってきました.この関数は私が使ってみたくて仕方のない関数のひとつです.
 
distinct-values()は、パラメータのsequence中から重複する項目を削除して結果を返します.例えば、
 
<xsl:variable name="testSeq" as="xs:integer*" select="(1,3,5,7,2,2,3,5)"/>
 
のとき、distinct-values($testSeq)は、(1,3,5,7,2)を返します.
 
でもこんなのは、実践的な例ではありませんね.わたしがどうしてもやりたいのは、今あるマニュアルのオーサリング~組版システムで、
 
distinct-values(//*/@model)
 
というのをやってみたいのです.
 
これはどういうことかというと、マニュアル用のXML文書(DocBook)は完全に1つの製品対応に作られてはいません.同じような内容の場合、いくつかのモデル(model)をまとめて作る場合もあるのです.このような場合、@model属性で「書き分け」を行い、PDFに出力するときに@modelでフィルターをかけます.この@modelの値をコンボボックスで表示して簡単に選択できるようにとお客さんから要望されているのです.フィルター自身はmodelの値をパラメータとしてXSLTスタイルシートで簡単に書けますが、@modelのユニークな値を拾い出してコンボボックスに表示するというのは、このdistinct-value()が使えるようにパーサーがXPath2.0をサポートしていないとできません.まだJDK6では無理だろうな.思い切ってSaxon9を入れてXSLT2.0でやっちゃおうかな?などと思っている次第です.
 
■ insert-before()、remove()、subsequence()
今までの関数が比較的「静的」であったのに対しinsert-before()、remove()はsequenceを操作します.と言っても、XSLTは変数の書き換えはできませんから、結果を別のsequenceで返すだけですが...
 
まず、insert-before()は、insert-before(sequence-1,position,sequence-2)と書いて、sequence-1のpositionの位置の前に、sequence-2を挿入してくれます.
 
例えばinsert-before*3は、('おじいさん','おとうさん','わたし')を返します.
remove()は、remove(sequence,position)と書いて、position位置の項目を削除したsquenceを返します.
 
例えば、remove(('おじいさん','おとうさん','わたし'),1)は、('おとうさん','わたし')を返します.
 
subsequence()は、subsequence(sequence,start,length)またはsubsequence(sequence,start,length)と書いて、sequenceのstart番目からlength個または以降すべての部分のsequenceを返します.
 
例えばsubsequence(('おじいさん','おとうさん','わたし','おかあさん','おばあちゃん'),3)は、('わたし','おかあさん','おばあちゃん')を返します.
 
以上でsequenceも一通りの操作ができることがわかったと思います.
 

*1:1,2,3,14,15,16,17

*2:))はtrue()を返します.
例えばexists((

*3:'おとうさん','わたし'),1,('おじいさん'