XSLT 2.0で便利になった機能(46) xsl:templateのmatch属性

テンプレートとmatch属性はXSLTスタイルシートの核をなす構成要素です.match属性の中身はパターン(pattern)と呼ばれます.ここには一般的にはパスのパターンを書きます.例えば
 
<xsl:template match="p">
 
はp要素にマッチした処理を記述します.
 
<xsl:template match="p[string(@outputclass) eq 'normal']">
 
はouputclass属性が"normal"のp要素にマッチします.
 
さて一般的にXSL-FOに変換するスタイルシートでは、次のような構成が基本であると考えられます.
 
<xsl:template match="[変換元XMLでのブロック要素]">
  <fo:block>
    <!-- fo:blockの属性を設定:前後(before、after)スペーシング、インデントなど-->
    <!--下位を処理-->
    <xsl:apply-templates/>
  </fo:block>
</xsl:template>
 
<xsl:template match="[変換元XMLでのインライン要素]">
  <fo:inline>
    <!-- fo:inlineの属性を設定:前後(start、end)スペーシング、文字属性など-->
    <!--下位を処理-->
    <xsl:apply-templates/>
  </fo:inline>
</xsl:template>
 
マッチする要素の条件が単純なうちはこれだけで済みます.しかし時として同じ要素でも、それ自身または下位の要素の条件により複雑な処理をしなければならない場合も発生します.
 
昔見た例ですが、<xsl:template match="~">と書き、そのテンプレート内部で、80個を超える要素の条件による分岐を記述しているテンプレートがありました.たぶんXSLT1.0で作られずっとメンテナンスされてきたものです.解読しようとしたのですが、ノードの条件分岐の数の多さに圧倒されあきらめました.このようなテンプレートは一人の方が専門にメンテナンスしない限り維持することは困難でしょう.
 
プログラムは集中と分散のバランスが取れていないとなかなか読みこなすことがむつかしいのですが、このテンプレートの場合、集中によってありとあらゆる異なったノード条件の処理がひとつのテンプレートに詰め込まれていました.
 
集中自体がすべて問題かといえば必ずしもそうではないのですが、このテンプレートの一番の問題は集中によってもたらされるロジックの分断で「完読性が失われる」ということでした.あるノード条件の場合、何の要素を生成し、どのような属性を付加し、下位をどのように処理するか、1000ステップの中に分断されて記述されていて、一見しただけでは何をやっているのかとてもわからないのです.
 
このようになってしまう原因のひとつにXSLT1.0ではxsl:template/@matchにあまり複雑な条件を記述するのが困難だったことがあります.例えばdiv/spanという構造があり、divのテンプレートでは下位のspanの文字数を数え、一定数以上のものがあったらdivをfo:blockで、そうでなければfo:inlineでレイアウトしたい場合があったとします.XSLT1.0では<xsl:template match="div">のテンプレートで文字数を数えて処理を分岐せざるを得ませんでした.
 
しかしXSLT2.0はすばらしい機能を提供してくれています.それはパターンのパスの制約条件に関数呼び出しが記述できるということです.この場合、
 
<xsl:function name="ahf:isInline" as="xs:boolean">
  <xsl:param name="prmElem" as="element()"/>
  <!--$prmElem以下の文字数を数えて閾値しきい値)と比較し、
      インラインでレイアウトすべき場合true()を返す.
      ブロックでレイアウトすべき場合false()を返す.
    -->
</xsl:function>
 
という関数さえ実装してしまえば、
 
<xsl:template match="div[ahf:isInline(.)]">
  <!--fo:inlineを生成し下位を処理-->
</xsl:template>
 
<xsl:template match="div[not(ahf:isInline(.))]">
  <!--fo:blockを生成し下位を処理-->
</xsl:template>
 
と記述できます.
 
これはまだ単純な例ですが、xsl:functionは非常に複雑なノードの条件を判断して返すことが出来ます.ですので先ほどの過度に集中したテンプレートもmatch属性のパターンの制約条件に関数さえ記述すれば、完読性の高い数個のテンプレートに分割できたでしょう.XSLT1.0でもXSLT2.0でもmatch属性はパターンを書くのですが、XSLT2.0ではxsl:functionの呼び出しを記述することにより、飛躍的にテンプレートの可読性を上げることができます.
 
このような関数の記述の欠点は、例えば上記の例の場合ahf:isInline(.)が二回呼び出されるかもしれないということです.最適化されたXSLTプロセッサならこのような場合も呼び出しを抑制してくれるかもしれませんが保障はありません.
 
また、トンネルパラメータがxsl:functionでは透過的に通過してくれない点も考えておかねばならないでしょう.トンネルパラメータは非常に便利で、最上位の文書要素で設定して、最下位のテキストノードテンプレートでこれを使うという芸当が可能です.なのですがxsl:functionの呼び出しでは関数にトンネルパラメータは渡されません.トンネルパラメータはあくまでもxsl:apply-templatesの連鎖で引き継がれます.
 
例えば文字数を数えるパラメータでimage/@placement="inline"のものがあった場合、これをEMサイズを考慮して文字数に換算するという処理があったとします.この場合EMサイズをトンネルパラメータで上位から渡せば便利です.しかし関数呼び出しではトンネルパラメータは渡せないので、このように厳格に行う場合、<xsl:template match="div[ahf:isInline(.)]">というような記述はできなくなります.
 
これらの問題はありますが、テンプレートを適度に分割して完読性を高める上でxsl:stylesheet/match属性での関数の使用はぜひ検討すべきでしょう.