9

i'm trying to transform a list to a distinct values list using XSLT.

Input:

<object name="obj1"/>
<object name="obj2"/>
<object name="obj1"/>

Desired output:

<object>obj1</object>
<object>obj2</object>

Somebody an idea how to get it done either in XSLT 1.0 or 2.0?

THX

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
toppless
  • 411
  • 1
  • 6
  • 16

2 Answers2

19

Use XSLT 2.0 and

<xsl:for-each select="distinct-values(//object/@name)">
  <object><xsl:value-of select="."/></object>
</xsl:for-each>

or

<xsl:for-each-group select="//object" group-by="@name">
  <object><xsl:value-of select="current-grouping-key()"/></object>
</xsl:for-each-group>
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • 1
    For me its coming this error `'distinct-values()' is an unknown XSLT function.` – shanmugharaj Jun 28 '16 at 19:32
  • 1
    You need to use an XSLT 2 processor to run that code. – Martin Honnen Jun 28 '16 at 19:44
  • Please can you tell me how to do that. I am using c# – shanmugharaj Jun 28 '16 at 19:47
  • I got a link from your [answer](http://stackoverflow.com/questions/27020359/forcing-xslt-to-use-version-2-with-xslcompiledtransform). Thanks! – shanmugharaj Jun 28 '16 at 19:49
  • @MartinHonnen `distinct-values()` returns `xs:anyAtomicType` though. What if I have a function with return type `attribute()*` and I want to return de-duplicated attributes? – Martynas Jusevičius Aug 21 '20 at 09:30
  • 1
    @MartynasJusevičius, I think you should raise that as a separate question, it is not clear if you want to de-duplicate the attributes based on node id or attribute value. But `` for de-duplicating by attribute value should do. For node id using `$attribute-sequence/.` should do. – Martin Honnen Aug 21 '20 at 10:39
3

For XSLT 1.0

objects.xml:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="objects.xsl"?>
<objects>
  <object id="id1" name="obj1"/>
  <object id="id2" name="obj2"/>
  <object id="id3" name="obj1"/>
</objects>

objects.xsl:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:key name="index1" match="*" use="@name" />

    <xsl:template match="/">
        <objects>
        <xsl:for-each select="//*[generate-id() = generate-id(key('index1',@name)[1])]">
            <object><xsl:value-of select="@name"/></object>
        </xsl:for-each>
        </objects>
    </xsl:template>

</xsl:stylesheet>

What is happening here:

  1. With <xsl:key name="index1" match="*" use="@name" /> you define an index for key() function named index1. It must be outside of xsl:template declaration.
  2. With match="*" you define that it is suitable for all elements.
  3. With use="@name" you define a search criteria for index1.
  4. Now key("index1","obj1") will return an array consists of nodes where attribute @name equals "obj1": [<object name="obj1" id="id1"/>,<object name="obj1" id="id3"/>].
  5. You will need generate-id() function that generates unique ID for a given node.
  6. Called with a parameter, generate-id(<object name="obj1" id="id1"/>) will return something like "id0xfffffffff6ddca80obj1".
  7. Called without parameters, generate-id() will return an ID for a current node.
  8. You start xsl:for-each cycle for all elements //* with condition that generate-id() of current node must be equal to generate-id() of a first node from key('index1',@name) result. Which means it must be the first node itself.
  9. You print current @name value with xsl:value-of. Since it happens only for a first element of key('index1',@name) result, it will be printed only once.
user619271
  • 4,766
  • 5
  • 30
  • 35