XSLT 2.0で便利になった機能(25) グルーピング(その2)

前回の残りで、xsl:for-each-groupのグルーピングの属性、group-starting-withとgroup-ending-withを紹介したいと思います.
 
group-by, group-adjacentとgroup-starting-withとgroup-ending-withには決定的な違いがあります.前者はXPath式によりグルーピングのキーを指定したのに対し、後者はグルーピングの開始と終了の条件をパターンで指定します.では早速例に入りましょう.
 
入力データは前回とまったく同じものを使用します.これを、製品ID(@id)の先頭一文字の切り替わりでグルーピングしてみます.
 
<?xml version="1.0" encoding="UTF-8" ?>
<shipping-data>
    <shipping product="鉛筆"         id="p-001" date="2010-08-25" count="10"/>
    <shipping product="鉛筆"         id="p-001" date="2010-08-30" count="30"/>
    <shipping product="ノート"        id="n-020" date="2010-08-30" count="10"/>
    <shipping product="ノート"        id="n-030" date="2010-09-05" count="15"/>
    <shipping product="消しゴム"    id="e-005" date="2010-09-05" count="20"/>
    <shipping product="ボールペン" id="b-011" date="2010-08-25" count="05"/>
    <shipping product="ボールペン" id="b-012" date="2010-08-30" count="20"/>
</shipping-data>
 
結果は次のようになります.(見やすいように編集してあります)
 
<?xml version="1.0" encoding="UTF-8"?>
<shipping-data>
   <product-data id="p">
      <shipping product="鉛筆"         date="2010-08-25" count="10"/>
      <shipping product="鉛筆"         date="2010-08-30" count="30"/>
   </product-data>
   <product-data id="n">
      <shipping product="ノート"        date="2010-08-30" count="10"/>
      <shipping product="ノート"        date="2010-09-05" count="15"/>
   </product-data>
   <product-data id="e">
      <shipping product="消しゴム"    date="2010-09-05" count="20"/>
   </product-data>
   <product-data id="b">
      <shipping product="ボールペン" date="2010-08-25" count="05"/>
      <shipping product="ボールペン" date="2010-08-30" count="20"/>
   </product-data>
</shipping-data>
 
これを実現するためのテンプレートは次のようなものです.
 
<xsl:template match="shipping-data">
    <shipping-data>
        <xsl:for-each-group  select="shipping" group-starting-with="shipping[substring(@id,1,1)!=substring(preceding-sibling::*[1]/@id,1,1)]">
            <xsl:element name="product-data">
                <xsl:attribute name="id" select="substring(./@id,1,1)"/>
                <xsl:for-each select="current-group()">
                    <xsl:sort select="@date"/>
                    <xsl:copy>
                        <xsl:copy-of select="@*[name()!='id']"/>
                    </xsl:copy>
                </xsl:for-each>
            </xsl:element>
        </xsl:for-each-group>
    </shipping-data>
</xsl:template>
 
group-starting-withのポイントはグループの開始となるパターンの指定ですので、上記のように、今回の場合は、"shipping[substring(@id,1,1)!=substring(preceding-sibling::*[1]/@id,1,1)]"となります.
 
このように、グループの開始を指定するグルーピングでは、グループキーというものが存在しません.group-by, roup-adjacentで使用していたcurrent-grouping-key()の参照は無意味なものとなります.
 
残りはgroup-starting-withで、グループの終了を指定するものです.次のようなテンプレートに修正してみます.
 
<xsl:template match="shipping-data">
    <shipping-data>
        <xsl:for-each-group  select="shipping" group-ending-with="shipping[substring(@id,1,1)!=substring(following-sibling::*[1]/@id,1,1)]">
            <xsl:element name="product-data">
                <xsl:attribute name="id" select="substring(./@id,1,1)"/>
                <xsl:for-each select="current-group()">
                    <xsl:sort select="@date"/>
                    <xsl:copy>
                        <xsl:copy-of select="@*[name()!='id']"/>
                    </xsl:copy>
                </xsl:for-each>
            </xsl:element>
        </xsl:for-each-group>
    </shipping-data>
</xsl:template>
 
修正点はgroup-starting-withでpreceding-sibling::*[1]を参照していたものをfollowing-sibling::*[1]に変更したのみです.これにより、次のような同じグルーピングの結果を得る事ができます.
 
<?xml version="1.0" encoding="UTF-8"?>
<shipping-data>
   <product-data id="p">
      <shipping product="鉛筆"         date="2010-08-25" count="10"/>
      <shipping product="鉛筆"         date="2010-08-30" count="30"/>
   </product-data>
   <product-data id="n">
      <shipping product="ノート"        date="2010-08-30" count="10"/>
      <shipping product="ノート"        date="2010-09-05" count="15"/>
   </product-data>
   <product-data id="e">
      <shipping product="消しゴム"    date="2010-09-05" count="20"/>
   </product-data>
   <product-data id="b">
      <shipping product="ボールペン" date="2010-08-25" count="05"/>
      <shipping product="ボールペン" date="2010-08-30" count="20"/>
   </product-data>
</shipping-data>
 
group-starting-withはどちらかといえばこのようなXMLデータを扱うより、平坦なXMLドキュメントを構造化するような処理に向いているようです.以前WordのXML形式、WordMLから、構造的なXMLを生成するスタイルシートを手がけたことがありましたが、XSLT1.0しかなかったのでテイルリカージョンを使って非常に苦労した覚えがあります.group-starting-withがあれば、このような処理が楽に記述できるでしょう.よくこのような点までXSLT2.0は機能を提供しているものだと思います.