Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to select unique nodes

Tags:

xml

xslt

I found this page describing the Muenchian method, but I think I'm applying it wrong.

Consider that this would return a set of ages:

/doc/class/person/descriptive[(@name='age')]/value

1..2..2..2..3..3..4..7

But I would like a nodeset only one node for each age.

1..2..3..4..7

Each of these seem to return all of the values, instead of unique values:

/doc/class/person/descriptive[(@name='age')][not(value=preceding-sibling::value)]/value
/doc/class/person/descriptive[(@name='age')]/value[not(value=preceding-sibling::value)]

What am I missing?

like image 496
pc1oad1etter Avatar asked Oct 22 '08 22:10

pc1oad1etter


3 Answers

Here's an example:

<root>
    <item type='test'>A</item>
    <item type='test'>B</item>
    <item type='test'>C</item>
    <item type='test'>A</item>
    <item type='other'>A</item>
    <item type='test'>B</item>
    <item type='other'>D</item>
    <item type=''>A</item>
</root>

And the XPath:

//preceding::item/preceding::item[not(.=preceding-sibling::item)]/text()

Results: A B C D

EDIT: As mousio commented this doesn't capture the last item in a list if it's the only time it appears. Taking that and Fëanor's comment into account, here's a better solution:

/root/item[not(.=preceding-sibling::item)]
like image 80
BQ. Avatar answered Oct 10 '22 12:10

BQ.


Here is the Muenchian version of BQ's answer using his data:

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

  <xsl:output indent="yes" method="text"/>
  <xsl:key name="item-by-value" match="item" use="."/>

  <xsl:template match="/">
    <xsl:apply-templates select="/root/item"/>
  </xsl:template>

  <xsl:template match="item">
    <xsl:if test="generate-id() = generate-id(key('item-by-value', normalize-space(.)))">
      <xsl:value-of select="."/>
      <xsl:text>
</xsl:text>
    </xsl:if>
  </xsl:template>

  <xsl:template match="text()">
    <xsl:apply-templates/>
  </xsl:template>
</xsl:stylesheet>

This transform gives

A
B
C
D

  1. The key() lookup above in the template for item returns a nodeset containing all the item elements with the same string value as the context node.
  2. If you apply a function that expects a single node to a nodeset, it will operate on the first node in that nodeset.
  3. All calls to generate-id() are guaranteed to generate the same ID for a given node during a single pass through a document.
  4. Therefore, the test will be true if the context node is the same node as the first one returned by the key() call.
like image 37
ChuckB Avatar answered Oct 10 '22 10:10

ChuckB


For those who still look for a select distinct in XSLT:

With XSLT 2.0, you can use "distinct-values(/doc/class/person/descriptive[(@name='age')]/value)"

like image 5
Grégory Avatar answered Oct 10 '22 11:10

Grégory