xslt-3.0

How to handle (elegantly) an empty group by expression


consider this code

    <xsl:variable name="sequence" select="
                  ( map { 'foo' : 1, 'bar' : () },
                    map { 'foo' : 1, 'bar' : () },
                    map { 'foo' : 1, 'bar' : '3' } )" as="map(xs:string,item()*)*"/>
    <xsl:variable name="distinct" as="map(xs:string,item()*)*">
        <xsl:for-each-group select="$sequence" group-by="map:get(.,'bar')">
            <xsl:sequence select="map { 'group' : current-grouping-key() }"/>            
        </xsl:for-each-group>
    </xsl:variable>

I want the result to be

 ( map { 'group' : () }
   map { 'group' : 3  } )

but it gives me

 ( map { 'group' : 3  } )

(which I actually find a little odd)

how can you elegantly handle the above in XSLT?

(I've tried wrapping the group expression in an array, but it secretly maps that to a sequence and then discards it as empty, and maps don't work in group-by expressions)


Solution

  • I think this works

        <xsl:variable name="sequence" select="
                      ( map { 'foo' : 1, 'bar' : () },
                        map { 'foo' : 1, 'bar' : () },
                        map { 'foo' : 1, 'bar' : '3' } )" as="map(xs:string,item()*)*"/>
        <xsl:variable name="distinct" as="map(xs:string,item()*)*">
            <xsl:for-each-group select="$sequence" group-by="map:get(.,'bar')" composite="true">
                <xsl:sequence select="map { 'group' : current-grouping-key() }"/>            
            </xsl:for-each-group>
        </xsl:variable>
    

    i.e. if composite is set to true, then () is not thrown away as a key.

    https://www.w3.org/TR/2017/REC-xslt-30-20170608/#xsl-for-each-group

    If composite="yes" is specified, there will be a single grouping key, which will in general be a sequence of zero or more atomic values; otherwise, there will be zero or more grouping keys, each of which will be a single atomic value.