変数のデフォルト
はじまり
リンクのページにあるリンクバナーの指定に手を入れようとしたことから始まりました.リンクバナーはimgタグを使い,このように記述しています.
<a href="http://www.atmarkit.co.jp/"><img class="banner" src="banner/link_banner2.gif" height="31" width="88" alt="" /></a>
<a href="http://lion_x.tripod.co.jp/index.html"><img class="banner" src="banner/lionban.gif" height="40" width="200" alt="" /></a>
<a href="http://validator.w3.org/check?uri=http://www.dab.hi-ho.ne.jp/sasa/"><img class="banner" src="banner/valid-xhtml11.png" width="88" height="31" alt="Valid XHTML 1.1!" /></a>
imgタグの属性height,width,altはある程度決まった値になります.つまり,リンクバナーの多くには31×88ピクセルでaltは空文字を指定しています.そこで,linkタグとbannerタグを新設し,以下のような変換をxsltで行おうとしました.
- height属性は指定がなければ31,あればその値
- width属性は指定がなければ88,あればその値
- alt属性は指定がなければ空文字列,あればその値
<!-- 変換前 --> <link href="http://www.atmarkit.co.jp/"><banner src="banner/link_banner2.gif"/></link> <!-- width,height,alt指定なし --> <link href="http://lion_x.tripod.co.jp/index.html"><banner src="banner/lionban.gif" height="40" width="200"/></link> <!-- width,height指定あり.alt指定なし --> <link href="http://validator.w3.org/check?uri=http://www.dab.hi-ho.ne.jp/sasa/"><banner src="banner/valid-xhtml11.png" alt="Valid XHTML 1.1!"/></link> <!-- alt指定あり --> <!-- 変換後 --> <a href="http://www.atmarkit.co.jp/"><img class="banner" src="banner/link_banner2.gif" height="31" width="88" alt="" /></a> <a href="http://lion_x.tripod.co.jp/index.html"><img class="banner" src="banner/lionban.gif" height="40" width="200" alt="" /></a> <a href="http://validator.w3.org/check?uri=http://www.dab.hi-ho.ne.jp/sasa/"><img class="banner" src="banner/valid-xhtml11.png" width="88" height="31" alt="Valid XHTML 1.1!" /></a>
べたうち
とりあえず,height属性とwidth属性の有無により4通りの条件分岐を記述しました.alt属性は指定した値をそのまま使うので分岐の条件にはなりません.
<xsl:template match="link">
<a href="{@href}"><xsl:apply-templates/></a>
</xsl:template>
<xsl:template match="banner">
<xsl:choose>
<xsl:when test="@height and @width">
<img class="banner" src="{@src}" height="{@height}" width="{@width}" alt="{@alt}"/>
</xsl:when>
<xsl:when test="@height">
<img class="banner" src="{@src}" height="{@height}" width="88" alt="{@alt}"/>
</xsl:when>
<xsl:when test="@width">
<img class="banner" src="{@src}" height="31" width="{@width}" alt="{@alt}"/>
</xsl:when>
<xsl:otherwise>
<img class="banner" src="{@src}" height="31" width="88" alt="{@alt}"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
これであっさり実現できたのですが,あまり格好良くありません.
変数の使用
imgタグの記述は最後に一つにできるのではないかと考えました.C言語風に書くなら,こんな感じです.
int $height=88;
if(@height){
$height=@height;
}
int $width=31;
if(@width){
$width=@height;
}
<img class="banner" src="{@src}" height="{$height}" width="{$width}" alt="{@alt}"/>
これをこのままxsltに書いてみました.
<xsl:template match="link">
<a href="{@href}"><xsl:apply-templates/></a>
</xsl:template>
<xsl:template match="banner">
<xsl:variable name="height" select="31"/>
<xsl:if test="@height">
<xsl:variable name="height" select="@height"/>
</xsl:if>
<xsl:variable name="width" select="88"/>
<xsl:if test="@width">
<xsl:variable name="width" select="@width"/>
</xsl:if>
<img class="banner" src="{@src}" height="{$height}" width="{$width}" alt="{@alt}"/>
</xsl:template>
結論から言うと,このスクリプトは意図した通りには動きませんでした.height属性とwidth属性にどんな値を設定しても,height=31 width=88となるのです.原因は変数のスコープにありました.
<xsl:template match="link">
<a href="{@href}"><xsl:apply-templates/></a>
</xsl:template>
<xsl:template match="banner">
<xsl:variable name="height" select="31"/>
<xsl:if test="@height">
<xsl:variable name="height" select="@height"/>
<!-- ここで作成した変数$heightはif要素の中のローカル変数.外側で定義した$heightとは別物で,if要素を抜けた時点で破棄 -->
</xsl:if>
<xsl:variable name="width" select="88"/>
<xsl:if test="@width">
<xsl:variable name="width" select="@width"/>
<!-- ここで作成した変数$widthはif要素の中のローカル変数.外側で定義した$widthとは別物で,if要素を抜けた時点で破棄 -->
</xsl:if>
<img class="banner" src="{@src}" height="{$height}" width="{$width}" alt="{@alt}"/>
<!-- $height=31 width=88 -->
</xsl:template>
C言語風に書くとこういうことなのです.
int $height=88;
if(@height){
int $height=@height;
//ここでローカル変数$heightを定義.ifのブロックを抜けた時点で破棄
}
int $width=31;
if(@width){
int $width=@height;
//ここでローカル変数$widthを定義.ifのブロックを抜けた時点で破棄
}
<img class="banner" src="{@src}" height="{$height}" width="{$width}" alt="{@alt}"/>
//$height=31 width=88
xsltでは変数への代入ができないため,このやり方では実現できません.
結果ツリーフラグメント
xsltの変数は文字や数値以外にタグを代入できます.これを利用しました.
<xsl:template match="link">
<a href="{@href}"><xsl:apply-templates/></a>
</xsl:template>
<xsl:template match="banner">
<xsl:variable name="height">
<xsl:choose>
<xsl:when test="@height">
<xsl:value-of select="@height"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="31"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="width">
<xsl:choose>
<xsl:when test="@width">
<xsl:value-of select="@width"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="88"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<img class="banner" src="{@img}" height="{$height}" width="{$width}" alt="{@alt}"/>
</xsl:template>
height属性とwidth属性への値の設定はうまくいきました.
テンプレートへの引数
<xsl:apply-templates>には引数を渡すことができ,引数はデフォルト値を指定することができます.このやり方でもうまくいきました.
<xsl:template match="link">
<a href="{@href}">
<xsl:choose>
<xsl:when test="banner/@width and banner/@height">
<xsl:apply-templates select="banner">
<xsl:with-param name="width" select="banner/@width"/>
<xsl:with-param name="height" select="banner/@height"/>
</xsl:apply-templates>
</xsl:when>
<xsl:when test="banner/@width">
<xsl:apply-templates select="banner">
<xsl:with-param name="width" select="banner/@width"/>
</xsl:apply-templates>
</xsl:when>
<xsl:when test="banner/@height">
<xsl:apply-templates select="banner">
<xsl:with-param name="height" select="banner/@height"/>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="banner"/>
</xsl:otherwise>
</xsl:choose>
</a>
</xsl:template>
<xsl:template match="banner">
<xsl:param name="width" select="88"/>
<xsl:param name="height" select="31"/>
<img class="banner" src="{@img}" width="{$width}" height="{$height}" alt="{@alt}"/>
</xsl:template>
apply-templatesとchooseを入替えると,引数の値が次のテンプレートに渡せませんでした.引数にどんな値を指定しても88と31になりました.
<!-- これは引数に値が渡りません -->
<xsl:template match="link">
<a href="{@href}">
<xsl:apply-templates select="banner">
<xsl:choose>
<xsl:when test="banner/@width and banner/@height">
<xsl:with-param name="width" select="banner/@width"/>
<xsl:with-param name="height" select="banner/@height"/>
</xsl:when>
<xsl:when test="banner/@width">
<xsl:with-param name="width" select="banner/@width"/>
</xsl:when>
<xsl:when test="banner/@height">
<xsl:with-param name="height" select="banner/@height"/>
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:apply-templates>
</a>
</xsl:template>
<xsl:template match="banner">
<xsl:param name="width" select="88"/>
<xsl:param name="height" select="31"/>
<img class="banner" src="{@img}" width="{$width}" height="{$height}" alt="{@alt}"/>
</xsl:template>
名前付きテンプレート
テンプレートへの引数同様,名前付きテンプレートへの引数もデフォルト値を持つことが可能です.これを利用しました.
<xsl:template match="link">
<a href="{@href}"><xsl:apply-templates/></a>
</xsl:template>
<xsl:template match="banner">
<xsl:choose>
<xsl:when test="@height and @width">
<xsl:call-template name="banner">
<xsl:with-param name="height" select="@height"/>
<xsl:with-param name="width" select="@width"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="@height">
<xsl:call-template name="banner">
<xsl:with-param name="height" select="@height"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="@width">
<xsl:call-template name="banner">
<xsl:with-param name="width" select="@width"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="banner"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="banner">
<xsl:param name="width" select="88"/>
<xsl:param name="height" select="31"/>
<img class="banner" src="{@img}" height="{$height}" width="{$width}" alt="{@alt}"/>
</xsl:template>
おしまい
結果ツリーフラグメント,テンプレート,名前付きテンプレートと作りましたが,結局最初に作ったべたうち版を使っています.
参考文献
Steven Holzner, Insede XSLT, New Riders Publishing, 2002
株式会社クイック訳,XSLT実践ガイド−XSLTスタイルシートによるXML文書の活用法−,株式会社アスキー,2002
株式会社ユニテック,改訂版 標準XML完全解説(下),株式会社技術評論社,2001
xsl:attributeを使う(2003.8.17追記)
このようなやりかたを教えていただきました.ありがとうございます.
imgタグの中に<xsl:attribute>で属性を設定し,その中でheightとwidthの設定を行います.わかりやすいやり方だと思います.
imgタグは
<img ... />
と書かなくてはという先入観があったようです.
<img ...>
<xsl:attribute>
</xsl:attribute>
</img>
までは思い浮かびませんでした.
<xsl:template match="banner">
<img class="banner" src="{@src}" alt="{@alt}">
<xsl:attribute name="height">
<xsl:choose>
<xsl:when test="boolean(@height)">
<xsl:value-of select="@height"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="31"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:attribute name="width">
<xsl:choose>
<xsl:when test="boolean(@width)">
<xsl:value-of select="@width"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="88"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
</img>
</xsl:template>
xsl:for-eachを使う(2006.08.27追記)
このようなやりかたを教えていただきました.ありがとうございます.
指定してある属性を全てコピーして,デフォルトが必要な属性の指定がなかったら付け加えます.
<xsl:template match="/root/banner">
<img class="banner">
<xsl:for-each select="@*">
<xsl:attribute name="{name()}">
<xsl:value-of select="." />
</xsl:attribute>
</xsl:for-each>
<xsl:if test=".[not(@width)]">
<xsl:attribute name="width">
<xsl:value-of select="88"/>
</xsl:attribute>
</xsl:if>
<xsl:if test=".[not(@height)]">
<xsl:attribute name="height">
<xsl:value-of select="31"/>
</xsl:attribute>
</xsl:if>
</img>
</xsl:template>
for-eachを使わずに書くこともできます.
<xsl:template match="/root/banner">
<img class="banner">
<xsl:apply-templates select="@*"/>
<xsl:if test=".[not(@width)]">
<xsl:attribute name="width">
<xsl:value-of select="88"/>
</xsl:attribute>
</xsl:if>
<xsl:if test=".[not(@height)]">
<xsl:attribute name="height">
<xsl:value-of select="31"/>
</xsl:attribute>
</xsl:if>
</img>
</xsl:template>
<xsl:template match="@*">
<xsl:attribute name="{name()}">
<xsl:value-of select="." />
</xsl:attribute>
</xsl:template>