sri
sri

Reputation: 71

Remove Duplicate values from the concatenated string in XSLT

I want to remove the duplicate values from the concatenated string.

Input is:

 <?xml version="1.0" encoding="ISO-8859-1"?>    
    <QL>
        <QITEM>
            <SERIAL>123</SERIAL>
            <PROD_NAME>User/Device</PROD_NAME>
        </QITEM>
        <QITEM>
            <SERIAL>123</SERIAL>
            <PROD_NAME>User/Dev</PROD_NAME>
        </QITEM>
        <QITEM>
            <SERIAL>123</SERIAL>
            <PROD_NAME>User/Device</PROD_NAME>
        </QITEM>
        <QITEM>
            <SERIAL>1234</SERIAL>
            <PROD_NAME>45 Mbps</PROD_NAME>
        </QITEM>
    </QL>
     <QL>
        <QITEM>
            <SERIAL>123</SERIAL>
            <PROD_NAME>User/Device</PROD_NAME>
        </QITEM>
        <QITEM>
            <SERIAL>123</SERIAL>
            <PROD_NAME>User/Dev</PROD_NAME>
        </QITEM>
        <QITEM>
            <SERIAL>123</SERIAL>
            <PROD_NAME>User/Device</PROD_NAME>
        </QITEM>
        <QITEM>
            <SERIAL>1234</SERIAL>
            <PROD_NAME>45 Mbps</PROD_NAME>
        </QITEM>
    </QL>

I want to concatenate the values and the output should be like:

<Result>
  <SERIAL>123,1234</SERIAL>
  <PROD_NAME>User/Dev,User/Device,45 Mbps</PROD_NAME>
</Result>
<Result>
  <SERIAL>123,1234</SERIAL>
  <PROD_NAME>User/Dev,User/Device,45 Mbps</PROD_NAME>
</Result>

So far, I have tried to achieve this with the following template:

<xsl:template name="join">
    <xsl:param name="list"/>
    <xsl:param name="separator"/>
    <xsl:for-each select="$list">
      <xsl:value-of select="."/>
      <xsl:if test="position() != last()">
        <xsl:value-of select="$separator"/>
      </xsl:if>
    </xsl:for-each>
</xsl:template>

This is giving the values separated by comma.
But I want to get the unique values.

Upvotes: 0

Views: 1131

Answers (1)

Daniel Haley
Daniel Haley

Reputation: 52858

Unique/distinct values in pure XSLT 1.0 is best achieved by using the Muenchian Method of grouping.

In the example below, we create an xsl:key for each one of the items we want to have a group of.

For example, the key serial is a group of all SERIAL elements using the value of itself as the grouping key.

Then we loop over each group (xsl:for-each) and only select the first occurrence of that group.

Full example...

XML Input

<QL>
    <QITEM>
        <SERIAL>123</SERIAL>
        <PROD_NAME>User/Device</PROD_NAME>
    </QITEM>
    <QITEM>
        <SERIAL>123</SERIAL>
        <PROD_NAME>User/Dev</PROD_NAME>
    </QITEM>
    <QITEM>
        <SERIAL>123</SERIAL>
        <PROD_NAME>User/Device</PROD_NAME>
    </QITEM>
    <QITEM>
        <SERIAL>1234</SERIAL>
        <PROD_NAME>45 Mbps</PROD_NAME>
    </QITEM>
</QL>

XSLT 1.0

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:key name="serial" match="SERIAL" use="."/>
  <xsl:key name="prodname" match="PROD_NAME" use="."/>

  <xsl:template match="/*">
    <Result>
      <SERIAL>
        <xsl:for-each select="QITEM/SERIAL[count(.|key('serial',.)[1])=1]">
          <xsl:if test="position() > 1">,</xsl:if>
          <xsl:value-of select="."/>
        </xsl:for-each>
      </SERIAL>
      <PROD_NAME>
        <xsl:for-each select="QITEM/PROD_NAME[count(.|key('prodname',.)[1])=1]">
          <xsl:if test="position() > 1">,</xsl:if>
          <xsl:value-of select="."/>
        </xsl:for-each>
      </PROD_NAME>
    </Result>
  </xsl:template>

</xsl:stylesheet>

XML Output

<Result>
   <SERIAL>123,1234</SERIAL>
   <PROD_NAME>User/Device,User/Dev,45 Mbps</PROD_NAME>
</Result>

UPDATE

I'm not sure I completely understand your update, but what I think you can do is create a compound key using the generated id of the ancestor QL.

XML Input (Wrapped in doc to make it well-formed.)

<doc>
    <QL>
        <QITEM>
            <SERIAL>123</SERIAL>
            <PROD_NAME>User/Device</PROD_NAME>
        </QITEM>
        <QITEM>
            <SERIAL>123</SERIAL>
            <PROD_NAME>User/Dev</PROD_NAME>
        </QITEM>
        <QITEM>
            <SERIAL>123</SERIAL>
            <PROD_NAME>User/Device</PROD_NAME>
        </QITEM>
        <QITEM>
            <SERIAL>1234</SERIAL>
            <PROD_NAME>45 Mbps</PROD_NAME>
        </QITEM>
    </QL>
    <QL>
        <QITEM>
            <SERIAL>123</SERIAL>
            <PROD_NAME>User/Device</PROD_NAME>
        </QITEM>
        <QITEM>
            <SERIAL>123</SERIAL>
            <PROD_NAME>User/Dev</PROD_NAME>
        </QITEM>
        <QITEM>
            <SERIAL>123</SERIAL>
            <PROD_NAME>User/Device</PROD_NAME>
        </QITEM>
        <QITEM>
            <SERIAL>1234</SERIAL>
            <PROD_NAME>45 Mbps</PROD_NAME>
        </QITEM>
    </QL>
</doc>

XSLT 1.0

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:key name="serial" match="SERIAL" use="concat(generate-id(ancestor::QL),'|',.)"/>
  <xsl:key name="prodname" match="PROD_NAME" use="concat(generate-id(ancestor::QL),'|',.)"/>

  <xsl:template match="QL">
    <Result>
      <SERIAL>        
        <xsl:for-each select="QITEM/SERIAL[count(.|key('serial',concat(generate-id(ancestor::QL),'|',.))[1])=1]">
          <xsl:if test="position() > 1">,</xsl:if>
          <xsl:value-of select="."/>
        </xsl:for-each>
      </SERIAL>
      <PROD_NAME>
        <xsl:for-each select="QITEM/PROD_NAME[count(.|key('prodname',concat(generate-id(ancestor::QL),'|',.))[1])=1]">
          <xsl:if test="position() > 1">,</xsl:if>
          <xsl:value-of select="."/>
        </xsl:for-each>
      </PROD_NAME>
    </Result>
  </xsl:template>

</xsl:stylesheet>

XML Output

<Result>
   <SERIAL>123,1234</SERIAL>
   <PROD_NAME>User/Device,User/Dev,45 Mbps</PROD_NAME>
</Result>
<Result>
   <SERIAL>123,1234</SERIAL>
   <PROD_NAME>User/Device,User/Dev,45 Mbps</PROD_NAME>
</Result>

Upvotes: 1

Related Questions