Search code examples
xsltcountingdistinct-valuesxslt-grouping

Counting distinct items in XSLT


I have XML like this:

<assessment>
 <variables>
  <variable>
   <attributes>
    <variable_name value="FRED"/>
   </attributes>
  </variable>
 </variables>
 <variables>
  <variable>
   <attributes>
    <variable_name value="MORTIMER"/>
   </attributes>
  </variable>
 </variables>
 <variables>
  <variable>
   <attributes>
    <variable_name value="FRED"/>
   </attributes>
  </variable>
 </variables>
</assessment>

I know that with this XSLT:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
 <xsl:output method="html"/>

 <xsl:template match="assessment">
  <xsl:for-each select=".//variables/variable/attributes/variable_name">
   <xsl:value-of select="@value"/>
   <br/>
  </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

I can output the following:

FRED 
MORTIMER 
FRED

But what I really want to output is this:

FRED: 2 
MORTIMER: 1 

That is, I want to list the distinct elements and how many times each occurs. Note that I want the elements to appear in the order of their first appearance (which might rule out some solutions that use sorting).

How do I do this?


Solution

  • This transformation:

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output method="text"/>
    
     <xsl:key name="kValueByVal" match="variable_name/@value"
      use="."/>
    
     <xsl:template match="/">
      <xsl:for-each select="
       /*/*/variable/attributes/variable_name/@value
                 [generate-id()
                 =
                  generate-id(key('kValueByVal', .)[1])
                 ]
       ">
         <xsl:value-of select=
         "concat(., ' ', count(key('kValueByVal', .)), '&#xA;')"/>
      </xsl:for-each>
     </xsl:template>
    </xsl:stylesheet>
    

    when applied on the provided XML document, produces the wanted, correct result:

    FRED 2
    MORTIMER 1