45

I'm using the document.evaluate() JavaScript method to get an element pointed to by an XPath expression:

var element = document.evaluate(
  path,
  document,
  null,
  XPathResult.FIRST_ORDERED_NODE_TYPE,
  null
).singleNodeValue;

But how do I get a list of elements in case the XPath expression points to more than one element on the page?

I tried the following code, but it is not working:

var element = document.evaluate(
  path,
  document,
  null,
  XPathResult.ORDERED_NODE_ITERATOR_TYPE,
  null
);
BryanH
  • 5,826
  • 3
  • 34
  • 47
Abhishek Tripathi
  • 1,211
  • 2
  • 15
  • 30

5 Answers5

64

I found the following solution in the book I am currently reading. It says that the code is from the Prototype library.

function getElementsByXPath(xpath, parent)
{
    let results = [];
    let query = document.evaluate(xpath, parent || document,
        null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    for (let i = 0, length = query.snapshotLength; i < length; ++i) {
        results.push(query.snapshotItem(i));
    }
    return results;
}

Use it like this:

let items = getElementsByXPath("//*"); // return all elements on the page
Krisztián Balla
  • 19,223
  • 13
  • 68
  • 84
  • Failed to read the 'snapshotLength' property from 'XPathResult': The result type is not a snapshot. – CS QGB Feb 23 '20 at 01:25
  • Mmh.. first time I am trying to use it. Looks like i am getting the same *"result type is not a snapshot"* in any case, whatever is the document, the browser or the query (a simple one: `//div`). `resultType` is always 4 and unable to get the snapshot. – Kamafeather Jul 22 '20 at 03:31
  • 1
    Ok, I see (from Community Ans, [down below](https://stackoverflow.com/a/62658961/3088045)). The result is an iterator. – Kamafeather Jul 22 '20 at 03:36
  • I don't understand what is the `parent` parameter here is used for? – Linh Nhat Nguyen Dec 26 '22 at 09:04
  • @LinhNhatNguyen the `parent` parameter is the [context node](https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate) on which the xpath query is executed. – Krisztián Balla Dec 27 '22 at 11:19
  • @Kamafeather I don't understand this. According to https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate `document.evalutate` returns an `XPathResult`, and according to https://developer.mozilla.org/en-US/docs/Web/API/XPathResult `XPathResult` has a property `snapshotLength`. Why does this not work then? – d-b Mar 19 '23 at 00:15
8

From the documentation

var iterator = document.evaluate('//phoneNumber', documentNode, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null );

try {
  var thisNode = iterator.iterateNext();

  while (thisNode) {
    alert( thisNode.textContent );
    thisNode = iterator.iterateNext();
  } 
}
catch (e) {
  dump( 'Error: Document tree modified during iteration ' + e );
}
Jay Byford-Rew
  • 5,736
  • 1
  • 35
  • 36
  • Results in an error message `Can't find variable: documentNode` for the `var iterator…`-line – d-b Mar 19 '23 at 00:44
2

Try this:

function getListOfElementsByXPath(xpath) {
    var result = document.evaluate(xpath, document, null, XPathResult.ANY_TYPE, null);
    return result;
}

Then call it:

var results = getListOfElementsByXPath("//YOUR_XPATH");
while (node = results.iterateNext()) {
     console.log(node);
}
MeirDayan
  • 620
  • 5
  • 20
2

In Chrome, there is a simpler solution, described in this document, at least in the console:

$x(path)

It does the same as the getElementsByXPath function above, but much easier for debugging.

gaborsch
  • 15,408
  • 6
  • 37
  • 48
  • This has become standardly available. – uchuugaka Apr 15 '23 at 10:35
  • It works because it also does all the type coercion for various result types from XPath expressions, not only returning matching elements but counts and text and other things. – uchuugaka Apr 15 '23 at 10:36
0

I was working hard with the same problem some weeks ago. I found out, that the result already represents a list of elements (if any) and one can iterate trough it. I needed to build a jQuery plugin for realize a search of partial or full text strings, which means the inner text of any DOM element like LI or H2. I got the initial understanding on his page : Document.evaluate() | MDN

After some hours I got the plugin running: Search for the word "architecture" only in "p" elements, find partial matching strings ("true" for <p>todays architecture in Europe</p>) instead of matches of entire text (<h2>architecture</h2>).

var found = $('div#pagecontent').findtext('architecture','p',true);

Found results are regular jQuery objects, which can be used as usual.

found.css({ backgroundColor: 'tomato'});

The example of usage above may be altered like this for search trough entire document and all node types like this (partial results)

var found = $('body').findtext('architecture','',true);

or only exact matches

var found = $('div#pagecontent').findtext('architecture');

The plugin itself shows a variable "es" which is the plural of a single "e" for "element". And you can see, how the results are iterated, and collected into a bunch of objects with f = f.add($(e)) (where "f" stands for "found"). The beginning of the function deals with different conditions, like full or partial search ("c" for condition) and the document range for the search ("d").

It may be optimized whereever needed, may not represent the maximum of possibilities, but it represents my best knowledge at the moment, is running without errors and it may answer your question, hopefully. And here is it:

(function($) {
    $.fn.findtext = function(s,t,p) {
        var c, d;
        if (!this[0]) d = document.body;
        else d = this[0];
        if (!t || typeof t !== 'string' || t == '') t = '*';
        if (p === true) c = './/'+t+'[contains(text(), "'+s+'")]';
        else c = './/'+t+'[. = "'+s+'"]';
        var es = document.evaluate(c, d, null, XPathResult.ANY_TYPE, null); 
        var e = es.iterateNext();
        var f = false;
        while (e) {
            if (!f) f = $(e);
            else f = f.add($(e));
            e = es.iterateNext();
        }
        return f || $();
    };
})(jQuery);
ddlab
  • 918
  • 13
  • 28