Given the following XML document
<root>
<a pos="0" total="2"/>
<a pos="1" total="2"/>
<a pos="0" total="3"/>
<a pos="1" total="3"/>
<a pos="2" total="3"/>
<a pos="0" total="4"/>
<a pos="1" total="4"/>
<a pos="2" total="4"/>
<a pos="3" total="4"/>
</root>
I need to translate it to
<root>
<group>
<a pos="0" total="2"/>
<a pos="1" total="2"/>
</group>
<group>
<a pos="0" total="3"/>
<a pos="1" total="3"/>
<a pos="2" total="3"/>
</group>
<group>
<a pos="0" total="4"/>
<a pos="1" total="4"/>
<a pos="2" total="4"/>
<a pos="3" total="4"/>
</group>
</root>
using an XSLT 1.0 stylesheet.
That is, each <a>
element with the @pos
attribute of 0
in the document
implicitly starts a group consisting of it and the @total
-1 following <a>
elements. To explain this in other words, @pos
denotes a 0-based index (position) of the element in the group of @total
adjacent elements.
I have come up with the following stylesheet, which works:
<?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" indent="yes" />
<xsl:template match="/">
<xsl:apply-templates select="root" />
</xsl:template>
<xsl:template match="root">
<xsl:apply-templates select="a[@pos=0]" mode="leader"/>
</xsl:template>
<xsl:template match="a" mode="leader">
<group>
<xsl:apply-templates select="." />
<xsl:apply-templates select="following-sibling::a[position() <= current()/@total - 1]" />
</group>
</xsl:template>
<xsl:template match="a">
<xsl:copy-of select="." />
</xsl:template>
</xsl:stylesheet>
The problem I have with my solution is that it makes those a[@pos=0]
elements "special": to further process each <a>
element in a prospective group, I have to separately apply the appropriate template first to the "group leader" element and then to the rest of the elements in the group.
In other words I would very much like to have something like (incorrect)
<xsl:template match="a" mode="leader">
<group>
<xsl:apply-templates select=". and following-sibling::a[position() <= current()/@total - 1]" />
</group>
</xsl:template>
which would apply my <xsl:template match="a">
template to all the elements in the group in one go. (To rephrase what I've attempted to spell in the select
expression: "select the context element and its following sibling elements matching …".)
Is there a way to have what I want with XSLT 1.0 without resorting to hacks like variables and exslt:node-set()
? May be there's a better way to do such grouping based on element counts than the one I have come up with (which inherently makes the first element in each group special)?
I admit the question's title is rather weak but I failed to come up with a succint one which correctly reflect the essense of my question.