1

My intent is in an XSL, to display only once the text node for set of nodes with a common value. For example, I have the following XML:

<Nodes>
    <Node att="1">A</Node>
    <Node att="1">B</Node>
    <Node att="2">C</Node>
    <Node att="2">D</Node>
    <Node att="3">E</Node>
</Nodes>

My output would be: "ACE".

I don't know what the values of the attribute "att" will be. It can be any string.

Any suggestion would be greatly appreciated!

Olyan
  • 105
  • 2
  • 6
  • Here's a very similar question, with lots of answers: http://stackoverflow.com/questions/399204/xslt-distinct-elements-and-grouping – Rob Fonseca-Ensor Dec 10 '10 at 23:03
  • The "ACE" output will be for the first one (document order) for each disctinct value. Please do clarify. –  Dec 10 '10 at 23:05
  • Good question, +1. See my answer for possibly the simplest and shortest solution -- an XPath one-liner. :) – Dimitre Novatchev Dec 11 '10 at 00:10

3 Answers3

1

This can even be done just in a single XPath expression:

/*/*[not(@att=preceding-sibling::*/@att)]/text()

So, wrapping it in XSLT gives us:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

 <xsl:template match="/">
  <xsl:copy-of select="/*/*[not(@att=preceding-sibling::*/@att)]/text()"/>
 </xsl:template>
</xsl:stylesheet>

and applying this to the provided XML document:

<Nodes>
    <Node att="1">A</Node>
    <Node att="1">B</Node>
    <Node att="2">C</Node>
    <Node att="2">D</Node>
    <Node att="3">E</Node>
</Nodes>

produces the wanted, correct result:

ACE
Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • I really like your answer, but just to be certain, are the nodes need to be sorted for it to work? – Olyan Dec 13 '10 at 14:39
  • @Olan: No, no sorting is assumed/needed. The XPath expression selects the first node (in document order) that has a specific, distinct value. – Dimitre Novatchev Dec 13 '10 at 16:34
0

It's a FAQ. See http://www.jenitennison.com/xslt/grouping/muenchian.html

This XSLT code:

<?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" indent="yes" encoding="UTF-8"/>

<xsl:key name="criteria" match="/Nodes/Node" use="@att"/>

<xsl:template match="Nodes">   
    <xsl:copy>
        <xsl:apply-templates select="Node[generate-id() = generate-id(key('criteria', @att))]"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="Node">
    <xsl:copy-of select="."/> <!-- Or other actions -->
</xsl:template>

</xsl:stylesheet>

Will provide the desired (if I understood correctly) output:

<?xml version="1.0" encoding="UTF-8"?>
<Nodes>
   <Node att="1">A</Node>
   <Node att="2">C</Node>
   <Node att="3">E</Node>
</Nodes>

It would also work with input like, for example:

<Nodes>
    <Node att="someRandomString">A</Node>
    <Node att="1aeawe">B</Node>
    <Node att="someRandomString">C</Node>
    <Node att="sfdf">D</Node>
    <Node att="">E</Node>
    <Node att="sfdf">F</Node>
</Nodes>

Output will be:

<?xml version="1.0" encoding="UTF-8"?>
<Nodes>
    <Node att="someRandomString">A</Node>
    <Node att="1aeawe">B</Node>
    <Node att="sfdf">D</Node>
    <Node att="">E</Node>
</Nodes>
Flack
  • 5,862
  • 2
  • 23
  • 27
0

This little stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>
    <xsl:key name="kNodeByAtt" match="Node" use="@att"/>
    <xsl:template match="Node[count(.|key('kNodeByAtt',@att)[1])!=1]"/>
</xsl:stylesheet>

Output:

ACE

Edit: Just for fun XPath 2.0 solution

string-join((/Nodes/Node)[index-of(/Nodes/Node/@att,@att)[1]],'')

Result:

ACE