mcc.id.au / xpathjs

XPath 1.0 in JavaScript

Here is an implementation of XPath 1.0 in JavaScript. It's not completely tested but it seems to work with the few tests I've been doing. It is released under a Creative Commons Licence. Please let me know if there are any bugs.

There is a mailing list for discussing the library. You can subscribe by going to the list information page. The archives are also available.

A simple example of how to use it:

// Create a new parser object
var parser = new XPathParser();

// Parse the XPath expression
var xpath = parser.parse("count(//svg:rect)");

// Create a context for the XPath to be evaluated in
var context = new XPathContext();
context.expressionContextNode = document.documentElement;

// Evaluate the XPath expression
var result = xpath.evaluate(context);

// Display the result
window.alert("There " + (result == 1 ? "is " : "are ") + result
  + "SVG rect elements in the document.");

The XPathContext constructor takes three arguments: a VariableResolver, a NamespaceResolver and a FunctionResolver. If these are not specified then default objects will be used. The default VariableResolver will not resolve any variables. The default NamespaceResolver will resolve namespaces based on the expression's context node. The default FunctionResolver resolves only the core XPath functions.

Here is an example using custom resolvers:

// MyVariableResolver

MyVariableResolver.prototype = new VariableResolver();
MyVariableResolver.prototype.constructor = MyVariableResolver;
MyVariableResolver.superclass = VariableResolver.prototype;

function MyVariableResolver() {
}

MyVariableResolver.prototype.getVariableWithName = function(ns, ln, c) {
  if (ns == null && ln == "doc") {
    // Make the $doc variable return a nodeset containing the
    // document element.
    var ns = new XNodeSet();
    ns.add(c.contextNode.ownerDocument.documentElement);
    return ns;
  }
  return null;
};

// MyNamespaceResolver

MyNamespaceResolver.prototype = new NamespaceResolver();
MyNamespaceResolver.prototype.constructor = MyNamespaceResolver;
MyNamespaceResolver.superclass = NamespaceResolver.prototype;

function MyNamespaceResolver() {
}

MyNamespaceResolver.prototype.getNamespace = function(prefix, n) {
  // Always resolve the prefix "ex".
  if (prefix == "ex") {
    return "http://example.org/functions";
  }
  return this.superclass.getNamespace(prefix, n);
};

// Extension function

var doubleString = function() {
  // Extension functions always have the XPathContext as the first
  // argument.
  var c = arguments[0];
  if (arguments.length != 2) {
    throw new Error("Function ex:double-string expects (string)");
  }

  // We take the first real argument to the extension function,
  // evaluate it, and get its string value.
  var s = arguments[1].evaluate(c).stringValue();

  // Then we return a new string object for the doubled string.
  return new XString(s + s);
};

var varRes = new MyVariableResolver();
var nsRes = new MyNamespaceResolver();
var funRes = new FunctionResolver();

funRes.addFunction("http://example.org/functions",
                   "double-string", doubleString);

var parser = new XPathParser();
var context = new XPathContext(varRes, nsRes, funRes);
var xpath = new XPath("ex:double-string($doc/@width)");
var result = xpath.evaluate(context);

Evaluating an XPath expression will return an object of class XBoolean, XNumber, XString or XNodeSet. All four of these classes have methods called booleanValue, numberValue and stringValue to convert them to JavaScript types. The XNodeSet class has a toArray method which will return an array containing the DOM nodes in that nodeset.

Because the XPath data model treats adjacent text nodes and CDATA sections as a single text node, expressions working on documents which have such adjacent text nodes may not evaluate correctly. There is a utility function included to coalesce all adjacent text nodes in a document to avoid this problem.

Utilities.coalesceText(document);

Since revision 18, the script includes DOM 3 XPath support. Including the script will attempt to install the XPathEvaluator functions on the current document (the one named by the global 'document' variable), if XPath support is not already available. This is not guaranteed to work in all DOM implementations, since the document object is a host object, and host objects need not store any extra properties. However, it works at least in Internet Explorer, Opera and Mozilla (though Mozilla already has DOM 3 XPath support).

The DOM 3 XPath support can be installed on any document object, with a call to the installDOM3XPathSupport function:

installDOM3XPathSupport(myDoc, new XPathParser());

The function must be given an XPathParser object that will be used for all XPath parsing.