Reputation: 7157
I'm using XSLT to get data out of a feed. Currently I use this block of code, which simply picks the first item out of the feed. I changed it a bit so it applies to this sample XML.
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="/">
<xsl:value-of select="catalog/book/author"/>
</xsl:template>
</xsl:stylesheet>
I want to sort the xml by price, and then pick the author associated with the highest priced book. I've tried all kind of things, but I can't seem to figure it out.
The current output is "Gambardella, Matthew", but I need it to be "Galos, Mike".
Upvotes: 5
Views: 695
Reputation: 338336
I want to sort the xml by price, and then pick the author associated with the highest priced book.
FWIW, you can do that with pure XPath, too.
/catalog/book[not(price < /catalog/book/price)]/author
(The predicate reads: "Select any <book>
whose <price>
is not lower than that of any book.")
This would select <author>Galos, Mike</author>
in the sample XML.
Notes
This expression does not select the highest priced book, but all highest-priced books (i.e., it would select two books if there were two with the same price). Use
/catalog/book[not(price < /catalog/book/price)][1]/author
to select exactly one matching book (the first one in document order would be selected).
XPath automatically coerces both operands of a "less/greater than (or equal to)"-type of comparison to numbers. As long as the values of <price>
are directly convertible to numbers, the above expression will succeed.
It must be the inverse logic ("not(lower than any)"), because the opposite ("greater than any") can never be true (whereas "greater than or equal to any" would always be true).
The time complexity of a nodeset1[expression < nodeset2]
operations is:
→ O(count(nodeset1) × count(nodeset2)).
In the above case nodeset1
and nodeset2
are identical, so the effective time complexity is:
→ O(n²).
In other words, it is not the most efficient way to solve this problem (I would say that <xsl:apply-templates>
with <xsl:sort>
is), but on the other hand - it's a one-liner that might very well be fast enough for you.
Upvotes: 3
Reputation: 6615
you can specify an <xsl:sort>
inside apply-templates, like so:
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates select="/catalog/book">
<xsl:sort select="price" order="descending" data-type="number"/>
</xsl:apply-templates>
</body>
</html>
</xsl:template>
and then in your little 'book' template, use the position()
to filter out only the first book node
<xsl:template match="book">
<xsl:if test="position() = 1">
<xsl:value-of select="author"/>
<br/>
<xsl:value-of select="price"/>
</xsl:if>
</xsl:template>
Upvotes: 1
Reputation: 990
you need and to use the position function to only return only the first one:
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates select="/catalog/book">
<xsl:sort select="price" order="descending" data-type="number"/>
</xsl:apply-templates>
</body>
</html>
</xsl:template>
<xsl:template match="book">
<xsl:if test="position()=first()">
<xsl:value-of select="author"/>
</xsl:if>
</xsl:template>
Upvotes: 0