グループをまとめるタグ その2

グループをまとめるタグ その1で記述したXSLTには大きな欠点が一つありました.それは,「グループ内の要素が一つだと,グループをまとめるタグを作成できない」というものです.つまり,以下のsam.xmlにsam.xslを適応すると,item[potision()=last()]のテンプレートだけを適応します.

<!-- sam.xml -->
<?xml version="1.0" encoding="utf-8"?>
<root>
  <item>a</item>
</root>

<!-- sam.xsl -->
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml"/>
  
  <xsl:template match="/root">
    <xsl:apply-templates select="item"/>
  </xsl:template>

  <xsl:template match="item[position()=1]">
    <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
    <xsl:text>dl</xsl:text>
    <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
    <dt><xsl:value-of select="self::node()"/></dt>
  </xsl:template>

  <xsl:template match="item">
    <dt><xsl:value-of select="self::node()"/></dt>
  </xsl:template>

  <xsl:template match="item[position()=last()]">
    <dt><xsl:value-of select="self::node()"/></dt>
    <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
    <xsl:text>/dl</xsl:text>
    <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
  </xsl:template>
</xsl:stylesheet>

<!-- 実行結果 -->
$ xt sam.xml sam.xsl
<?xml version="1.0" encoding="utf-8"?>
<dt>a</dt></dl>
  

要素が一つしかないということは,position()=1 and position=last()ということなので,sam.xslに修正を加えました.

<!-- sam.xsl -->
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml"/>
  
  <xsl:template match="/root">
    <xsl:apply-templates select="item"/>
  </xsl:template>

  <xsl:template match="item[position()=1 and position()=last()]"> <!-- itemが一つしかない場合 -->
    <dl>
      <dt><xsl:value-of select="self::node()"/></dt>
    </dl>
  </xsl:template>

  <xsl:template match="item[position()=1]">
    <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
    <xsl:text>dl</xsl:text>
    <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
    <dt><xsl:value-of select="self::node()"/></dt>
  </xsl:template>

  <xsl:template match="item">
    <dt><xsl:value-of select="self::node()"/></dt>
  </xsl:template>

  <xsl:template match="item[position()=last()]">
    <dt><xsl:value-of select="self::node()"/></dt>
    <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
    <xsl:text>/dl</xsl:text>
    <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
  </xsl:template>
</xsl:stylesheet>
  

しかし,実行結果は同じでした.

<!-- 実行結果 -->
$ xt sam.xml sam.xsl
<?xml version="1.0" encoding="utf-8"?>
<dt>a</dt></dl>
  

何気なく順番を入替えてみました.

<!-- sam.xsl -->
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml"/>
  
  <xsl:template match="/root">
    <xsl:apply-templates select="item"/>
  </xsl:template>

  <xsl:template match="item[position()=1]">
    <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
    <xsl:text>dl</xsl:text>
    <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
    <dt><xsl:value-of select="self::node()"/></dt>
  </xsl:template>

  <xsl:template match="item">
    <dt><xsl:value-of select="self::node()"/></dt>
  </xsl:template>

  <xsl:template match="item[position()=last()]">
    <dt><xsl:value-of select="self::node()"/></dt>
    <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
    <xsl:text>/dl</xsl:text>
    <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
  </xsl:template>

  <xsl:template match="item[position()=1 and position()=last()]"> <!-- itemが一つしかない場合を一番下に -->
    <dl>
      <dt><xsl:value-of select="self::node()"/></dt>
    </dl>
  </xsl:template>
</xsl:stylesheet>

<!-- 実行結果 -->
$ xt sam.xml sam.xsl
<?xml version="1.0" encoding="utf-8"?>
<dl><dt>a</dt></dl>
  

思っていたとおりの結果が出ました.もう少し試してみます.

<!-- sam.xsl -->
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml"/>
  
  <xsl:template match="/root">
    <xsl:apply-templates select="item"/>
  </xsl:template>

  <xsl:template match="item">
    <dt><xsl:value-of select="self::node()"/></dt>
  </xsl:template>

  <xsl:template match="item[position()=last()]">
    <dt><xsl:value-of select="self::node()"/></dt>
    <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
    <xsl:text>/dl</xsl:text>
    <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
  </xsl:template>

  <xsl:template match="item[position()=1 and position()=last()]"> 
    <dl>
      <dt><xsl:value-of select="self::node()"/></dt>
    </dl>
  </xsl:template>

  <xsl:template match="item[position()=1]"> <!-- 最初のitemに合致するテンプレートを一番下に -->
    <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
    <xsl:text>dl</xsl:text>
    <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
    <dt><xsl:value-of select="self::node()"/></dt>
  </xsl:template>
</xsl:stylesheet>

<!-- 実行結果 -->
$ xt sam.xml sam.xsl
<?xml version="1.0" encoding="utf-8"?>
<dl><dt>a</dt>

