「HTML鳩丸倶楽部」更新履歴の2001-10-03で提示され、agendaのカレントノードについて(XSLT)で言及されている問題について、別解を求めるべく挑戦してみました。一応問題を書いておくと、
<?xml version="1.0"?>
<document>
<foo>1</foo>
<foo>2</foo>
<foo>3</foo>
<foo>4</foo>
<foo>5</foo>
</document>
という文書を、
<?xml version="1.0"?>
<document>
<bar>
<foo>1</foo>
<foo>2</foo>
</bar>
<bar>
<foo>3</foo>
<foo>4</foo>
</bar>
<bar>
<foo>5</foo>
</bar>
</document>
とするにはどうすればよいか、というものです。
最初にXSLTスタイルシートを作った時は「やはりfor-eachを使うべきだろうか」と思ってそのようにしてみたのですが、できてみるとjintrickさん作成のものと殆ど同じになってしまったので、どうにかして何とか別のやり方ができないかと頭を捻ったところ、以下のスタイルシートができました。
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:param name="group" select="2"/>
<xsl:template match="/document">
<document>
<xsl:apply-templates select="
foo[(position() mod $group)=1 or $group=1]
"/>
</document>
</xsl:template>
<xsl:template match="foo">
<bar>
<xsl:copy-of select="
. | following-sibling::foo[position()<$group]
"/>
</bar>
</xsl:template>
</xsl:stylesheet>
つまりapply-template要素ではfoo要素の中で1, 3, 5, ... 番目のものだけにテンプレートを適用するようにして、テンプレートでは「カレントノードと、その後に続くfoo要素のうちカレントノートから数えて2番目未満のもの」をコピーしてbar要素の中に入れる、という方法。言わば諸々の条件分岐をすべてXPath式(select属性)に押し込んだ形であり、書いているうちはあまり深く考えていなかったのですが、実際できてみると「XPathって便利だなあ」と実感したものでした。
「幾つずつグループ化するか」というのはgroupパラメタで指定しているので、外から2以外の数を指定することもできます。例えばxsltprocを使ってgroupパラメタに3を渡すと、このように。
$ xsltproc --param group 3 [XSL file] [input XML file] <?xml version="1.0"?> <document> <bar> <foo>1</foo> <foo>2</foo> <foo>3</foo> </bar> <bar> <foo>4</foo> <foo>5</foo> </bar> </document>
一応 group=1 でも動作するようになってます。apply-templates要素のselect属性に or $group=1
という条件が入っているのはそのためで、本来の目標からすると無くてもよい部分だったり。
for-each要素を使わずにapply-templates要素とtemplate要素でなんとかできてみると、そこはかとない満足感を覚えるのは自分だけでしょうか。
(2004年5月4日)
北村曉 kits@akatsukinishisu.net