5

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'?

1
  • Are you using XSLT 1.0 or XSLT 2.0? Commented Mar 28, 2013 at 23:10

3 Answers 3

3

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
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! I tried and got what I want! What's more important is that now I've gained new knowledge regarding Muenchian Grouping. Many thanks!
3

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.

3 Comments

thank you for your answer. Can I ask a silly question? What is the second parameter of the concat() function?
@D.Q., The second parameter in this case is a string that consists of a single character, whose (Unicode) code is the number 10, or hexadecimal A. This is actually the new-line character.
Thank you for your explanation! And thank you for your answer and the link you provided, which is absolutely useful! Since Tim answered my question earlier, I accecpted his answer. I do appreciate your help!!
-1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.