XSLT2.0で便利になった機能(52) xsl:template(続き)

テンプレートから複数の種類のitem()から成るシーケンスを返す例を紹介します.例えば次のような要望があったと考えます.

① section/divという要素構造がある.sectionはdivから構成され、divがテキストを持つ.
② divの文字数が一定数以上のものを抽出したい.
③ divの文字数の範囲により、異なる処理をしたい.

②~③はもちろん一つのテンプレートに押し込めることもできますが、ここでは機能上2つのテンプレートとして分離することにします.そうすると、②で抽出のためにdivの文字数を数えますが、③でも文字数の情報が必要になります.③で文字数を数えなおしても良いのですが冗長なので、②から③へは

1.対象のdiv
2.divの文字数

をパラメータで渡すものとします.では②のテンプレートがどうかかけるかというと次のようになります.

<xsl:template name="extractDiv" as="item()*">
  <xsl:param name="prmSection" as="element()" required="yes"/>
  <xsl:param name="prmMinCharCount" as="xs:integer" required="yes"/>
  <xsl:for-each select="$prmSection/div">
    <xsl:variable name="div" select="."/>
    <xsl:variable name="divCharCount" as="xs:integer">
        <!--$divの文字数を数えます.
            divに下位要素があったり、漢字と英数字のカウントを区別したりすると
            実際にはもっと複雑です.-->
        <xsl:sequence select="string-length(string($div))"/>
    </xsl:variable>
    <xsl:if test="$divCharCount ge $prmMinCharCount">
        <!--最低文字数以上のdivとその文字数を返します-->
        <xsl:sequence select="$div"/>
        <xsl:sequence select="$divCharCount"/>
    </xsl:if>
  </xsl:for-each>
</xsl:template>

このテンプレートでは要素のdivとその文字数である整数値を返しています.でもこのままでは戻り値としては扱いづらいでしょう.つぎのようにすれば、要素と整数値を分離できます.

<!--②のテンプレートの呼び出し-->
<xsl:variable name="divAndCharCount" as="item()*">
    <xsl:call-template name="extractDiv">
        <xsl:with-param name="prmSection" select="[section要素]"/>
        <xsl:with-param name="prmMinCharCount" select="[最小文字数]"/>
    </xsl:call-template>
</xsl:variable>

<!--結果をdivと文字数に分離する-->
<xsl:variable name="targetDiv" select="$divAndCharCount[. instance of element()]"/>
<xsl:variable name="divCharCount" select="$divAndCharCount[. instance of xs:integer]"/>

<!--分離した結果を表示してみる-->
<xsl:for-each select="$targetDiv">
    <xsl:message select="'$targetDiv[',position(),']=',."/>
</xsl:for-each>
<xsl:for-each select="$divCharCount">
    <xsl:message select="'$divCharCount[',position(),']=',."/>
</xsl:for-each>

<!--③を呼び出す-->
<xsl:call-template name="processDiv">
  <xsl:with-param name="prmTargetDiv" select="$targetDiv"/>
  <xsl:with-param name="prmDivCharCount" select="$divCharCount"/>
</xsl:call-template>

"instance of"を使えば要素と整数を簡単に分離できます.本当だったらXPathにもScalaのタプルみたいなデータ構造があって、要素と整数をペアで扱えるともっと良いのですが、それはないものねだりというものでしょう.

実際に表示まで動かしてみた例を示します.

<xsl:variable name="section" as="document-node()">
    <xsl:document>
        <section>
            <div>AES</div>
            <div>APIPA</div>
            <div>Authentication</div>
            <div>BOOTP</div>
            <div>DNS Server</div>
        </section>
    </xsl:document>
</xsl:variable>

<!--②のテンプレートの呼び出し-->
<xsl:variable name="divAndCharCount" as="item()*">
    <xsl:call-template name="extractDiv">
        <xsl:with-param name="prmSection" select="$section/section[1]"/>
        <xsl:with-param name="prmMinCharCount" select="6"/>
    </xsl:call-template>
</xsl:variable>

デバッグ表示結果

$targetDiv[ 1 ]=<div>Authentication</div>
$targetDiv[ 2 ]=<div>DNS Server</div>
$divCharCount[ 1 ]= 14
$divCharCount[ 2 ]= 10

ちゃんと動いてくれました.

これがXPath3.0になると、型階層から見て関数でさえも戻り値の一部として返せることになります.さすがに試したことはないですが、ちよっと恐ろしいくらいの機能です.

XPath and XQuery Functions and Operators 3.0
1.5 Type system