xsl:attribute-setについて考える.

xsl:attribute-setについて考えてみました.みなさんはどのように使っておられるでしょうか?
一般的にxsl:attribute-setは属性すなわちスタイルの定義です.なので通常テンプレートとは別個のファイルにまとめて定義し、ロジックを記述するテンプレートの方でxsl:use-attribute-setsで名前をキーにして属性群を「ひっぱり」そこに「展開」します.
 
<xsl:attribute-set name="atsP">
  <xsl:attribute name="font-family" select="'Arial'"/>
  <xsl:attribute name="font-size"   select="'10.5pt'"/>
  <xsl:attribute name="start-indent"  select="'1em'"/>
</xsl:attribute-sets>
 
<xsl:template match="section/p">
  <fo:block xsl:use-attribute-sets="atsP">
    ...
  </fo:block>
</xsl:template>
 
ところで、XSLT2.0でもxsl:attribute-setの下位は単にxsl:sttributeを羅列できるだけです.しかしxsl:attributeの中には、ロジックが記述できます.このときに参照できるのはカレントコンテキストです.ですから例えば、sectionの下で最初に出現する段落の場合のみ、space-beforeを確保したければ
 
<xsl:attribute-set name="atsP">
  <xsl:attribute name="space-before">
     <xsl:choose>
        <xsl:when test="empty(preceding-sibling::*)">
            <xsl:value-of select="'10mm'"/>   
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="'0mm'"/>
        </xsl:otherwise>
    </xsl:choose>
  </xsl:attribute>
</xsl:attribute-set>
 
と記述することができます.私はこういう記述はやったことはありませんでしたが、お客様のスタイルシートでは結構使われているようでした.
でも実際に解析をしようとすると、テンプレート部分とxsl:attribute-setの記述されたスタイルシートの両方を見比べなければならず大変でした.
 
やはり、いくらスタイルとはいえ、スタイルの選択を行うのはテンプレートを行う部分に記述すべきではないかと思います.要するに記述が分散してしまって何をやっているのか把握するのが困難になるからです.
ところが、xsl:attributeはxsl:use-attribute-sets属性で「無条件」に引っ張れるだけです.「あるときはこっち」また「あるときはこっち」とスタイルを選択する選択の余地はありません.これではxsl:attributeにロジックを記述しなければならないのももっともです.
 
ところがXSLTの仕様を読むと、「The result of evaluating an attribute set is a sequence of attribute nodes. 」と書いてあります.それにもかかわらず、xsl:attribute-setはattribute()*として扱えないのです.これはXSLT仕様の非常な弱点ではないかと考えています.
 
もし次のように記述したらどうでしょうか?
 
<xsl:variable name="atsP" as="attribute()*">
  <xsl:attribute name="font-family" select="'Arial'"/>
  <xsl:attribute name="font-size"   select="'10.5pt'"/>
  <xsl:attribute name="start-indent"  select="'1em'"/>
</xsl:variable>

変数$atsPはxsl:attribute-setに比べてはるかに自由に使うことが出来ます.例えば
 
<fo:block>
  <xsl:copy-of select="$atsP"/>
  ...
</fo:block>
 
と、xsl:attribute-setと同じように使えるのは当たり前です.font-familyだけを除いて適用したければ
 
<xsl:copy-of select="$atsP[name() ne 'font-family']"/>
 
とやれます.また次のようにスタイルを別々に作って、
 
<!--段落共通のスタイル-->
<xsl:variable name="atsP" as="attribute()*">
 ...
</xsl:variable>
<!--先頭段落のスタイル-->
<xsl:variable name="atsPFirst" as="attribute()*">
 ...
</xsl:variable>
<!--最終段落のスタイル-->
<xsl:variable name="atsPLast" as="attribute()*">
 ...
</xsl:variable>
 
最初の段落、中間の段落、最後の段落というふうに選択してスタイルをあてることも可能です.
 
