Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Searching the DOM for multiples of the same string, using XPath

I'm writing a Chrome extension that will search the DOM and highlight all email addresses on the page. I found this to look for at symbols on the page but it only returns correctly when there is one email address, it breaks when there are multiple addresses found.

found = document.evaluate('//*[contains(text(),"@")]', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0);

What is the correct way to have this return multiples if more than one is found?

like image 866
Ryan Grush Avatar asked Sep 08 '15 21:09

Ryan Grush


1 Answers

If you want to handle multiple results, don’t call .snapshotItem(0) on document.evaluate() but instead loop through the results using a for loop and snapshotLength():

Example: Loop through results using snapshotLength() with snapshotItem()

var nodesSnapshot = document.evaluate('//*[contains(text(),"@")]',
    document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );

for ( var i=0 ; i < nodesSnapshot.snapshotLength; i++ )
{
  console.dir( nodesSnapshot.snapshotItem(i) );
}

Either that, or specify the XPathResult.UNORDERED_NODE_ITERATOR_TYPE argument (instead of XPathResult.ORDERED_NODE_SNAPSHOT_TYPE), and use a while loop with iterateNext():

Example: Iterate over results using iterateNext()

var iterator = document.evaluate('//*[contains(text(),"@")]',
    document, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null );

try {
  var thisNode = iterator.iterateNext(); 
  while (thisNode) {
    console.dir( thisNode );
    thisNode = iterator.iterateNext();
  } 
}
catch (e) {
  console.log( 'Error: Document tree modified during iteration ' + e );
}

In cases that are sorta the reverse of the one in this question—cases when you really do want just get the first matching node—you can specify the XPathResult.FIRST_ORDERED_NODE_TYPE value, to return just a single node, and then use the property (not method) singleNodeValue:

Example: Use XPathResult.FIRST_ORDERED_NODE_TYPE and singleNodeValue

var firstMatchingNode = document.evaluate('// [contains(text(),"@")]',
    document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null );

console.dir( firstMatchingNode.singleNodeValue );

Getting text or counts back instead, or testing true/false conditions

Note that among the other values (constants) you can specify as the second-to-last argument to document.evaluate() to get other results types, you can make it directly return:

  • a single string (XPathResult.STRING_TYPE) slurped from some part of the document
  • a number representing a count of some kind (XPathResult.NUMBER_TYPE); for example, a count of the number of e-mail addresses found in the document
  • a boolean value (XPathResult.BOOLEAN_TYPE) representing some true/false aspect of the document; e.g., an indicator whether or not the document contains any e-mail addresses

Of course to get those other result types back, the XPath expression you give as the first argument to document.evaluate() needs to be an expression that will actually return a string, or a number, or a boolean value (instead of returning a set of attribute nodes or element nodes).


More at MDN

The examples above are all based on the MDN Introduction to using XPath in JavaScript tutorial, which is highly recommended to anybody trying to work with XPath and document.evaluate().

like image 179
sideshowbarker Avatar answered Oct 12 '22 23:10

sideshowbarker