<!-- sam.xsl -->
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml"/>
  
  <xsl:template match="/root">
    <xsl:apply-templates select="item"/>
  </xsl:template>

  <xsl:template match="item[position()=last()]">
    <dt><xsl:value-of select="self::node()"/></dt>
    <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
    <xsl:text>/dl</xsl:text>
    <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
  </xsl:template>

  <xsl:template match="item[position()=1 and position()=last()]"> 
    <dl>
      <dt><xsl:value-of select="self::node()"/></dt>
    </dl>
  </xsl:template>

  <xsl:template match="item[position()=1]">
    <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
    <xsl:text>dl</xsl:text>
    <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
    <dt><xsl:value-of select="self::node()"/></dt>
  </xsl:template>

  <xsl:template match="item"> <!-- 最初と最後以外のitemに合致するテンプレートを一番下に -->
    <dt><xsl:value-of select="self::node()"/></dt>
  </xsl:template>
</xsl:stylesheet>

<!-- 実行結果 -->
$ xt sam.xml sam.xsl
<?xml version="1.0" encoding="utf-8"?>
<dl><dt>a</dt>
  

一つしかないitem要素は

  • item[position()=1]
  • item[position()=last()]
  • item[position()=1 and position()=last()]

の三つのテンプレートに合致し,XSLTの優先順序(最後に定義したテンプレートを優先)従い,一番最後にあるテンプレートが有効になっていたようです.item[position()=1 and position()=last()]を持つテンプレートを最後に書けば思ったとおりのことができます.しかし,忘れそうなので,priority属性で優先順位を指定することとしました.

<!-- sam.xsl -->
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml"/>
  
  <xsl:template match="/root">
    <xsl:apply-templates select="item"/>
  </xsl:template>

  <xsl:template match="item[position()=1 and position()=last()]" priority="1"> <!-- itemが一つしかない場合はこのテンプレートを優先する -->
    <dl>
      <dt><xsl:value-of select="self::node()"/></dt>
    </dl>
  </xsl:template>

  <xsl:template match="item[position()=1]">
    <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
    <xsl:text>dl</xsl:text>
    <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
    <dt><xsl:value-of select="self::node()"/></dt>
  </xsl:template>

  <xsl:template match="item">
    <dt><xsl:value-of select="self::node()"/></dt>
  </xsl:template>

  <xsl:template match="item[position()=last()]">
    <dt><xsl:value-of select="self::node()"/></dt>
    <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
    <xsl:text>/dl</xsl:text>
    <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
  </xsl:template>
</xsl:stylesheet>

<!-- 実行結果 -->
$ xt sam.xml sam.xsl
<?xml version="1.0" encoding="utf-8"?>
<dl><dt>a</dt></dl>
  

priorityを使わず,以下のやり方でも同様の結果となります.

<!-- sam.xsl -->
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml"/>
  
  <xsl:template match="/root">
    <xsl:apply-templates select="item"/>
  </xsl:template>

  <xsl:template match="item[position()=1 and position()=last()]"> <!-- itemが一つしかない場合はこのテンプレート -->
    <dl>
      <dt><xsl:value-of select="self::node()"/></dt>
    </dl>
  </xsl:template>

  <xsl:template match="item[position()=1 and position()!=last()]"> <!-- itemが複数ある場合の先頭はこのテンプレート -->
    <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
    <xsl:text>dl</xsl:text>
    <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
    <dt><xsl:value-of select="self::node()"/></dt>
  </xsl:template>

  <xsl:template match="item">
    <dt><xsl:value-of select="self::node()"/></dt>
  </xsl:template>

  <xsl:template match="item[position()!=1 and position()=last()]"> <!-- itemが複数ある場合の最後はこのテンプレート -->
    <dt><xsl:value-of select="self::node()"/></dt>
    <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
    <xsl:text>/dl</xsl:text>
    <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
  </xsl:template>
</xsl:stylesheet>

<!-- 実行結果 -->
$ xt sam.xml sam.xsl
<?xml version="1.0" encoding="utf-8"?>
<dl><dt>a</dt></dl>
  

トップページを作るXSLTのcontent部分も再修正です.

<!-- 修正後 -->
<xsl:template match="content[position()=1 and position()=last()]" priority="1">
  <dl class="contents">
    <xsl:call-template name="content"/>
  </dl>
</xsl:template>

<xsl:template match="content[position()=1]">
  <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
  <xsl:text>dl class="contents"</xsl:text>
  <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
  <xsl:call-template name="content"/>
</xsl:template>

<xsl:template match="content">
  <xsl:call-template name="content"/>
</xsl:template>

<xsl:template match="content[position()=last()]">
  <xsl:call-template name="content"/>
  <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
  <xsl:text>/dl</xsl:text>
  <xsl:text disable-output-escaping="yes">&gt;</xsl:text>
</xsl:template>

<xsl:template name="content">
  <dt><a href="{@href}"><xsl:value-of select="@name"/></a></dt>
  <dd>
    <xsl:apply-templates/>
  </dd>
</xsl:template>