XSLT 2.0で便利になった機能(24) グルーピング

XSLT2.0で導入された看板機能にxsl:for-each-groupによるグルーピングがあります.今回はこれを紹介します.
 
まず最もポピュラーな例です.次のような入力データを出荷日(@date)でグルーピングして、さらに製品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>
   <daily-data date="2010-08-25">
      <shipping product="ボールペン" id="b-011" count="05"/>
      <shipping product="鉛筆"         id="p-001" count="10"/>
   </daily-data>
   <daily-data date="2010-08-30">
      <shipping product="ボールペン" id="b-012" count="20"/>
      <shipping product="ノート"        id="n-020" count="10"/>
      <shipping product="鉛筆"         id="p-001" count="30"/>
   </daily-data>
   <daily-data date="2010-09-05">
      <shipping product="消しゴム"    id="e-005" count="20"/>
      <shipping product="ノート"        id="n-030" count="15"/>
   </daily-data>
</shipping-data>
 
これを実現するためのテンプレートは次のようなものです.
 
<xsl:template match="shipping-data">
    <shipping-data>
        <xsl:for-each-group select="shipping" group-by="@date">
            <xsl:element name="daily-data">
                <xsl:attribute name="date" select="current-grouping-key()"/>
                <xsl:for-each select="current-group()">
                    <xsl:sort select="@id"/>
                    <xsl:copy>
                        <xsl:copy-of select="@*[name()!='date']"/>
                    </xsl:copy>
                </xsl:for-each>
            </xsl:element>
        </xsl:for-each-group>
    </shipping-data>
</xsl:template>
 
xsl:for-each-groupのポイントは、@selectで対象を指定し、@group-byでグルーピングするキーを指定します.この例ではグループ毎に"daily-data"という要素を生成していますが、date属性にキーとなった日付をセットしています.その時々のコンテキストのグループのキーは、current-grouping-key()で取得できます.
 
"daily-data"というデータの下に、そのグループに属するデータをコピーします.そのグループの要素はcurrent-group()という関数から取得できます.いかがでしょうか?以外と簡単で有用です.
 
今までDocBookなどの索引の作成は、indexterm要素をソートして、テイルリカージョン(tail recursion)という方法で先頭から次々にindextermをたどる方法でやってきましたが、このxsl:for-each-groupを使えば、簡単なものでしたらあまり苦労せずに作れそうです.
 
さてxsl:for-each-groupには様々なバリエーションがあります.先ほどの例では、@group-byを使いましたが、これをgrouping-adjacentに変えるとどうなるでしょうか?結果はこのようになります.
 
<?xml version="1.0" encoding="UTF-8"?>
<shipping-data>
   <daily-data date="2010-08-25">
      <shipping product="鉛筆"         id="p-001" count="10"/>
   </daily-data>
   <daily-data date="2010-08-30">
      <shipping product="ノート"        id="n-020" count="10"/>
      <shipping product="鉛筆"         id="p-001" count="30"/>
   </daily-data>
   <daily-data date="2010-09-05">
      <shipping product="消しゴム"    id="e-005" count="20"/>
      <shipping product="ノート"        id="n-030" count="15"/>
   </daily-data>
   <daily-data date="2010-08-25">
      <shipping product="ボールペン" id="b-011" count="05"/>
   </daily-data>
   <daily-data date="2010-08-30">
      <shipping product="ボールペン" id="b-012" count="20"/>
   </daily-data>
</shipping-data>
 
group-adjacentでは、入力データの順にグループキーが作られグルーピングが行われます.グループキーの切り替わり(この場合はdate)で新たなグループが生成されます.結果としてこの例ではdateが同じ値のグループが複数出力されます.
 
group-byとgrouping-adjacentは排他的です.いずれか1つしか指定できません.あと指定可能なグルーピングの属性として、group-starting-withとgroup-ending-withがあります.また機会があったら取り上げてみたいと思います.