Omnigia

November 30, 2007

Chaining XPath queries in Mozilla

Filed under: xpath — Dan Muresan @ 4:52 am

If you search for xpath and mozilla, you will find a lot of pages telling you how to do a single query (I personally learned from Mark Pilgrim’s excellent Dive into Greasemonkey). What you will not find, though, is how to further refine the xpath results by chaining a second xpath query. For instance, suppose you have selected some rows out of a table:

var query = '//table [@id eq "scores"]/tr [@class eq "oddRow"]';
var results = document.evaluate (query, document,
  XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

… and now you want to replace all text within bold tags with links (say a link whose target depends on the first cell of the row). How do you get to those bold tags? Some people assume that replacing the context item argument of the evaluate call is enough:

var row = results.snapshotItem (i);
var bold = document.evaluate ('//b', row, ...)

and are surprised when this returns all bold tags within the entire document. The above expression doesn’t even use the value of the context item. You must instead use a relative location path:

var bold = document.evaluate ('b', row, ...)

Not a big deal, but I’ve seen this question being asked a few times.

Finally, here are some utility functions to help write xpath queries faster:

function xpath_raw (query, ctx, type) {
  type = type || XPathResult.ORDERED_NODE_SNAPSHOT_TYPE;
  ctx = ctx || document;
  // HTML pages are usually small; no point in using iterators
  // or UNORDERED results
  return document.evaluate(query, ctx, null, type, null);
}
// returns an array of DOM nodes
function xpath (query, ctx, type) {
  var res = xpath_raw (query, ctx, type);
  var l = [], i;
  for (i = 0; i != res.snapshotLength; i++)
    l.push (res.snapshotItem (i));
  return l;
}
// returns the first DOM node in the result set
function xpath_single (query, ctx) {
  var res = xpath_raw (query, ctx);
  return res.snapshotItem (0);
}

[ Powered by WordPress ]