Search code examples
xsltxslt-2.0

Is order of iteration by xsl for-each-group consistent?


Is the order of iteration in for-each-group predictable with the same data? or could it change if the same loop is executed twice, for example I have this loop repeated twice in separate parts of a template:

<xsl:for-each-group
            select="descendant::FormSectionElements[not(LoadBindBase = '')]" 
            group-by="LoadBindBase">

... first ...

</xsl:for-each-group>


<xsl:for-each-group
            select="descendant::FormSectionElements[not(LoadBindBase = '')]" 
            group-by="LoadBindBase">

... second ...

</xsl:for-each-group>

Solution

  • The default order for processing groups is "order of first appearance": if the first appearance of "London" as a grouping key precedes the first appearance of "Paris", then the group with key "London" is processed before the group with key "Paris". You can change the order using an xsl:sort as a child of xsl:for-each-group. In both cases the order is not only stable between multiple uses of the instruction, but predictable and interoperable between different implementations.

    I'm not sure I would agree with @michael.hor257k that it's a bad idea to do the same grouping twice. Firstly, until you move to XSLT 3.0 with maps, there's no convenient data structure for holding the groups. Secondly, there's a memory-time trade-off here so it depends on your circumstances.

    The way to do this with maps in 3.0 would be:

    <xsl:variable name="groups" as="map(xs:string, node()*)">
      <xsl:map>
        <xsl:for-each-group
             select="descendant::FormSectionElements[not(LoadBindBase = '')]">
          <xsl:map-entry key="current-grouping-key()" select="current-group()"/>
        </xsl:for-each-group>
      </xsl:map>
    </xsl:variable>
    

    and then you can iterate over the groups using, for example:

    <xsl:for-each select="map:keys($groups)">
      <group key="{.}">
        <xsl:apply-templates select="$groups(.)" mode="xxx"/>
      </group>
    </xsl:for-each>
    

    The order of processing of map:keys() is unpredictable -- but it should be consistent across multiple calls.