Wrox Home  
Search
Professional JavaScript for Web Developers
by Nicholas C. Zakas
April 2005, Paperback


XPath support in Firefox

As you may have guessed, Firefox supports the XPath according to the DOM standard. A DOM Level 3 addition called DOM Level 3 XPath defines interfaces to use for evaluating XPath expressions in the DOM. Unfortunately, this standard is more complicated than Microsoft's fairly straightforward approach.

Although a handful of XPath-related objects exist, the two most important ones are XPathEvaluator and XPathResult. An XPathEvaluator is used to evaluate an XPath expression with a method named, appropriately enough, evaluate().

The evaluate() method takes five arguments: the XPath expression, the context node, a namespace resolver, the type of result to return, and an XPathResult object to fill with the result (usually null). The third argument, the namespace resolver, is necessary only when the XML code uses an XML namespace, and so typically is left as null. The fourth argument, the type of result to return, is one of 10 constants values:

  • XPathResult.ANY_TYPE — Returns the type of data appropriate for the XPath expression
  • XPathResult.ANY_UNORDERED_NODE_TYPE — Returns a node set of matching nodes, although the order may not match the order of the nodes within the document
  • XPathResult.BOOLEAN_TYPE — Returns a Boolean value
  • XPathResult.FIRST_ORDERED_NODE_TYPE — Returns a node set with only one node, which is the first matching node in the document
  • XPathResult.NUMBER_TYPE — Returns a number value
  • XPathResult.ORDERED_NODE_ITERATOR_TYPE — Returns a node set of matching nodes in the order in which they appear in the document. This is the most commonly used result type.
  • XPathResult.ORDERED_NODE_SNAPSHOT_TYPE — Returns a node set snapshot, capturing the nodes outside of the document so that any further document modification doesn't affect the node list. The nodes in the node set are in the same order as they appear in the document.
  • XPathResult.STRING_TYPE — Returns a string value
  • XPathResult.UNORDERED_NODE_ITERATOR_TYPE — Returns a node set of matching nodes, although the order may not match the order of the nodes within the document
  • XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE — Returns a node set snapshot, capturing the nodes outside of the document so that any further document modification doesn't affect the node set. The nodes in the node set are not necessarily in the same order as they appear in the document.

The type of result you specify determines how to retrieve the value of the result. Here's a typical example:

