XSLT 2.0で便利になった機能(30) xsl:attribute

xsl:attributeについて変更点を紹介したいと思います.xsl:attributeはその名のとおり属性ノードを生成するものです.XSLT2.0からはselect属性がつきました.今までは
 
<xsl:attribute name="font-weight>
    <xsl:choose>
        <xsl:when test="string(@bold)='yes'">
            <xsl:value-of select="'bold'"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="'normal'"/>
        </xsl:otherwise>
    </xsl:choose>
<xsl:attribute>
 
なんてちんたら書いていましたが、これからは
 
<xsl:attribute name="font-weight" select="if (string(@bold)='yes') then 'bold' else 'normal'"/>
 
と一発のXPath式で書けるようになります.また、XPath式の戻り値がsequenceの場合も想定されていて、separatorで区切り文字を指定できるようになっています.例えば、
 
<xsl:attribute name="border-bottom" select="('thin','solid','black')" separator=" "/>
 
とすれば、仮にselectでボーダーの太さ、種類、色をxs:string*で順に返す関数を呼び出しても、区切り文字に気を使わないで使えます.結果として上記は
 
<xsl:attribute name="border-bottom" select="'thin solid black'"/>
 
と同じになります.あと追加されたものにvalidationとtypeがありますが、これらはschema-awareなXSLTプロセッサで意味を持ちます.
 
ところで、xsl:attributeは、xsl:copyやxsl:element、もっと直接的には<fo:block>などの結果ツリーへ要素が書き込まれる直後に記述されます.またxsl:attribute-setの下に書いて、複数の属性をまとめて操作することができました.
例えば
 
<xsl:attribute-set name="atsPara">
    <xsl:attribute name="font-family" select="'Times'"/>
    <xsl:attribute name="font-size"   select="'10.5pt'"/>
    <xsl:attribute name="line-height" select="'1.1em'"/>
    <xsl:attribute name="color"       select="'black'"/>
<xsl:attribute>
 
と書いて、XSL-FOでは、
 
<fo:block xsl:use-attribute-sets="atsPara">
     ...
</fo:block>
 
としたりします.このxsl:attribute-setですが、XSLT2.0では変更がありませんでした.私の考えでは、このxsl:attribute-setはもはや現実の要求に合わなくなってきているのではないかと思います.
 
例えば先ほどの例では、atsParaという名称のxsl:attribute-setsに入れたが最後、use-attribute-sets(もしくはxsl:use-attribute-sets)で使用する以外何の使い道もないのです.
 
ところが実際のスタイルシートでは、次のような要求が実際に寄せられます.
1.1つのスタイルシートで、複数言語の出力を得たい.
2.1つの文書で複数の言語を使いたい.
 
こうした時に、特にuse-attribute-setsは悲惨で、動的に取り込むxsl:attribute-setsを変更することができません.またxsl:attribute-setsにxsl:langをつけて、言語毎の属性を定義するということも不可能です.
私の実際にやっている仕事では、もうこういう不便なxsl:attribute-setsに見切りをつけて、スタイル定義はほとんどを外部ファイルに持たせて、そこから必要な属性の集合をxsl:copy-ofで選択出力するように切り替えています.
 
また、xsl:attributeは属性値であるにもかかわらず、xsl:attribute-setsに入れてしまうと、その属性値が参照できない(見られない)というのも極めて不便です.これなら、先ほどのxsl:attribute-setsは
 
<xsl:variable name="atsPara" as="element()">
  <dummy>
    <xsl:attribute name="font-family" select="'Times'"/>
    <xsl:attribute name="font-size"   select="'10.5pt'"/>
    <xsl:attribute name="line-height" select="'1.1em'"/>
    <xsl:attribute name="color"       select="'black'"/>
  </dummy>
<xsl:variable>
 
として、use-attribute-setsなど使わずに、
 
<fo:block>
    <xsl:copy-of select="$atsPara/@*">
    ...
</fo:block>
 
とでもやったほうがましです.これだけなら、あまり関係ないかもしれませんが、値が見られると言うことは非常に重要で、例えば固定幅の表を出力の用紙サイズ(マイナス左右マージン)に合わせてカラム幅の縮小を行いたいなどという要求は実際に存在します.このような場合、属性の定義値をスタイルシートで取得して計算に利用するということは切実な問題になります.
 
<!-- XSL-FOのページ定義用の属性-->
<xsl:variable name="atsCommonPage" as="element()">
  <dummy>
    <xsl:attribute name="page-width"    select="'215.9mm'"/>
    <xsl:attribute name="page-height"   select="'279.4mm'"/>
    <xsl:attribute name="margin-top"    select="'0mm'"/>
    <xsl:attribute name="margin-right"  select="'0mm'"/>
    <xsl:attribute name="margin-bottom" select="'0mm'"/>
    <xsl:attribute name="margin-left"   select="'0mm'"/>
  </dummy>
</xsl:variable>
<!-- XSL-FOのfo:region-body定義用の属性-->
<xsl:variable name="atsCommonRegionBody" as="element()">
  <dummy>
    <xsl:attribute name="margin-top"    select="'25.4mm'"/>
    <xsl:attribute name="margin-right"  select="'31.7mm'"/>
    <xsl:attribute name="margin-bottom" select="'25.4mm'"/>
    <xsl:attribute name="margin-left"   select="'31.7mm'"/>
    <xsl:attribute name="overflow"      select="'error-if-overflow'"/>
  </dummy>
</xsl:variable>
 
と定義してやれば、
 
 
で本文領域幅は計算できます.(もちろん単位は考慮するとして)しかも、<xsl:copy-of select="$atsCommonPage/@*"/>で、xsl:use-attribute-setsと同じ機能を実現できます.xsl:attribute-setsで属性を定義してしまったら、決して値の参照と計算はできないのです.今回は便利になった点というよりは、文句が多かったですが、今後のXSLTで改善してもらえないかと思う諸点です.