12

XML Source:

<documents>
    <document>
        <id>3</id>
    </document>
    <document>
        <id>7</id>
    </document>
    <document>
        <id>1</id>
    </document>
</documents>

I need the document-element with the highest value in its id-element (so <document><id>7</id></document> in the example). I can't change the C# code, it is XMLDocument.SelectSingleNode(...), I only can modify the XPath used.

Is there something like documents/document[id=max(id)] or like order by id descending to get it?

Hinek
  • 9,519
  • 12
  • 52
  • 74

5 Answers5

29
documents/document[not(../document/id > id)]/id
alexkb
  • 3,216
  • 2
  • 30
  • 30
Nick Jones
  • 6,413
  • 2
  • 18
  • 18
  • 2
    +1 For shortest and correct XPath 1.0 maximum expression, besides cuadratic complexity... –  Sep 24 '10 at 12:13
  • thanks, great solution, you should get bonus points for this one! – Hinek Sep 24 '10 at 12:35
18

Besides Nick Jones XPath 1.0 correct answer, in XPath 2.0:

/documents/document[id = max(../document/id)]
2

You can use the preceding and following XPath axes to check whether there is no greater value:

XmlDocument doc = new XmlDocument();
doc.LoadXml("<documents>"
    + "  <document><id>3</id></document>"
    + "  <document><id>7</id></document>"
    + "  <document><id>1</id></document>"
    + "</documents>");

var max = doc.SelectSingleNode(
    "/documents/document[not(id < preceding::document/id)
                         and not(id < following::document/id)]");

If there are several maximum id values in the document the above code will return the first one. If you want the last element with a maximum id you can use

var max = doc.SelectSingleNode(
    "/documents/document[not(id < preceding::document/id) 
                         and not(id <= following::document/id)]");

If you want to get a list with all elements having a maximum id you can use the SelectNodes method:

var maxList = doc.SelectNodes(
    "/documents/document[not(id < preceding::document/id) 
                         and not(id < following::document/id)]");

XSLT version:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes"/>

    <xsl:template match="/">
      <root>
        <xsl:value-of 
             select="/documents/document/id[not(. &lt;= preceding::document/id) 
                     and not(. &lt;= following::document/id)]"/>
      </root>
    </xsl:template>
</xsl:stylesheet>
Dirk Vollmar
  • 172,527
  • 53
  • 255
  • 316
  • Would this not just give you the first one it came accross that was higher than the one before and the one after it? – jimplode Sep 24 '10 at 11:33
1

Have a gander at this link. It uses a function math:max().

There is a link for source for that function and explains how it can be implemented

http://www.exslt.org/math/functions/max/index.html

Hope this helps you

jimplode
  • 3,474
  • 3
  • 24
  • 42
  • And after implementing this you can do something like this: /documents/document/id[number(child::text()) < max(number(child::text()))] – Restuta Sep 24 '10 at 11:25
  • As far as I see, it is supported by several XSLT processors, but not by .NET ... am I missing something? – Hinek Sep 24 '10 at 11:41
  • @Restuts, I need the document node, shouldn't it be more like: /documents/document[number(id/child::text()) = math:max(number(id/child::text()))] – Hinek Sep 24 '10 at 11:45
0

Unfortunately, neither XPath 1.0 or XPath 2.0 define a sort() function. However, most implementations have a sort. For example, <xsl:sort> is available in XSLT implementations, and DOM4J has a sort by XPath method. I'm sure there is something similar in C# but you would have to change the C# code a bit.

Mark Thomas
  • 37,131
  • 11
  • 74
  • 101