Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to select the first occurrence of a node using xslt

Tags:

xslt

xpath

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

like image 316
D.Q. Avatar asked Mar 28 '13 22:03

D.Q.


People also ask

How do I select a substring in XSLT?

XSLT doesn't have any new function to search Strings in a reverse manner. We have substring function which creates two fields substring-before-last and substring-after-last.In XSLT it is defined as follows: <xsl:value-of select="substring (string name ,0, MAX_LENGTH )"/>...

What does node () mean in XSLT?

1. Well, more precisely, node() means child::node(), and @* means attribute::*, so it is matching all children and attributes of the context node. (It doesn't match document nodes or namespace nodes).

What is Number () in XSLT?

If the argument is a boolean value, the value true is converted to the number 1 ; the value false is converted to the number 0 . If the argument is a node-set, the node-set is converted to a string as if it were passed to the string() function, then that string is converted to a number like any other string.

What is current group () in XSLT?

Returns the contents of the current group selected by xsl:for-each-group. Available in XSLT 2.0 and later versions. Available in all Saxon editions. current-group() ➔ item()*


2 Answers

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
like image 174
Tim C Avatar answered Sep 22 '22 21:09

Tim C


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.

like image 29
Dimitre Novatchev Avatar answered Sep 22 '22 21:09

Dimitre Novatchev