Search code examples
xmlxsltselectdistinct-values

XSL How to select multiple values from one distinct value


I wanted to extract the unique values with a function like select distinct. Eventually I ended up using the xsl:key combination with for-each. I've managed to get the unique value of model, but I can't extract the style that belongs to that model.

I've tried to put together a sample XML.

    <PLAYER>
     <ALBUM>
      <MODEL>AA</MODEL>
      <STYLE>BB</STYLE>
      <NUMBER>1</NUMBER>
     </ALBUM>
     <ALBUM>
      <MODEL>FF</MODEL>
      <STYLE>GG</STYLE>
      <NUMBER>2</NUMBER>
     </ALBUM>
     <ALBUM>
      <MODEL>AA</MODEL>
      <STYLE>BB</STYLE>
      <NUMBER>3</NUMBER>
     </ALBUM>
     <ALBUM>
      <MODEL>FF</MODEL>
      <STYLE>GG</STYLE>
      <NUMBER>4</NUMBER>
     </ALBUM>
     <ALBUM>
      <MODEL>HH</MODEL>
      <STYLE>JJ</STYLE>
      <NUMBER>5</NUMBER>
     </ALBUM>
    </PLAYER>

I need to extract the unique value of MODEL with it's matching STYLE.

    <?xml version="1.0" encoding="UTF-8"?>
     <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL Transform">
      <xsl:output method="xml" encoding="UTF-8" indent="yes"/>

      <xsl:key name="model" match="/player/album/model/text()" use="." />

      <xsl:template match="/">

       <xsl:for-each select="/player/album/model/text()[generate-id()
                                   = generate-id(key('model',.)[1])]">
      <li>
        <xsl:value-of select="model"/>
      </li>
      <li>
        <xsl:value-of select="style"/>
      </li>

     </xsl:for-each>
    </xsl:template>
   </xsl:stylesheet>

Wanted result:

      <MODEL>AA</MODEL>
      <STYLE>BB</STYLE>

      <MODEL>FF</MODEL>
      <STYLE>GG</STYLE>

      <MODEL>HH</MODEL>
      <STYLE>JJ</STYLE>

Is it possible to extract these unique value-pairs?


Solution

  • XML (and indeed XSLT) is case-sensitive, so player in the xpath in your XSLT will not match PLAYER in your XML (and similar for the other nodes).

    Additionally, your xsl:for-each is selecting text nodes, so doing <xsl:value-of select="model"/> is looking for a child node, called model of that text node, which obviously doesn't exist.

    You could change the expression to <xsl:value-of select="../../MODEL"/>, but it might be better to simply change the key to match ALBUM nodes instead.

    Try this XSLT....

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" encoding="UTF-8" indent="yes"/>
    
    <xsl:key name="model" match="/PLAYER/ALBUM" use="MODEL" />
    
    <xsl:template match="/">
      <xsl:for-each select="/PLAYER/ALBUM[generate-id() = generate-id(key('model',MODEL)[1])]">
        <li>
          <xsl:value-of select="MODEL"/>
        </li>
        <li>
          <xsl:value-of select="STYLE"/>
        </li>
      </xsl:for-each>
    </xsl:template>
    </xsl:stylesheet>
    

    I am not quite sure if you what your exact wanted result is, as the question shows <MODEL>AA</MODEL><STYLE>BB</STYLE> as the wanted result, but the XSLT is outputting LI tags instead, but it is easy enough to adapt.