<!--スタイルの処理-->
<xsl:variable name="blkAttr" as="attribute()*">
  <xsl:choose>
    <!--最初の段落-->
    <xsl:when test="empty(preceding-sibling::*)">
      <xsl:copy-of select="$atsP"/>
      <xsl:copy-of select="$atsPFirst"/>
    </xsl:when>
    <!--最後の段落-->
    <xsl:when test="empty(follwoing-sibling::*)">
      <xsl:copy-of select="$atsP"/>
      <xsl:copy-of select="$atsPLast"/>
    </xsl:when>
    <!--通常の段落-->
    <xsl:otherwise>
      <xsl:copy-of select="$atsP"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:variable>
 
<fo:block>
  <xsl:copy-of select="$blkStyle"/>
  <!--コンテンツの処理-->
  ...
</fo:block>
 
いかがでしょうか?スタイルをattribute()*として扱えればこれだけ記述性が向上します.スタイルの処理とコンテンツの処理を分離できるのも魅力です.大概のスタイルシートは、この2つがごちゃ混ぜになっているのが普通ですから.
 
でも普段スタイルシートを作っていて、スタイルをxsl:variableに定義する人はまずいないでしょう.そもそもそんなことは考えないでしょうし、xsl:variableではxsl:attribute-setsの天性の特徴の「スタイルの階層性を実現する」xsl:use-attribute-sets属性が使えないからです.次のようにすれば回避できますがあまりスマートとはいえません.
 
<xsl:variable name="commonDiv" as="attribute()*">
  <xsl:attribute name="text-align" select="'start'"/>
</xsl:variable>
 
<xsl:variable name="atsP" as="attribute()*">
  <xsl:copy-of select="$commonDiv"/>
  <xsl:attribute name="font-family" select="'Arial'"/>
  <xsl:attribute name="font-size" select="'10.5pt'"/>
  <xsl:attribute name="start-indent" select="'1em'"/>
</xsl:variable>
 
でも私はこの10年来xsl:attribute-setsをほとんど使ったことがありません.今まで述べてきたxsl:attribute-setsの不自由な点を改善し、スタイルとロジックを分離してスタイルを外付けにする仕組みをつくってあり、大概のスタイルシートはこの仕組みの使いまわしでやっているからです.実際には下記のような感じの外付けのXMLファイルにスタイルと変数を定義します.xsl:attribute-sets、xsl:variableとよく似ていますが外付けです.
 
<!--これはスタイルシートではありません.
    似ていますが、外付けのスタイル定義です.-->
<attribute-set name="commonDiv">
  <attribute name="text-align">start</attribute>
</attribute-set>
<variable name="paraFont">Arial</variable>
<attribute-set name="atsP" use-attribute-sets="commonDiv">
  <attribute name="font-family">$paraFont</attribute>
  <attribute name="font-size">10.5pt</attribute>
  <attribute name="start-indent">1em</attribute>
</attribute-set>
 
この$paraFontというのが変数参照で、"Arial"に置き換わります.また"atsP"というスタイルは、"commonDiv"というスタイルを継承します.
 
そしてこれらのスタイル、変数定義はテンポラリツリーに変換しておき、スタイルシート中から、
 
<!--スタイルを取り出す.結果はattribute()*-->
<xsl:call-template name="getAttributeSet">
  <xsl:with-param name="prmStyle" select="'atsP'"/>
<xsl:call-template>
 
<!--変数値を取り出す.結果はxs:string -->
<xsl:call-template name="getVarValue">
  <xsl:with-param name="prmVarName" select="'paraFont'"/>
<xsl:call-template>
 
というテンプレート呼び出しで取り出せるようにしています.
なにかXSLTとごちゃ混ぜになりそうですが、こうしてスタイルを外付けにすることにより、お客様がスタイルシートに触らずに自由にpublicationのスタイルを変更したり、スタイルシートの処理の閾値(<variable>で定義)を調整できるようにしてあります.
 
XSLTの標準でこのようなことが出来るようになってくれれば便利なのにと思いますが、いかがでしょう?今のXSLT仕様ではスタイルとロジックが分離できるようでいて実はうまくできないのです.