テンプレートと変数(1)

XSLT1.0が勧告になり、やがてMicrosoftがMSXML3を出し始めた頃は、結構スゴイということ話題になりました.使い始めたのですが、ともかく動かすこと優先でした.サンプルを見てトライアンドエラーでやっていて、恥ずかしながらXSLT1.0勧告を根本から理解してスタイルシートを作るという余裕はありませんでした.MSXML3は今から考えるとxsl:messageでデバッグメッセージを出せなかったり、エラーメッセージが不親切など、開発者にしてみればイマイチでしたが、ともかくMicrosoft謹製ということでなんの疑問もなく使っていました.この頃はまだMicrosoftJava VMを作っていて、その上で動くInstant Saxonが便利と言うことになり、だんだんSaxonに移っていったように覚えています.

さてXSLT1.0勧告が出たのは1999年11月ですからもう15年くらい前です.ここでは原点であるXSLT1.0に立ち戻ってテンプレートと変数を振り返ってみたいと思います.(ただし勧告に沿う厳密な書き方をしていません)

まずXPath1.0は4つのデータ型を定義しています.

・node-set型(入力ツリーから抽出したノードの集合)
・boolean型(真偽値型)
・number型(浮動小数点型)
・string型(文字列型)

変数のxsl:variableでは、select属性を使ってこれらの属性を持った変数を生成できました.以下は一例です.

    <!--$piはnumber型です.-->
    <xsl:variable name="pi" select="3.141567"/>

    <!-- $cYes,$cNoはstring型です. -->
    <xsl:variable name="cYes" select="'yes'"/>
    <xsl:variable name="cNo"  select="'no'"/>

    <!--初期値を持ったパラメータ-->
    <xsl:param name="PRM_OUTPUT_FROM_VARIABLE" select="$cYes"/>

    <!--$pOutputFromVariableはboolean型です.-->
    <xsl:variable name="pOutputFromVariable" select="$PRM_OUTPUT_FROM_VARIABLE = $cYes"/>

    <!-- $chapterはnode-set型です -->
    <xsl:variable name="chapter" select="/doc/chapter"/>

XSLT1.0ではXSLT2.0のas属性のように型を明示的に与えることは出来ませんでした.select属性に記述された内容から自動的に型が決定されました.

$pOutputFromVariableのような変数を作っておくと、

<xsl:if test="$PRM_OUTPUT_FROM_VARIABLE = $cYes">

などと書かずに

<xsl:if test="$pOutputFromVariable">

と一発で判定できるので重宝したものです(今も使っています).

$chapterは言ってみれば入力ツリーへのポインタ(?というか参照)みたいなものなので、XPath式にそのまま使えました.例えば次のように書くことが出来ました.

<xsl:for-each select="$chapter[1]/p">

変数はselect属性を書かなければ<xsl:variable>~</xsl:varibale>の間にXSLTの命令とLiteral result elementと呼ばれるXSLT名前空間以外の要素やテキストを書くことが出来ました.例えば

    <!-- $bookはResult Tree Fragmentです. -->
    <xsl:variable name="book">
        <book>
            <xsl:copy-of select="$chapter"/>
        </book>
    </xsl:variable>

    <!-- $bookRefはResult Tree Fragment型です. -->
    <xsl:variable name="bookRef" select="$book"/>

とかけます.コメントにもありますようにこれはResult Tree Fragment(結果ツリーの一部)と呼ばれる第5の型です.Result Tree Fragmentは

1.入力ツリーのノードだけでなく任意のノードを書くことが出来ます.(上の例では<book>)
2.しかし勧告により使用方法は限定されていて、テンプレートの中で参照してxsl:copy-ofで結果ツリーに書き込むことが主な用途とされていました.

例えばテンプレートの中で、

<xsl:copy-of select="$book"/>

とします.

以上が変数の概略です.つまり変数はnode-set,boolean,number,string,Result tree fragmentを保持することが出来ました.node-setから入力ツリーをXPath式で参照したり、xsl:copy-ofで結果ツリーに書き込めました.boolean,number,stringは計算や判定に用いることが出来ました.

ではテンプレートとはなんだったのでしょう?簡単に言えば変数が様々な型のインスタンスを保持したのですが、テンプレートは<xsl:template>~</xsl:template>の間にXSLTの命令やLiteral result elementを書いて、結果ツリーの一部となるノードとして生成するのが役割です.例えば

    <!--テンプレートは結果ツリーに対してノードを生成します-->
    <xsl:template match="/doc">
        <xsl:choose>
            <xsl:when test="$pOutputFromVariable">
                <xsl:copy-of select="$book"/>
            </xsl:when>
            <xsl:otherwise>
                <book>
                    <xsl:copy-of select="/doc/chapter"/>
                </book>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

などと書けます.

XSLT1.0勧告ではxsl:templateは

<!-- Category: top-level-element -->
<xsl:template
  match = pattern 
  name = qname 
  priority = number 
  mode = qname>
  <!-- Content: (xsl:param*, template) -->
</xsl:template>

と定義されていて、その内容はxsl:paramのあとは"template"となっています.xsl:templateの中身がtemplateなのでいかにもわかりづらく、そもそもtemplateとは何か?というちゃんとした説明がなかなかされていないように思えました.しかし、これはXSLT1.0勧告の最後についているDTDサンプルを見るとわかります.



実体定義のtemplateは

<!ENTITY % template "
 (#PCDATA
  %instructions;
  %result-elements;)*
">

と定義されていました.%instructionsはXSLTの命令です.

<!ENTITY % char-instructions "
  | xsl:apply-templates
  | xsl:call-template
  | xsl:apply-imports
  | xsl:for-each
  | xsl:value-of
  | xsl:copy-of
  | xsl:number
  | xsl:choose
  | xsl:if
  | xsl:text
  | xsl:copy
  | xsl:variable
  | xsl:message
  | xsl:fallback
">

<!ENTITY % instructions "
  %char-instructions;
  | xsl:processing-instruction
  | xsl:comment
  | xsl:element
  | xsl:attribute
">

%result-elementsは例えば

<!ENTITY % result-elements "
  | fo:inline-sequence
  | fo:block
">

という例が載っていました.結果ツリーに書き込む要素なのでいわば目的の文書モデルにあっていれば何でも良いわけです.

つまり、templateはノードを生成するためのXSLTの命令やLiteral result elementの集まりです.そして実はtemplateはxsl:templateだけでなくxsl:variableでも定義に使われています.

<!-- Category: top-level-element -->
<!-- Category: instruction -->
<xsl:variable
  name = qname 
  select = expression>
  <!-- Content: template -->
</xsl:variable>

つまり、変数のxsl:variableもselect属性がなければxsl:templateと同じ構造をしています.変数がtemplateの生成するノードを保持するのに対して、テンプレートはtemplateの生成するノードを結果ツリーに書き込む訳です.

これがXSLT1.0の変数とテンプレートの概略です.ではXSLT2.0ではこれはどう変わったのでしょうか?次に取り上げたいと思います.