Why is xsl:for-each-group missing some elements?

Viewed 118

The first for-each-group almost works. It finds everything that I need it to find, except some of the sachbegriff nodes (i.e. it finds 47, but one is missing etc.). When I a do a second pass (another for-each-group) explicitly asking for those nodes, I get them. (Of course, now I have some nodes twice and the order is messed up). Any ideas why I don't get them the first time around?

Tried with Saxon-HE .NET 9.x and 10.2 now.

<xsl:for-each-group select="/museumPlusExport/sammlungsobjekt[@objId eq $id]/*" group-by="string()">
    <xsl:sort select="name()" />
    <xsl:apply-templates select="."/>
</xsl:for-each-group>
<xsl:for-each-group select="/museumPlusExport/sammlungsobjekt[@objId eq $id]/sachbegriff" group-by="string()">
   <xsl:apply-templates select="."/>
</xsl:for-each-group>

SOLUTION/SOLVED based on Martin Honnen's answer/explanation below XSLT 2.0

<xsl:for-each-group select="/museumPlusExport/sammlungsobjekt[@objId eq $id]/*" 
    group-by="concat(name(), '|', string())">
    <xsl:sort select="name()" />
    <xsl:variable name="name" select="name()"/>
    <xsl:apply-templates select="."/>
</xsl:for-each-group>

XSLT 3.0

<xsl:for-each-group select="/museumPlusExport/sammlungsobjekt[@objId eq $id]/*" 
    composite="yes" group-by="node-name(), string()">
    <xsl:sort select="name()" />
    <xsl:variable name="name" select="name()"/>
    <xsl:apply-templates select="."/>
</xsl:for-each-group>
2 Answers

The code

<xsl:for-each-group select="/museumPlusExport/sammlungsobjekt[@objId eq $id]/*" group-by="string()">
    <xsl:sort select="name()" />
    <xsl:apply-templates select="."/>
</xsl:for-each-group>

groups all child elements of /museumPlusExport/sammlungsobjekt[@objId eq $id] by their string value and then pushes the first element in each group with <xsl:apply-templates select="."/> to a matching template. If some sachbegriff elements don't appear that way then because they are grouped together with other elements having the same string value but preceding them.

I can't tell which result you want, whether you want to really group all child elements by the string value but only process the first item in each group further. If you want to push all items in each group through to templates you can use <xsl:apply-templates select="current-group()"/>. But usually, with grouping, people want to wrap and/or accumulate some items, so it doesn't seem likely to me you want that either.

I think it is better you show small, representative samples of input and wanted output, together with the criteria that map the input to the output.

Based on your edit to the question, if you really use XSLT 3, instead of group-by="concat(name(), '|', string())" I think you can use composite="yes" group-by="node-name(), string()" for the grouping.

To exclude the sachbegriff elements from the first grouping, change

/museumPlusExport/sammlungsobjekt[@objId eq $id]/*

to

/museumPlusExport/sammlungsobjekt[@objId eq $id]/*[not(self::sachbegriff)]
Related