9
\$\begingroup\$

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?

\$\endgroup\$
  • \$\begingroup\$ 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. \$\endgroup\$ – wOxxOm Jul 8 '17 at 12:08
  • \$\begingroup\$ @wOxxOm okay, I was afraid to be told that. Alright, please consider adding the statement as an answer, thanks! \$\endgroup\$ – alecxe Jul 10 '17 at 1:32
5
\$\begingroup\$

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>

\$\endgroup\$
3
+50
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.