var oEvaluator = new XPathEvaluator();
var oResult = oEvaluator.evaluate("employee/name", 
oXmlDom.documentElement, null, 
	XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
	if (oResult != null) {
    var oElement = oResult.iterateNext();
    while(oElement) {
        alert(oElement.tagName);
        oElement = oResult.iterateNext();
    }
}

This example uses the XPathResult.ORDERED_NODE_ITERATOR_TYPE result, which is the most commonly used result type. If no nodes match the XPath expression, evaluate() returns null; otherwise, it returns an XPathResult object. If the result is a node iterator, whether it be ordered or unordered, you use the iterateNext() method repeatedly to retrieve each matching node in the result. When there are no further matching nodes, iterateNext() returns null. Using a node iterator, it's possible to create a selectNodes() method for Firefox:

Element.prototype.selectNodes = function (sXPath) [
    var oEvaluator = new XPathEvaluator();
    var oResult = oEvaluator.evaluate(sXPath, this, null, 
      XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);    
	  
	var aNodes = new Array;
    
    if (oResult != null) {
        var oElement = oResult.iterateNext();
        while(oElement) {
            aNodes.push(oElement);
            oElement = oResult.iterateNext();
        }
    }
    
    return aNodes;};

The selectNodes() method is added to the Element class to mimic the behavior in IE. When evaluate() is called, it uses the this keyword as the context node (which is also how IE works). Then, a result array (aNodes) is filled with all the matching nodes. You can use this new method like so:

var aNodes = oXmlDom.documentElement.selectNodes("employee/name");
for (var i=0; i < aNodes.length; i++) {
    alert(aNodes[i].xml);
}

If you specify a snapshot result type (either ordered or unordered), you must use the snapshotItem() and snapshotLength() methods, as in the following example:

var oEvaluator = new XPathEvaluator();
var oResult = oEvaluator.evaluate("employee/name", 
	oXmlDom.documentElement, null, 
    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
	if (oResult != null) {
    for (var i=0; i < oResult.snapshotLength; i++) {
        alert(oResult.snapshotItem(i).tagName);
    }
}

In this example, snapshotLength returns the number of nodes in the snapshot and snapshotItem() returns the node in a given position in the snapshot (similar to length and item() in a NodeList).

The XPathResult.FIRST_ORDERED_NODE_TYPE result returns the first matching node, which is accessible through the singleNodeValue property:

var oEvaluator = new XPathEvaluator();
var oResult = oEvaluator.evaluate("employee/name", 
oXmlDom.documentElement, null, 
XPathResult.FIRST_ORDERED_NODE_TYPE, null);
alert(oResult.singleNodeValue.xml);

As you may have guessed, this code can be used to mimic the IE selectSingleNode() method:

Element.prototype.selectSingleNode = function (sXPath) {
    var oEvaluator = new XPathEvaluator();
    var oResult = oEvaluator.evaluate(sXPath, this, null, 
		XPathResult.FIRST_ORDERED_NODE_TYPE, null);
	if (oResult != null) {
        return oResult.singleNodeValue;
    } else {
        return null;
    }              
}

This method can then be used the same as the one in IE:

var oNode = oXmlDom.documentElement.selectSingleNode("employee/name");
alert(oNode);

The last section of XPathResult types are the Boolean type, number type, and string type. Each of these result types returns a single value using the booleanValue, numberValue, and stringValue properties, respectively. For the Boolean type, the evaluation typically returns true if at least one node matches the XPath expression and returns false otherwise:

var oEvaluator = new XPathEvaluator();
var oResult = oEvaluator.evaluate("employee/name", 
	oXmlDom.documentElement, null, 
	XPathResult.BOOLEAN_TYPE, null);
	alert(oResult.booleanValue);

In this example, if any nodes match "employee/name", the booleanValue property is equal to true.

For the number type, the XPath expression must use an XPath function that returns a number, such as count(), which counts all the nodes that match a given pattern:

var oEvaluator = new XPathEvaluator();
var oResult = oEvaluator.evaluate("count(employee/name)", 
	oXmlDom.documentElement, 
   null, XPathResult.BOOLEAN_TYPE, null);
	alert(oResult.numberValue);

This code outputs the number of nodes that match "employee/name" (which is 2). If you try using this method without one of the special XPath functions, numberValue is equal to NaN.

For the string type, the evaluate() method finds the first node matching the XPath expression, then returns the value of the first child node, assuming the first child node is a text node. If not, the result is an empty string. Here's an example:

var oEvaluator = new XPathEvaluator();
var oResult = oEvaluator.evaluate("employee/name", 
	oXmlDom.documentElement, null, 
    XPathResult.STRING_TYPE, null);
	alert(oResult.stringValue);

The previous code outputs "Nicholas C. Zakas", because that is the first text node in the first <name/> element under an <employee/> element.

If you feel like living dangerously, you can use the XPathResult.ANY_TYPE. By specifying this result type, evaluate() returns the most appropriate result type based on the XPath expression. Typically, this result type is a Boolean value, number value, string value, or an unordered node iterator. To determine which result type has been returned use the resultType property:

var oEvaluator = new XPathEvaluator();
var oResult = oEvaluator.evaluate("employee/name", 
	oXmlDom.documentElement, null, 
 	XPathResult.STRING_TYPE, null);if (oResult != null) {
    switch(oResult.resultType) {
        case XPath.STRING_TYPE:
            //handle string type
            break;
        case XPath.NUMBER_TYPE:
            //handle number type
            break;
        case XPath.BOOLEAN_TYPE:
            //handle boolean type
            break;
        case XPath.UNORDERED_NODE_ITERATOR_TYPE:
            //handle unordered node iterator type
            break;
        default:
            //handle other possible result types    }
}

As you can tell, XPath evaluation in Firefox is much more complicated than IE, but also much more powerful. By using the custom selectNodes() and selectSingleNode() methods, you can perform XPath evaluation in both browsers using the same code.

Nicholas C. Zakas is the lead author of Professional Ajax by (Wrox, 2006, ISBN: 0-471-77778-1). This article is adapted from chapter 15 "XML in JavaScript" of his first book Professional JavaScript for Web Developers (Wrox, 2005, ISBN: 0-7645-7908-8). He has worked in web development for more than five years. He has helped develop web solutions in use at some of the largest companies in the world. Nicholas is also an active blogger on JavaScript and Ajax topics at http://www.nczonline.net/ and his most recent other articles at Wrox.com are Introduction to the Google Maps API, XMLHttp Requests for Ajax, Ajax and the Yahoo! Connection Manager, and Ajax Submission Throttling.