XSLTに計算をさせてみる

とりあえず、階乗を出力するXSLTを書いてみた。

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <html>
            <head>
                <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
                <title>factorial</title>
            </head>
            <body>
                <p>
                    <xsl:apply-templates select="factorial"/>
                </p>
            </body>
        </html>
    </xsl:template>
    
    <!-- 階乗要素から階乗を計算する -->
    <xsl:template match="factorial">
        <xsl:call-template name="factorial-impl">
            <xsl:with-param name="n" select="@n"/>
        </xsl:call-template>
    </xsl:template>
    
    <!-- 階乗の計算をするテンプレート -->
    <xsl:template name="factorial-impl">
        <!-- 計算する値 -->
        <xsl:param name="n"/>
        <!-- 途中結果 デフォルト値は1 -->
        <xsl:param name="r" select="1"/>
        
        <!-- xsl:chooseを使うとインデントが深くなるので、xsl:ifを使用 -->
        <!-- 終了条件 -->
        <xsl:if test="$n=0">
            <xsl:value-of select="$r"/>
        </xsl:if>
        <!-- 計算本体 -->
        <xsl:if test="$n!=0">
            <xsl:call-template name="factorial-impl">
                <xsl:with-param name="n" select="$n - 1"/>
                <xsl:with-param name="r" select="$n * $r"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

計算結果をxsl:variableで受け取ってもいいけど、無意味に末尾再帰にしてみた。


XSLTでは分岐のためにxsl:ifとxsl:chooseがあって、xsl:ifは単一の分岐に、xsl:chooseは複数の分岐に使用する。
でも、xsl:elseはないから、成立する場合としない場合に別の処理をさせたい場合、xsl:chooseを使うことが良くある。
上のxsl:ifをxsl:chooseで書き換えると、

<xsl:choose>
    <xsl:when test="$n!=0">
        <xsl:value-of select="$r"/>
    </xsl:when>
    <xsl:otherwise>
        <xsl:call-template name="factorial-impl">
            <xsl:with-param name="n" select="$n - 1"/>
            <xsl:with-param name="r" select="$n * $r"/>
        </xsl:call-template>
    </xsl:otherwise>
</xsl:choose>

こんな感じ。
コメントにあるように、インデントが深くなってしまうのが難点。
ということで、実際には真偽が反対になるxsl:ifを書くことでelseっぽいものを実現してみたり。
=に対して!=の他にも、全体をnot()に渡す方法もある。


このXSLTを使って、実際にIE7で計算させてみた*1

<?xml version='1.0' encoding='utf-8'?>
<?xml-stylesheet href='factorial.xsl' type='text/xsl'?>

<factorial n="170"/>
72574156153080040000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000

これ以上はInfinityが出力された。てか計算速いな。もしかして末尾最適化が実装されてる?

*1:見にくいので80文字で折り返し