# Evaluating an XPath with document.evaluate() to get an array of nodes

The Problem Statement:

Filter all nodes with an existing attribute that starts with a specific string (temp for example purposes). Print an array of node string representations as a result.

The Code:

var result = document.evaluate("//*[@*[starts-with(name(), 'temp')]]", document, null, XPathResult.ANY_TYPE, null);

var node = result.iterateNext();
var nodes = [];
while (node) {
nodes.push(node);
node = result.iterateNext();
}
console.log(nodes);
<div temp-callback="1" temp-post-callback="4"></div>
<div temp-callback="2"></div>
<div></div>
<div temp-callback="8" temp-post-callback="7"></div>

The code is a bit bulky for such a straightforward problem. Is there a simpler and a more concise way to get an array of nodes from a document.evaluate() result?

• That's how it is. You can remove one line inside the loop node = result.iterateNext(); by moving the assignment from var node; to while ((node = result.iterateNext())) { which is a dubious practice though. – wOxxOm Jul 8 '17 at 12:08
• @wOxxOm okay, I was afraid to be told that. Alright, please consider adding the statement as an answer, thanks! – alecxe Jul 10 '17 at 1:32

I don't see a method to directly turn it into an array -- Array.from doesn't work, eg. The best I could achieve was a little cleanup:

var result = document.evaluate("//*[@*[starts-with(name(), 'temp')]]", document, null, XPathResult.ANY_TYPE, null);

var node, nodes = []
while (node = result.iterateNext())
nodes.push(node);

console.log(nodes);
<div temp-callback="1" temp-post-callback="4"></div>
<div temp-callback="2"></div>
<div></div>
<div temp-callback="8" temp-post-callback="7"></div>

There doesn't appear to be a way to get the results directly from document.evaluate().

Taking the concept from this answer, one could get a snapshot result, create an array filled with dummy values (e.g. 0s) and then map calls to snapshotItem().

const query = document.evaluate("//*[@*[starts-with(name(), 'temp')]]", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
const results = Array(query.snapshotLength).fill(0).map((element, index) =>  query.snapshotItem(index));
console.log(results);
<div temp-callback="1" temp-post-callback="4"></div>
<div temp-callback="2"></div>
<div></div>
<div temp-callback="8" temp-post-callback="7"></div>

While less of a functional programming approach, one could also just use a for loop:

const query = document.evaluate("//*[@*[starts-with(name(), 'temp')]]", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for (var i=0, results = []; i< query.snapshotLength; results.push(query.snapshotItem(i++)));
console.log(results);
<div temp-callback="1" temp-post-callback="4"></div>
<div temp-callback="2"></div>
<div></div>
<div temp-callback="8" temp-post-callback="7"></div>

Additionally, given the last example of Equivalent constructs, the while loop could be rewritten as a for loop as well:

for ( ; condition; ) {
statements;
}


So the original code could be written as such:

var nodes = [];
for(var node = result.iterateNext(); node; node = result.iterateNext()) {
nodes.push(node);
}


See this demonstrated in the snippet below:

var result = document.evaluate("//*[@*[starts-with(name(), 'temp')]]", document, null, XPathResult.ANY_TYPE, null);
var nodes = [];
for(var node = result.iterateNext(); node; node = result.iterateNext()) {
nodes.push(node);
}

console.log(nodes);
<div temp-callback="1" temp-post-callback="4"></div>
<div temp-callback="2"></div>
<div></div>
<div temp-callback="8" temp-post-callback="7"></div>

### Performance Comparison

Compare the four approaches with this jsperf example. In Chrome, Firefox, and MS Edge it reports that the original code is the fastest.