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.

Download: xpath.js

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.


Fourteen comments

You can subscribe to the comment feed to follow the responses to this entry.

  1. I am trying to do very basic navigation in an XML file in Javascript. Do I need a such parser or should the msxml4.dll be enough?
    What I am trying to do is reach nodes in the XML using selectNodes(“//tag”);
    This succeeds with msxml4
    Then I try to navigate back up the XML tree using
    selectSingleNode(“ancestor”);
    which does not work.
    I am not sure if I am doing something wrong or if I actually need an additional parser such a yours.
    I’d appreciate any help.

  2. Meir (sorry for the delay in replying), it depends if you want to be able to navigate the document using XPath in UAs other than IE. If not, then using msxml is fine. If so, then you’ll need to do things differently depending on which UA your page is running on.

    Note that nowadays, the three major non-IE browsers support DOM Level 3 XPath, and in IE you can use the msxml functions, so the need for a library such as mine is probably diminished a bit.

    The specific problem you’re encountering though is that you need to use selectSingleNode("ancestor::*") to select the parent node.

  3. Does it have support for name spaces, especially on Opera and Chrome?

    Best regards

  4. It does have support for namespaces; see the use of NamespaceResolver in the examples above.

    To be honest, I haven’t tested this code lately so I don’t know if it works in Opera and Chrome.

  5. Kim Ji Hwan
    12 March 2010, 6:20pm

    it is Awesome!!

  6. I have been using this library successfully for a long time and for the first time I am now dealing with an XML document with namespaces declared.

    I have tried to follow the simple examples here but I simply don’t understand how to do it. Are there any other simple examples out there I can refer to?

  7. Ed: I don’t know of other examples out there. The namespace resolver object you supply must have a getNamespace(prefix, node) function on it, where prefix is the namespace prefix to resolve and node is a context element node, IIRC. From this function, return the namespace URI the prefix maps to.

  8. it is great. thanks for your hard work.

    and there is a type error at line 4180.

    case XPathResult.FIRST_UNORDERED_NODE_TYPE:

  9. Thanks XY, fixed now.

  10. Hi Cameron,

    I am writing to you to let you know that I am using your xpath.js library in a GWT SVG library of mine and it is working very well. Many thanks for this fine work.

    If you are interested, here are a few links to pages where I mention your work:
    http://www.vectomatic.org/lib-gwt-svg
    http://www.vectomatic.org/lib-gwt-svg/availability-of-lib-gwt-svg-0-5-4-and-other-announcements

    as well as SVG samples / games which use your code:

    http://www.vectomatic.org/gwt/lib-gwt-svg-samples/lib-gwt-svg-samples.html (see the xpath sample)
    http://www.vectomatic.org/gwt/lib-gwt-svg-edu/lib-gwt-svg-edu.html

    Many thanks,

    Lukas

  11. Thanks Lukas!

  12. Hi Cameron,

    For completeness, here are some other javascript-based XPath libraries I cam across while evaluating your library:

    XPathJS – has namespace support
    https://github.com/andrejpavlovic/xpathjs

    JavaScript-XPath – fastest
    http://coderepos.org/share/wiki/JavaScript-XPath

    Llama’s XPath.js – smallest
    http://llamalab.com/js/xpath/

    Google AJAXSLT – oldest
    http://code.google.com/p/ajaxslt/

    Thanks for building this library, I’ve found it very useful for my projects.

    Cheers,
    Andrej

  13. Hi Cameron

    I have found xpath.js works great on node.js. Since many node modules only work on Linux, it seems that xpath.js is invaluable when using node on windows. I think it would be great to make xpath.js a node module and upload it to github. If you do not have time I am happy to do this.

    What do you think?

    Thanks,
    Yaron

  14. The following expression causes an infinite loop:
    “’1′ mod/html’”

    To fix, replace:

    while ((c = s.charAt(pos++)) != delimiter) {
    literal += c;
    }

    with:

    while ((c = s.charAt(pos++)) != delimiter && c != “”) {
    literal += c;
    }
    if (c == “”)
    throw new Error(“Unclosed literal: ‘” + literal + “‘”);

Leave a comment