function getElementByXPath(path, context=null, document=null, namespaceResolver=null, first_ordered=true) {
  if (context === null) context = window.document.documentElement;
  if (document === null) document = context.ownerDocument;
  if (namespaceResolver === null) namespaceResolver = document.createNSResolver(context);
  const result = document.evaluate(
    path,
    context,
    namespaceResolver,
    first_ordered ? XPathResult.FIRST_ORDERED_NODE_TYPE : XPathResult.ANY_UNORDERED_NODE_TYPE,
    null);
  return result.singleNodeValue || null;
}

function getElementsByXPath(path, context=null, document=null, namespaceResolver=null, ordered=true) {
  if (context === null) context = window.document.documentElement;
  if (document === null) document = context.ownerDocument;
  if (namespaceResolver === null) namespaceResolver = document.createNSResolver(context);
  const result = document.evaluate(
    path,
    context,
    namespaceResolver,
    ordered ? XPathResult.ORDERED_NODE_SNAPSHOT_TYPE : XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
    null);
  return Array.from(new Proxy(result, {
    get(target, property, receiver) {
      if (typeof property === "string") {
        if (property === "length") return target.snapshotLength;
        if (!isNaN(property)) return target.snapshotItem(property);
      }
      return Reflect.get(...arguments);
    }
  }));
}

// Alternate possibilities:

function getElementsByXPath(path, context=null, document=null, namespaceResolver=null, ordered=true, snapshot=true) {
  if (context === null) context = window.document.documentElement;
  if (document === null) document = context.ownerDocument;
  if (namespaceResolver === null) namespaceResolver = document.createNSResolver(context);
  if (!snapshot) {
    // Optional codepath to allow streaming iteration
    const result = document.evaluate(
      path,
      context,
      namespaceResolver,
      ordered ? XPathResult.ORDERED_NODE_ITERATOR_TYPE : XPathResult.UNORDERED_NODE_ITERATOR_TYPE,
      null);
    return Object.assign(result, ({
      next() {
        const value = this.iterateNext();
        return { value, done: !value };
      },
      [Symbol.iterator]() {
        return this;
      }
    }));
  }
  const result = document.evaluate(path,
    context,
    namespaceResolver,
    ordered ? XPathResult.ORDERED_NODE_SNAPSHOT_TYPE : XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
    null);
  return Array.from(new Proxy(result, {
    get(target, property, receiver) {
      if (typeof property === "string") {
        if (property === "length") return target.snapshotLength;
        if (!isNaN(property)) return target.snapshotItem(property);
      }
      return Reflect.get(...arguments);
  /*
  // TODO compare performance:
  return Array.from(Object.assign(result, {
    [Symbol.iterator]() {
      var i = 0;
      return {
        next() {
          const value = this.snapshotItem(i++);
          return { value, done: !value };
        }
      };
    }
  }));
  */
}

Leave a Reply

Your email address will not be published. Required fields are marked *

Warning: This site uses Akismet to filter spam. Until or unless I can find a suitable replacement anti-spam solution, this means that (per their indemnification document) all commenters' IP addresses will be sent to Automattic, Inc., who may choose to share such with 3rd parties.
If this is unacceptable to you, I highly recommend using an anonymous proxy or public Wi-Fi connection when commenting.