D.Q.
D.Q.

Reputation: 547

How to select the first occurrence of a node using xslt

I have a xml which is like:

<bookstores>
    <bookstore>
        <book id="1">
            <author>ABC</author> 
        </book>
        <book id="2">
            <title>YYY</title> 
        </book>
    </bookstore>
    <bookstore>
        <book id="3">
            <author>ABC</author> 
        </book>
        <book id="4">
            <author>DEF</author> 
        </book>
    </bookstore>
    <bookstore>
        <book id="5">
            <price>50</price>
        </book>
        <book id="6">
            <title>ZZZ</title> 
        </book>
    </bookstore>
</bookstores>

I would like to select the first occurrence of the child of 'book' node, or in other words, all unique child node of the 'book' node.

So the output should be like:

author
title
price

I wrote a xslt as:

<xsl:for-each select="bookstores/bookstore/book"> 
    <xsl:if test="count(preceding-sibling::*[1]) = 0">
        <xsl:value-of select="local-name(*[1])"/>
    </xsl:if>
</xsl:for-each>

It returned me nothing...Could anyone give me some help on this? Thanks!!

UPDATE:

What if I have several 'bookstores' elements in my xml, and I just would like to limit the uniqueness within the context of each 'bookstores' so that even 'author' appears in one 'bookstores', it could still be displayed if it appears in another 'bookstores'?

Upvotes: 5

Views: 9808

Answers (3)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243449

Somewhat shorter/simpler -- completely in "push style":

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>
 <xsl:key name="kBChildrenByName" match="book/*" use="name()"/>

 <xsl:template match=
  "book/*[generate-id()=generate-id(key('kBChildrenByName', name())[1])]">
     <xsl:value-of select="concat(name(), '&#xA;')"/>
 </xsl:template>
 <xsl:template match="text()"/>
</xsl:stylesheet>

When this transformation is applied to the provided XML document:

<bookstores>
    <bookstore>
        <book id="1">
            <author>ABC</author>
        </book>
        <book id="2">
            <title>YYY</title>
        </book>
    </bookstore>
    <bookstore>
        <book id="3">
            <author>ABC</author>
        </book>
        <book id="4">
            <author>DEF</author>
        </book>
    </bookstore>
    <bookstore>
        <book id="5">
            <price>50</price>
        </book>
        <book id="6">
            <title>ZZZ</title>
        </book>
    </bookstore>
</bookstores>

the wanted, correct result is produced:

author
title
price

Explanation:

Appropriate use of the Muenchian grouping method.

Upvotes: 3

Tim C
Tim C

Reputation: 70618

If you are using XSLT1.0, the way to get distinct elements is by a technique called Muenchian Grouping. In your case you want to 'group' by book child elements, so to start with, you define a key to look up the child elements of books by the element name

 <xsl:key name="child" match="book/*" use="local-name()" />

To get the distinct names, you then look at all book child elements, but only output the elements that occur first in the group for their given name. You do this using this scary statement:

<xsl:apply-templates 
   select="//book/*[generate-id() = generate-id(key('child', local-name())[1])]" />

Here is the full XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="text"/>
   <xsl:key name="child" match="book/*" use="local-name()" />

   <xsl:template match="/">
      <xsl:apply-templates select="//book/*[generate-id() = generate-id(key('child', local-name())[1])]" />
   </xsl:template>

   <xsl:template match="//book/*">
      <xsl:value-of select="concat(local-name(), '&#10;')" />
   </xsl:template>
</xsl:stylesheet>

When applied to your XML, the following is output

author
title
price

Upvotes: 3

Shelan Perera
Shelan Perera

Reputation: 1753

You can use <xsl:for-each select="//book"> to select occurances

Upvotes: -1

Related Questions