Utilizing Array.from with a XPathResult: A Comprehensive Guide

In my sample document, I found 138 nodes with the tag td using querySelectorAll.

Array.from(document.querySelectorAll('td')).length
138

However, when I tried to do the same using XPath, I did not get any result:

Array.from(document.evaluate(".//td", document.body, null, XPathResult.ANY_TYPE, null)).length
0

Despite this, there is at least one match:

document.evaluate(".//td", document.body, null, XPathResult.ANY_TYPE, null).iterateNext().nodeName
"TD"

The issue seems to be that Array.from cannot handle a XPathResult. Even when trying to iterate over it directly, I still get 0 results:

Array.from(document.evaluate('.', document.body, null, XPathResult.ANY_TYPE, null)).length
0

Is there a way to make a XPathResult compatible with Array.from?

Answer №1

Sorry, it is not possible to use Array.from with XPathResult objects because they do not meet the criteria for conversion:

  1. They are not "array-like" and do not have a .length property.
  2. They do not implement the iterator protocol which is required for conversion to an array.

To convert a XPathResult object to an array, you would need to manually iterate over the results and store them in an array like this:

const nodes = [];
let node = xPathResult.iterateNext();
while (node) {
  nodes.push(node);
  node = xPathResult.iterateNext();
}

However, if you are already looping over the nodes, you can likely perform any necessary array operations within that loop instead.

Answer №2

Expanding on the solution provided by this post authored by @JamesTheAwesomeDude, you have the option to utilize Array.from (or leverage the spread operator) after implementing an iterator onto XPathResult. This custom iterator offers enhanced functionality as it can handle various types of XPathResult objects:

if (!XPathResult.prototype[Symbol.iterator]) XPathResult.prototype[Symbol.iterator] = function* () {
    switch (this.resultType) {
        case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
        case XPathResult.ORDERED_NODE_ITERATOR_TYPE:
            let result;
            while ((result = this.iterateNext()) != null ) yield result;
            break;
        case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE:
        case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE:
            for (let i=0; i < this.snapshotLength; i++) yield this.snapshotItem(i);
            break;
        default:
            yield this.singleNodeValue;
            break;
    }
};

Answer №3

The current answer clarifies that this is not exactly "supported" because, for some strange reason, the XPathResult does not adhere to JS' standard iteration protocols.

If needed, you can create a custom solution that aligns with your specific use-case using Array.from:

function convertXPathToIterator (xpr) {
  // Generate a JavaScript iterator for an *ITERATOR_TYPE XPathResult
  // or a JavaScript iterable for a *SNAPSHOT_TYPE XPathResult
  switch (xpr.resultType) {
    case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE:
    case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE:
      return {
        [Symbol.iterator] () {
          var i = 0;
          return {
            next() {
              var node = xpr.snapshotItem(i++);
              return {value: node, done: !node};
            }
          };
        },
        at(i) {
          return xpr.snapshotItem(i) || undefined;
        }
      };
    case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
    case XPathResult.ORDERED_NODE_ITERATOR_TYPE:
      return {
        next() {
          var node = xpr.iterateNext();
          return {value: node, done: !node};
        },
        [Symbol.iterator] () {
          return this;
        },
      };
  }
}


