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>
<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>
<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>
<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>
<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があります.また機会があったら取り上げてみたいと思います.