tags:

views:

48

answers:

3

When I run this:

XmlDocument xmlResponse = new XmlDocument();
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlResponse.NameTable);
nsmgr.AddNamespace("fn", " http://www.w3.org/2005/xpath-functions");

xmlResponse.LoadXml(
    "<LIST>" + 
        "<ITEM NUMBER='3' TEXT='C'/>" + 
        "<ITEM NUMBER='2' TEXT='B'/>" + 
        "<ITEM NUMBER='1' TEXT='A'/>" + 
    "</LIST>");

XmlNode xmlNode = xmlResponse.SelectSingleNode("//ITEM[fn:max(@NUMBER)]", nsmgr);

I get an exception "XsltContext is needed for this query because of an unknown function." on the final line. I am trying to select the ITEM element with the highest NUMBER attribute. Is this possible using XPATH?

I am using .Net 2.0 and Linq is not an option.

Thanks

+1  A: 

you can use the following mechanism to find the node with the highest value for any given attribute:

XmlNode xmlNode = xmlResponse.SelectSingleNode( "//ITEM[not(preceding-sibling::ITEM/@NUMBER > @NUMBER or following-sibling::ITEM/@NUMBER > @NUMBER)]", nsmgr ); 

In XPath you can implements your own XsltContext and pass it to XPathExpression.SetContext() method. This XstlContext is called each time XPath meats unknown function and this is the way to extend XPath with custom functions and variables.

Bharath K
So it is not possible to use the max function described here?http://www.w3schools.com/Xpath/xpath_functions.asp
ShellShock
XPath supports a subset of available xslt functions. The following link details what is supported: http://www.w3.org/TR/xpath/
Bharath K
+3  A: 
XmlNode xmlNode = xmlResponse.SelectSingleNode("//ITEM[fn:max(@NUMBER)]",

nsmgr);

max() is an XPath 2.0 function. .NET only supports XPath1.0.

In XPath 1.0 you can use:

/*/ITEM[not(@NUMBER > ../@NUMBER)]

Even if .NET had implemented XPath 2.0, the XPath expression from the question does not select the ITEM with the maximum NUMBER attribute. A correct XPath 2.0 expression that uses max() to select this is:

/*/ITEM[xs:integer(@NUMBER) eq max(../@NUMBER/xs:string(.))]

This is because the argument to max() must be the sequence of items in which we need to determine the maximum element. Instead, in the XPath expression from the question:

//ITEM[fn:max(@NUMBER)]

max has only a single argument -- the NUMBER attribute of the context node. Therefore the above is equivalent to:

//ITEM[@NUMBER]

which selects all ITEM elements that have a NUMBER attribute.

Dimitre Novatchev
+1 nice explanation.
Bharath K
+1  A: 

As explained, the Microsoft .NET framework XML APIs like XmlDocument and XPathDocument/XPathNavigator only support XPath 1.0 while the max function you want to use is XPath 2.0. To find an element with a maximum value there is however another solution possible with the Microsoft APIs, namely sorting (for the maximum in descending order) and accessing the first item in sorted order:

        XmlDocument xmlResponse = new XmlDocument();


    xmlResponse.LoadXml(
        "<LIST>" +
            "<ITEM NUMBER='3' TEXT='C'/>" +
            "<ITEM NUMBER='2' TEXT='B'/>" +
            "<ITEM NUMBER='1' TEXT='A'/>" +
        "</LIST>");

    XPathNavigator nav = xmlResponse.CreateNavigator();
    XPathExpression exp = nav.Compile("LIST/ITEM");
    exp.AddSort("@NUMBER", XmlSortOrder.Descending, XmlCaseOrder.None, "", XmlDataType.Number);
    XmlNode item = nav.SelectSingleNode(exp).UnderlyingObject as XmlNode;
    Console.WriteLine(item.OuterXml);
Martin Honnen