// For example, extract the top child elements
let sample_xpr = document.evaluate('/html/*', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
// Convert into an Array:
let converted_arr = Array.from(convertXPathToIterator(sample_xpr));

// Can be converted to an array!
console.log("length:", converted_arr.length);
console.log("map demo (e => e.tagName):", converted_arr.map(e => e.tagName));

...also, it directly supports for...of without needing to create an entire Array:

function convertXPathToIterator (xpr) {
  // Generate a JavaScript iterator for an *ITERATOR_TYPE XPathResult
  // or a JavaScript iterable for a *SNAPSHOT_TYPE XPathResult
  switch (xpr.resultType) {
    case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE:
    case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE:
      return {
        [Symbol.iterator] () {
          var i = 0;
          return {
            next() {
              var node = xpr.snapshotItem(i++);
              return {value: node, done: !node};
            }
          };
        },
        at(i) {
          return xpr.snapshotItem(i) || undefined;
        }
      };
    case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
    case XPathResult.ORDERED_NODE_ITERATOR_TYPE:
      return {
        next() {
          var node = xpr.iterateNext();
          return {value: node, done: !node};
        },
        [Symbol.iterator] () {
          return this;
        },
      };
  }
}

let another_sample_xpr = document.evaluate('/html/*', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);

for( let e of convertXPathToIterator(another_sample_xpr) ) {
  // It's iterable!
  console.log(e.tagName);
}

Hence, this approach is more versatile (and may consume slightly less memory when dealing with extremely large numbers of nodes without initializing an Array).


In my test document, I found a total of 138 td nodes.

Just in case you are primarily interested in counting the number of matches, you can retrieve it directly using the .snapshotLength property for non-iterator types (or manually count for iterator types if preferred):

function convertXPathToIterator (xpr) {
  // Generate a JavaScript iterator for an *ITERATOR_TYPE XPathResult
  // or a JavaScript iterable for a *SNAPSHOT_TYPE XPathResult
  switch (xpr.resultType) {
    case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE:
    case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE:
      return {
        [Symbol.iterator] () {
          var i = 0;
          return {
            next() {
              var node = xpr.snapshotItem(i++);
              return {value: node, done: !node};
            }
          };
        },
        at(i) {
          return xpr.snapshotItem(i) || undefined;
        }
      };
    case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
    case XPathResult.ORDERED_NODE_ITERATOR_TYPE:
      return {
        next() {
          var node = xpr.iterateNext();
          return {value: node, done: !node};
        },
        [Symbol.iterator] () {
          return this;
        },
      };
  }
}

let final_sample_xpr = document.evaluate('/html/*', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);

let match_count = final_sample_xpr.snapshotLength;

console.log("number of matches:", match_count);


let final_iterator_xpr = document.evaluate('/html/*', document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);

let manual_match_count = 0;
for( let e of convertXPathToIterator(final_iterator_xpr) )
  manual_match_count ++;

console.log("number of matches:", manual_match_count);

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to replicate touch functionality on mobile browsers for both Android and iPhone devices?

Recently, while developing a web application, I ran into an issue on mobile browsers where the :active pseudo class wasn't functioning properly. I am currently utilizing CSS sprites and looking for guidance on how to simulate clicks for mobile browser ...

Step-by-step guide on creating a monochrome version of an image using automation

In WordPress, I have a logo parade where all logos are in color RGB. I really like the effect of having them appear as black and white initially and then transitioning to color when hovered over. I am familiar with using sprites for this effect, but it wo ...

"Encountering a Heroku error due to an oversized cookie while using Express.js and

Being unsure of the exact cause, I am encountering an issue with Heroku that gives me the error message t=error code=H25 desc="HTTP restriction: oversized cookie" whenever I attempt to authenticate myself with Discord OAuth. Interestingly, this problem onl ...

Press the submit button after receiving the ajax response

Is there a way to manually or programmatically submit a specific page? The catch is that before the submission can happen, a function called create_impi_num(x) needs to be triggered in order to store data using $.post(). The issue at hand: If I set the s ...

What is the best way to search for and isolate an array object within an array of objects using Javascript?

I am attempting to narrow down the list based on offerings const questions = [ { "id": 2616, "offerings": [{"code": "AA"},{"code": "AB"}]}, { "id": 1505, "offerings": [ ...

The website code lacks a dynamically generated <div> element

When using jQuery to dynamically add content to a "div" element, the content is visible in the DOM but not in the view page source. For example: <div id="pdf"></div> $("#btn").click(function(){ $("#pdf").html("ffff"); }); How can I ensur ...

To access the context menu popup and the first element of a row in a datatable, simply right-click on the row

Currently, I am working on writing code for a datatable where when you right-click a row, two actions should be triggered: a context menu should appear, and I need to retrieve the first cell of that row to pass it to my Ajax function. I have managed to wr ...

Is it feasible for a JavaScript function to receive the innerHTML of the element from which it is invoked as an argument?

Is there a way to bind a function that takes a String as a parameter to a button so that it is called onclick and takes the innerHTML of the element as a parameter, without assigning the button an id or using queryselector? <button onclick="myFunct ...

Tips for passing array objects as parameters to a method during array parsing

As a beginner in Java and programming, I am encountering a challenge that I need help with: I am working with an array containing 5 objects, out of which 2 have a specific property. I would like to pass these 2 objects as parameters to a method. Is it pos ...

Communication between nodes using serial ports in Node.js fails to receive a response from the connected Arduino board

I've been attempting to establish communication between a computer and an Arduino board using node.js. Despite having a simple program, it just doesn't seem to work as expected. The Arduino program (which seems to be working fine) is as follows: ...

Unable to display material-ui icon when using a conditional ternary statement in React

I'm facing an issue where my app crashes when attempting to display a mui icon based on a certain condition. I suspect the problem lies in how I am using PriceCheckIcon within {}. Can someone provide assistance? <span style={ ...

An error popped up out of the blue while attempting to execute Webpack

Looking to deploy my React website on IIS but encountering an error when running npm run build:prod. The error message states: index.js Line 1: Unexpected reserved word. You may need an appropriate loader to handle this file type. Below is the snippet fro ...

Simultaneously activate the top and bottom stacked components by clicking on them

One of my components has two child components, A and B. Component A is set to position: absolute and placed on top of component B like an overlay. Additionally, component A has a higher z-index. Both components have onClick events attached to them. My que ...

Is there a way to change an ISO 8601 date into the format '/Date(1525687010053)/' using JavaScript?

Is there a way to convert a date value that is formatted as 9999-12-31T00:00:00Z to the format /Date(1525687010053)/ using javascript? I tried implementing the following code, but it doesn't seem to be working: var datevalue = '9999-12-31T00:00 ...

Having trouble getting my HTML file and CSS styles to render properly in Chrome

Currently, I am in the process of developing a website and facing challenges with displaying CSS properties accurately. Despite seeking input from friends and users, my HTML file is not rendering as expected within various browsers such as Chrome, Edge, an ...

Android Chrome users experiencing sidebar disappearance after first toggle

Over the past few days, I've dedicated my time to developing a new project. I've been focusing on creating the HTML, CSS, and Java layout to ensure everything works seamlessly. After completing most of the work, the design looks great on desktop ...

Firebase Firestore is returning the dreaded [object Object] rather than the expected plain object

I've created a custom hook called useDocument.js that retrieves data from a firestore collection using a specific ID. However, I'm encountering an issue where it returns [object Object] instead of a plain object. When I attempt to access the nam ...

The Chrome browser does not recognize Sys.WebForms

I am encountering an issue with my Web-Application when trying to perform partial updates. The error message "Sys.WebForms is undefined in Chrome (latest version)" keeps popping up. Despite my extensive research efforts, I have not been able to find a solu ...

Utilizing Ajax to update the login form without reloading the page and displaying any errors

I have created a login form that utilizes ajax to handle login errors such as "incorrect password" from my login.php file. Instead of reloading a new page with the error message, it now displays the errors on the same login form, which is working perfectly ...

Implement a loading bar on the entire page in Vue.js while a request is being made

There is an "edit" button on my page which allows me to edit certain elements and either save or close without saving. I would like to implement a loading bar that displays when the user clicks the "save" button, indicating that the data is being processe ...