XSLT3.0への道(4) xsl:iterate

xsl:iterateについて紹介したいと思います.xsl:iterateですから、「繰り返し」→ループかな?と思いました.確かに繰り返しではありますが、tail-recursion(後方再帰呼び出し?)を簡単に作れるようにしたのが本質です.これはドラフトのNoteのところで解説されています.
 
Conceptually, xsl:iterate behaves like a tail-recursive function. The xsl:next-iteration instruction then represents the recursive call, supplying the tail of the input sequence as an implicit parameter. There are two main reasons for providing the xsl:iterate instruction. One is that many XSLT users find writing recursive functions to be a difficult skill, and this construct promises to be easier to learn. The other is that recursive function calls are difficult for an optimizer to analyze.
 
XSLTのユーザーにとっては再帰呼び出しをつくるのが、難しいスキルを要求されていること、XSLTプロセッサにとっては再帰呼び出しは最適化するのに解析が難しいことが理由のようです.
ドラフトには一番簡単な例で次のようなものが載っています.
 
 
<account>
  <xsl:stream href="transactions.xml">
    <xsl:iterate select="transactions/transaction">
      <xsl:param name="balance" select="0.00" as="xs:decimal"/>
      <xsl:variable name="newBalance"
                    select="$balance + xs:decimal(@value)"/>
      <balance date="{@date}" value="{$newBalance}"/>
      <xsl:next-iteration>
        <xsl:with-param name="balance" select="$newBalance"/>
      </xsl:next-iteration>
    </xsl:iterate>
  </xsl:stream>
</account>
 
(#入力データと出力結果は是非上記のWEBを御覧ください.)
 
このスタイルシートはxsl:streamを使っています.もちろんそれが一番効果的なのですが、そうでなくとも動きます.次が書き直したものです.
 
<xsl:output indent="yes"/>
<xsl:template match="transactions">
    <account>
        <xsl:iterate select="transaction">
            <xsl:param name="balance" select="0.00" as="xs:decimal"/>
            <xsl:variable name="newBalance"
                          select="$balance + xs:decimal(@value)"/>
            <balance date="{@date}" value="{$newBalance}"/>
            <xsl:next-iteration>
                <xsl:with-param name="balance" select="$newBalance"/>
            </xsl:next-iteration>
        </xsl:iterate>
    </account>
</xsl:template>
 
xsl:iterateのselect属性で指定された要素を次々に処理します.xsl:next-iterationで自分自身を呼び出して、xsl:iterateのシーケンスコンストラクタ部分で結果を生成します.(この場合はbalance要素)
これと等価でxsl:iterateを使わないで書いた、一般的なtail-recursionのスタイルシートは次のようになります.
 
<xsl:output indent="yes"/>
<xsl:template match="transactions">
    <account>
        <xsl:apply-templates select="transaction[1]">
            <xsl:with-param name="balance" select="0.00" as="xs:decimal"/>
        </xsl:apply-templates>   
    </account>
</xsl:template>
<xsl:template match="transaction">
    <xsl:param name="balance" required="yes" as="xs:decimal"/>
    <xsl:variable name="newBalance"
                  select="$balance + xs:decimal(@value)"/>
    <balance date="{@date}" value="{$newBalance}"/>
    <xsl:apply-templates select="following-sibling::transaction[1]">
        <xsl:with-param name="balance" select="$newBalance"/>
    </xsl:apply-templates>
</xsl:template>
 
これを見てわかりますが、一般的なtail-recursionでは再帰呼び出しのきっかけをつくるテンプレートと、実際の再帰を処理する2つのテンプレートが必要ですが、xsl:iterateでは1つで済みます.なれてくれば簡単に作れそうですね.
 
ところでxsl:iterateはどのような処理に向くのでしょうか?それはやはり一気通関の処理かなのではないかと思います.最初から順に処理していって最後でおしまいというパターンです.途中でやめたければxsl:breakを使って、止めることができます.また最後にまとめの処理を行いたければxsl:on-completionで結果を返せます.ストリーミングと併用すれば効果的でしょう.(Saxon EE持ってないので試せませんが...)
苦手なのは、シーケンスに処理してゆく事は同じだが、途中でやめてまた次から再開というような複雑な場合です.また止める条件がxsl:choose、xsl:ifをつかって複雑に記述しなければならないのもダメです.なぜかというと、止めるためのxsl:breakは、xsl:iterateのシーケンスコンストラクタのtail positionになければならないとされていてこれを実現するのが実は非常に大変です.
 
コードが大きいので載せられませんが、以下のようなWordML文書(たぶんOpen XMLでもできる)を、XMLに落とすスタイルシートを二段階のXSLTで使って作ってみました.中を見ればわかりますが多分にxsl:iterateでは無理があります.動いていますが今度作れといわれたらxsl:iterateは使わないでしょう.
 
[元のWordML文書]
イメージ 1
 
[生成したXML]
イメージ 2
 
お暇な方はコード御覧ください.
 
 
listsample.xml:元のWordMLファイル(Word2003文書)
step1_result.xml:ステップ1の結果XML
step2_1.xsl:ステップ2のスタイルシート(xsl:iterate使用)
step2_result.xml:ステップ2の結果XML