The standard approach to this kind of problem in XSLT 1.0 is called Muenchian grouping. You define a key that groups your target elements in the way you want
<xsl:key name="bsById" match="b" use="@id" />
then use a trick with generate-id
to extract just the first node in each group as a proxy for the group as a whole
<xsl:apply-templates select="b[generate-id()
= generate-id(key('bsById', @id)[1])]"
mode="group">
<xsl:sort select="@id" />
</xsl:apply-templates>
So now the following template would fire once per group, and you can use the key
function within it to get all the nodes in the group
<xsl:template match="b" mode="group">
<table>
<!-- extract all the nodes that are grouped with this one -->
<xsl:apply-templates select="key('bsById', @id)">
<!-- you could <xsl:sort> here if you want to sort within groups -->
</xsl:apply-templates>
</table>
</xsl:template>
<xsl:template match="b">
<tr><td>...</td></tr>
</xsl:template>
All the above is fine if that example is your entire XML document, but if there's more than one a
element within the document each with its own set of b
elements that need grouping independently, then the key needs to be more complex. The usual trick here is to use the generate-id
of the parent a
node as part of the grouping key value for its b
children:
<xsl:key name="bsByParentAndId" match="a/b" use="concat(generate-id(..), '|', @id)" />
and for the Muenchian grouping expression
<xsl:template match="a">
<xsl:apply-templates select="b[generate-id()
= generate-id(key('bsByParentAndId', concat(
generate-id(current()), '|', @id))[1])]"
mode="group"/>
</xsl:template>
For the record, if you could use XSLT 2.0 then it becomes significantly easier. No need to define a complex key, you simply use for-each-group
<xsl:template match="a">
<xsl:for-each-group select="b" group-by="@id">
<xsl:sort select="current-grouping-key()" />
<table>
<xsl:apply-templates select="current-group()" />
</table>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="b">
<tr><td>...</td></tr>
</xsl:template>