xpathxslt-3.0

getting distinct values from a sequence of maps


I want to take a sequence of maps and find distinct values, the obvious thing to do is.

    <xsl:variable name="sequence" select="
                  ( map { 'foo' : 1, 'bar' : () },
                    map { 'foo' : 1, 'bar' : '3' },
                    map { 'foo' : 1, 'bar' : '3' } )" as="map(xs:string,item()*)*"/>
    <xsl:variable name="distinct" select="distinct-values($sequence)"/>

but this raises the warning

Error in xsl:variable/@select on line 29 column 76 of PurchasableSeasonOG.content.xsl: FOTY0013 An atomic value is required for the first argument of fn:distinct-values(), but the supplied type is a map type, which cannot be atomized

i.e. maps aren't atomic values, fair enough.

I'm expecting the answer to be

( map { 'foo' : 1, 'bar' : () },
  map { 'foo' : 1, 'bar' : '3' } )

but how would you do this?

P.S.

I can see how to do this with hard coded for-each-group, or even construct a recursive function to do this via for-each-group, though I'm hoping there is something in the XPath toolbox to do it, and I'm struggling to group by a key that can be an empty sequence.


Solution

  • deep-equal works on maps so one way would be

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      version="3.0"
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      exclude-result-prefixes="#all">
      
      <xsl:variable name="sequence" select="
                      ( map { 'foo' : 1, 'bar' : () },
                        map { 'foo' : 1, 'bar' : '3' },
                        map { 'foo' : 1, 'bar' : '3' } )" as="map(xs:string,item()*)*"/>
    
      <xsl:output method="adaptive"/>
    
      <xsl:template match="/" name="xsl:initial-template">
        <xsl:sequence 
          select="fold-left($sequence, (), function($a, $m) { $a, $m[not(some $m2 in $a satisfies deep-equal($m, $m2))] })"/>
      </xsl:template>
    
    </xsl:stylesheet>
    

    Example fiddle.