32

If I have XML like:

<foo>
  <bar id="1" score="192" />
  <bar id="2" score="227" />
  <bar id="3" score="105" />
  ...
</foo>

Can I use XPath to find the minimum and maximum values of score?

Edit: The tool i'm using (Andariel ant tasks) doesn't support the XPath 2.0 solution.

GameFreak
  • 2,881
  • 7
  • 34
  • 38
brasskazoo
  • 76,030
  • 23
  • 64
  • 76

6 Answers6

44

Here's a slightly shorter solution.

Maximum:

/foo/bar/@score[not(. < ../../bar/@score)][1]

Minimum:

/foo/bar/@score[not(. > ../../bar/@score)][1]

I've edited the predicate so that it's applicable to any sequence of bar, even if you decide to change the path. Note that parent of attribute is the element to which it belongs.

If embedding these queries in XML files like XSLT or ant scripts, remember to encode < and > as &lt; respecting &gt;.

Jens Erat
  • 37,523
  • 16
  • 80
  • 96
Pavel Minaev
  • 99,783
  • 25
  • 219
  • 289
  • Cool...but I can't guarantee that the there won't be (for example) multiple maximum values of `score`. This would list a value for each element that contained the maximum.. – brasskazoo Jul 16 '09 at 03:35
  • Probably you want to use `number(.)` because otherwise it compares string values of attributes. Am I right? – DanSkeel Sep 11 '14 at 13:55
  • You shouldn't need that, since relative comparison operators will automatically apply it to both sides as needed. It's only `==` and `!=` which will default to string comparisons unless one operand is a number. – Pavel Minaev Sep 11 '14 at 20:08
  • nice XPath 1.0 solution! I run it with the example in the question, appending the element with score 227 at the end and it gave me a duplicate result for the maximum -> /foo/bar/@score[not(. < ../../bar/@score)][1] -> would it be possible that you take another look? – udo Aug 20 '15 at 09:06
20

Turns out the tool does not support XPath 2.0.

XPath 1.0 doesn't have the fancy min() and max() functions, so to find these values we need to be a little tricky with the XPath logic, and compare the values on the siblings of the node:

Maximum:

/foo/bar[not(preceding-sibling::bar/@score >= @score) 
    and not(following-sibling::bar/@score > @score)]/@score

Minimum:

/foo/bar[not(preceding-sibling::bar/@score <= @score) 
    and not(following-sibling::bar/@score < @score)]/@score

If embedding these queries in XML files like XSLT or ant scripts, remember to encode < and > as &lt; respecting &gt;.

Jens Erat
  • 37,523
  • 16
  • 80
  • 96
brasskazoo
  • 76,030
  • 23
  • 64
  • 76
  • nice job! I tried the top ranked solution first but that one does not exclude duplicate maxima/minima. – udo Aug 20 '15 at 09:16
6

This should work ...

max(foo/bar/@score)

... and ...

min(foo/bar/@score)

... check out this function reference.

JP Alioto
  • 44,864
  • 6
  • 88
  • 112
5

I stumbled upon the thread and didn't find an answer that worked for me, so where is what I eventually ended up using...

Outputs the lowest value, of course you could choose to output the @id from the node with the lowest value instead of you choose.

<xsl:for-each select="/foo">
  <xsl:sort select="@score"/>
  <xsl:if test="position()=1">
    <xsl:value-of select="@score"/>
  </xsl:if>
</xsl:for-each>

The same for the maximum value:

<xsl:for-each select="/foo">
  <xsl:sort select="@score" order="descending"/>
  <xsl:if test="position()=1">
    <xsl:value-of select="@score"/>
  </xsl:if>
</xsl:for-each>
Graham Lower
  • 71
  • 1
  • 2
3

Try this:

//foo/bar[not(preceding-sibling::bar/@score <= @score) and not(following-sibling::bar/@score <= @score)]

Maybe this will work on XPath 1.0.

kuy
  • 944
  • 5
  • 12
3

I know this is five years old. Just adding some more options for whoever may search and come across this.

Something similar to this worked for me in XSLT 2.0.

min(//bar[@score !='']/@score)

The !='' was to avoid a nulls which brought up NaN values (there's probably a better way to do that)

Here is a working xpath/xquery:

//bar/@score[@score=min(//*[@score !='']/number(@score))]
Paulb
  • 1,471
  • 2
  • 16
  • 39