XSLT2.0で便利になった機能(6) シーケンス

XSLT2.0から取り入れられたsequenceの概念、xsl:templateもxsl:variableもXSLT2.0では、そのコンテンツは、sequence-constructorとして定義されます.
 
<xsl:variable name="..." as="...">
    <!-- sequence-constructor -->
</xsl:variable>
 
<xsl:template name="..." as="...">
    <!-- sequence-constructor -->
</xsl:template>
 
sequenceを記述するときは"(",")"を使う.例えばxs:integer*として(1,2,3)と書ける.
 
いや、そうは言っても、xsl:templateなんてほとんど今までどおり使えます.xsl:variableだって、いちいちxsl:sequenceを使わなくたってかける場合があります.sequenceの恩恵ってなんなのか?そう思ったときがありました.
 
的確な答えを持っているわけではありませんが、sequenceは非常に便利に使えるときがあります.
 
例えばスタイルシートからのメッセージの出力.みなさんはどのようにコーディングされておられるでしょうか?なんらかの理由で、エラーメッセージを出して終了するとき、私はテンプレートの該当箇所にそのまま<xsl:message>を書くのが普通でした.例えば以下のようなコードです.しかしこれでは、仕様書にメッセージ一覧を書くとき大変です.メッセージ番号の管理もメッセージ本体の記述を集中させないと困ります.
 
<xsl:message>
    <xsl:text>[Document Check 100W] The part level element contains attribute toc='no'. Reference file='</xsl:text>
    <xsl:value-of select="@ohref"/>
    <xsl:text>' navtitle='</xsl:text>
    <xsl:value-of select="@navtitle"/>
    <xsl:text>' file='</xsl:text>
    <xsl:value-of select="@xtrf"/>
    <xsl:text>'.</xsl:text>
</xsl:message>
 
私はこんなふうにsequenceをつかってやってみました.メッセージは次のように集中して1つのファイルにまとめて書きます.
 
<xsl:variable name="mes100" as="xs:string">
    <xsl:text>[Document Check 100W] The part level element contains attribute toc='no'. Reference file='%ohref' navtitle='%navtitle' file="%xtrf'.</xsl:text>
</xsl:variable>
 
すこし手間ですが、次のようなtemplateとfunctionを準備します.
 
<!-- メッセージを出力して処理継続 -->
<xsl:template name="warningContinue">
    <xsl:param name="prmMes" required="yes" as="xs:string"/>
    <xsl:message terminate="no"><xsl:value-of select="$prmMes"/></xsl:message>
</xsl:template>
 
<!-- backslashをslashに変換 -->
<xsl:function name="tmf:bsToSlash" as="xs:string">
    <xsl:param name="prmStr" as="xs:string"/>
    <xsl:sequence select="translate($prmStr,'&#x005C;','/')"/>
</xsl:function>
 
<!-- 正規表現エスケープ文字back-slashを避けて文字列を置換 -->
<xsl:function name="tmf:safeReplace" as="xs:string">
    <xsl:param name="prmStr" as="xs:string"/>
    <xsl:param name="prmSrc" as="xs:string"/>
    <xsl:param name="prmDst" as="xs:string"/>
    <xsl:sequence select="replace($prmStr,$prmSrc,tmf:bsToSlash($prmDst))"/>
</xsl:function>
 
<!-- sequenceを使って文字列を置換 -->
<xsl:function name="tmf:replace" as="xs:string">
    <xsl:param name="prmStr" as="xs:string"/>
    <xsl:param name="prmSrc" as="xs:string+"/>
    <xsl:param name="prmDst" as="xs:string+"/>
    <xsl:variable name="firstResult" select="tmf:safeReplace($prmStr,$prmSrc[1],$prmDst[1])" as="xs:string"/>
    <xsl:choose>
        <xsl:when test="exists($prmSrc[2]) and exists($prmDst[2])">
            <xsl:sequence select="tmf:replace($firstResult,subsequence($prmSrc,2),subsequence($prmDst,2))"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:sequence select="$firstResult"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:function>

ここまで準備すると先ほどの<xsl:message>は次のように書くことができます.
 
<xsl:call-template name="warningContinue">
    <xsl:with-param name="prmMes"
          select="tmf:replace($mes100,
                                        ('%ohref','%navtitle','%xtrf'),
                                        (@ohref,@navtitle,@xtrf))"/>
</xsl:call-template>
 
結構sequenceを使ってスマートにかけると思いませんか?
私は今ではこの記法に統一してコーディングするようにしています.なおtmf:replaceの第3パラメータは、xs:string+ですが、XSLTプロセッサがattribute()+をxs:string+にキャストしてくれます.
 
また、sequenceは可変長の1次元の表のようにも使えます.例えば、月の番号から英語の月の綴りを求めるテンプレート.XSLT1.0なら.次のように書いたでしょう.
 
<xsl:template name="numberToMonth">
    <xsl:param name="prmMonth" select="0"/>
    <xsl:choose>
        <xsl:when test="$prmMonth=1">
            <xsl:value-of select="'Jan'"/>
        </xsl:when>
    ...
        <xsl:when test="$prmMonth=12">
            <xsl:value-of select="'Dec'"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="'???'"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>
 
でもXSLT2.0ならもっと簡単にいけますよ.次を御覧ください.
 
<xsl:variable name="monthEn" as="xs:string+" select="('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec')"/>
<xsl:function name="tmf:numberToMonth" as="xs:string">
    <xsl:param name="prmMonth" as="xs:integer"/>
    <xsl:choose>
        <xsl:when test="($prmMonth ge 1) and ($prmMonth le count($monthEn))">
            <xsl:sequence select="$monthEn[$prmMonth]"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:sequence select="'???'"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:function>
 
いかがでしょうか?私の使っているのはこれくらいですが、もっといろいろと使えると思います.応用を考えると楽しくなりますね.