変数のデフォルト

はじまり

リンクのページにあるリンクバナーの指定に手を入れようとしたことから始まりました.リンクバナーは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>