6

I am looking for a Javascript solution to this problem:

I have an XML document which is loaded by way of a typical AJAX method:

var xml = http.responseXML;

Consider the following example xml snippet:

<main>
    <primary>
         <enabled>true</enabled>
     </primary>
     <secondary>
         <enabled>true</enabled>
     </secondary>
 </main>

I would like to get the value of a node by specifying the path as follows:

var second_enabled = getNodeValueByPath('main/secondary/enabled', xml);

I cannot find any concise way of doing something like this. I seem to be forced to iterate through node collections after using getElementsByTagName and the like.

How would I construct the method getNodeValueByPath or is there some construct in Javascript that allows for this?

I am not well versed in Javascript.

EDIT: Here is an example that shows my attempt at using XPath and how it is failing:

XML:

<?xml version="1.0" ?>
<main xmlns="example.com">
  <primary>
    <enabled>true</enabled>
  </primary>
  <secondary>
    <enabled>false</enabled>
  </secondary>
</main>

JavaScript: (these are the relevant functions only)

function useHttpResponse()
{
    if (http.readyState == 4)
    {
        if(http.status == 200)
        {
            var xml = http.responseXML;
            var evalue = getXMLValueByPath('/main/secondary/enabled', xml);
            alert(evalue);
        }
    }
}

function getXMLValueByPath(nodepath, xml)
{
    var result = xml.evaluate(nodepath, xml, null, XPathResult.STRING_TYPE, null).stringValue;
    return result;
}

I am using the above JavaScript without any additional libraries. I am using Mozilla Firefox 3.6.13 which uses the document.evaluate method to select nodes (according to this information from w3schools). This application is for internal use and does not have to work on multiple browsers.

Given these examples, the alert dialog will appear, but contain no text. If I remove the string xmlns="example.com" from the XML document, the alert dialog appears with the text "false" as desired.

Debugging using Firebug shows that resultNode is an empty string as long as the namespace is declared.

Developer
  • 231
  • 4
  • 19
JYelton
  • 35,664
  • 27
  • 132
  • 191
  • Based on answers so far, also looking at: http://stackoverflow.com/questions/3739588/using-xpathresult – JYelton Dec 21 '10 at 01:37
  • Note that the issue is affected by XML namespaces. In the sample XML of my question, the namespace declaration is not present. See (http://stackoverflow.com/questions/4505103/how-to-remove-xml-namespaces-using-javascript) for related information. – JYelton Dec 24 '10 at 00:28
  • Check my answer for an XPath 1.0 explanation plus an example for your specific XPath engine. –  Dec 27 '10 at 21:45
  • If you don't mind "CSS selectors" instead of XPath, you may be able to get away with "just" JQuery. –  Dec 27 '10 at 21:48

4 Answers4

5

This is FAQ: In XPath 1.0, a QName test without prefix selects elements under the null (or empty) namespace URI. So you need to register a prefix to the default namespace URI from your input source (example.com).

How do you do that with DOM 3 XPath API?

From http://www.w3.org/TR/DOM-Level-3-XPath/xpath.html#XPathEvaluator-evaluate

evaluate
Evaluates an XPath expression string and returns a result of the specified type if possible.
Parameters
[...]
resolver of type XPathNSResolver
The resolver permits translation of all prefixes, including the xml namespace prefix, within the XPath expression into appropriate namespace URIs. If this is specified as null, any namespace prefix within the expression will result in DOMException being thrown with the code NAMESPACE_ERR.

And from https://developer.mozilla.org/en/DOM/document.evaluate

var xpathResult = document.evaluate(
 xpathExpression, 
 contextNode, 
 namespaceResolver, 
 resultType, 
 result);
  • namespaceResolver is a function that will be passed any namespace prefixes and should return a string representing the namespace URI associated with that prefix. It will be used to resolve prefixes within the XPath itself, so that they can be matched with the document. null is common for HTML documents or when no namespace prefixes are used.

Following this guide, you can use document.createNSResolver or a function like:

function nsResolver(prefix) {   
  return prefix == 'ex' ? 'example.com' : null;   
}  

function useHttpResponse()
{
  if (http.readyState == 4)
  {
     if (http.status == 200)
     {
        var xml = http.responseXML;
        var evalue = xml.evaluate('string(/ex:main/ex:secondary/ex:enabled)',
                                  xml,
                                  nsResolver,
                                  XPathResult.STRING_TYPE,
                                  null);
        alert(evalue.stringValue);
     }
  }
}     
  • Alejandro, this is *exactly* what I was needing all along. Thank you for putting the puzzle together. – JYelton Dec 27 '10 at 23:14
  • @JYelton: You are wellcome. I'm glad it could help you. About last `useHttpResponse()` JavaScript function: I've just used your example, but remember that in real scenario you will have to iterate over a node set result from `evaluate()` JavaScript function. –  Dec 27 '10 at 23:23
1

I've been playing around with the same thing, I tried a few XPath JS libraries, here's one: http://js-xpath.sourceforge.net/, but having said that, I couldn't get stuff working so I'll be watching this question avidly :)

Also, at SO: Cross-browser XPath implementation in JavaScript

Community
  • 1
  • 1
danjah
  • 2,939
  • 2
  • 30
  • 47
  • I hope I didn't put you off that 1st link, I gave up because I only had 45 mins to implement it, so I moved on to other things intending fully to come back later and leverage that library. – danjah Dec 21 '10 at 01:49
1
var xpathtosearchfor = '//div';
var result = new XPathEvaluator().evaluate(xpathtosearchfor,
    document.documentElement, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);

In firefox at least

qwertymk
  • 34,200
  • 28
  • 121
  • 184
  • The Xpath lib in my answer normalises Xpath 1.0 - touting IE/Mozilla consistency. IE doesn't 'do' evaluate(), but does do selectNodes(). – danjah Dec 21 '10 at 01:31
  • This returns an `XPathResult` object rather than a value. Using the `XPathResult` property `.stringValue` doesn't seem to help. – JYelton Dec 21 '10 at 01:46
  • Upon more investigating, this works when I remove the namespace declaration from the XML. I haven't yet figured out how to get the namespace specified in the XPath; I'd prefer to just remove the namespace entirely from the XML prior to parsing. – JYelton Dec 22 '10 at 00:01
  • Can you please update your question to say which XPath library you are using, show the code, show the actual XML (you posted XML has no namespace declarations), and show what the result is when you do not remove the namespaces. – John Saunders Dec 24 '10 at 00:35
  • @John Saunders: Added code and error information as requested. – JYelton Dec 24 '10 at 18:09
-2
XmlDocument xDoc = new XmlDocument();
xDoc.Load("d:\\one.xml");
XmlNode xNode = xDoc.SelectSingleNode(@"//main/secondary/enabled");
string result = ((XmlElement)xNode).FirstChild.Value;
abatishchev
  • 98,240
  • 88
  • 296
  • 433
Rajesh Kumar G
  • 1,424
  • 5
  • 18
  • 30
  • using JavaScript, not C#, I guess – abatishchev Dec 21 '10 at 09:33
  • -1: Getting a specific node value by path is very easy in C# (using `SelectSingleNode` and that nodes' `InnerText` property (for strings)). My question is for Javascript, which is a good deal more frustrating. – JYelton Dec 21 '10 at 17:06