toppless
toppless

Reputation: 411

XSLT select distinct values using attributes

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

Upvotes: 9

Views: 26696

Answers (2)

user619271
user619271

Reputation: 5022

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.

Upvotes: 4

Martin Honnen
Martin Honnen

Reputation: 167716

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>

Upvotes: 19

Related Questions