Skip to content

Instantly share code, notes, and snippets.

@JamesTheAwesomeDude
Last active March 25, 2024 16:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JamesTheAwesomeDude/093d3835cdaa991e6fad0e360dd9e6c6 to your computer and use it in GitHub Desktop.
Save JamesTheAwesomeDude/093d3835cdaa991e6fad0e360dd9e6c6 to your computer and use it in GitHub Desktop.
Get element(s) by xpath
/**
* Get an element by XPath.
* @param {string} expression - A string representing the XPath to be evaluated.
* @param {Element} [context] - The context node. Defaults to the current document.
* @param {Document} [document] - The document against which evaluate() will be called. Defaults to the context node's document.
* @param {boolean} [ordered=true] - Whether to return the "first" node selected by the expression. If false, allows the XPath engine to return an arbitrary node selected by the expression.
* @returns {Element|null}
*/
function getElementByXPath(expression, context = null, document = null, ordered = true) {
if (context === null) context = window.document;
if (document === null) document = context.getRootNode();
const result = document.evaluate(
expression,
context,
document,
ordered ? XPathResult.FIRST_ORDERED_NODE_TYPE : XPathResult.ANY_UNORDERED_NODE_TYPE
);
return result.singleNodeValue || null;
}
/**
* Get multiple elements by XPath.
* @param {string} expression - A string representing the XPath to be evaluated.
* @param {Element} [context] - The context node. Defaults to the current document.
* @param {Document} [document] - The document against which evaluate() will be called. Defaults to the context node's document.
* @param {boolean} [ordered=true] - Whether to return the nodes in strict order. If false, allows the XPath engine to return the elements in arbitrary order.
* @param {boolean} [snapshot=true] - If true, immediately return an Array containing all selected elements; if false, return a "lazy" Iterator yielding all selected elements.
* @returns {Array.<Element>|Iterable.<Element>}
*/
function getElementsByXPath(expression, context = null, document = null, ordered = true, snapshot = true) {
if (context === null) context = window.document;
if (document === null) document = context.getRootNode();
if (snapshot) {
// Array
const result = document.evaluate(
expression,
context,
document,
ordered ? XPathResult.ORDERED_NODE_SNAPSHOT_TYPE : XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE
);
return Array.from({
[Symbol.iterator]() {
var i = 0;
return ({
next() {
const value = result.snapshotItem(i++);
return ({ value, done: !value });
},
});
}
});
} else {
// Iterator
const result = document.evaluate(
expression,
context,
document,
ordered ? XPathResult.ORDERED_NODE_ITERATOR_TYPE : XPathResult.UNORDERED_NODE_ITERATOR_TYPE
);
return ({
next() {
const value = result.iterateNext();
return { value, done: !value };
},
[Symbol.iterator]() {
return this;
},
});
}
}
@JamesTheAwesomeDude
Copy link
Author

TODO: compare de performance

// Option 1: status quo
Array.from({
	[Symbol.iterator]() {
		var i = 0;
		return ({
			next() {
				const value = result.snapshotItem(i++);
				return ({ value, done: !value });
			},
		});
	}
});

// Option 2: generator meme
Array.from({
	*[Symbol.iterator]() {
		const length = result.snapshotLength;
		for( var i = 0 ; i < length ; i++ ) {
			yield result.snapshotItem(i);
		}
	}
});

// Option 3: length-and-index meme (deep fried)
Array.from(
	new Proxy(
		result,
		{
			get(t, p, r) {
				if (typeof p === 'string') {
					if (p === 'length')
						return t.snapshotLength;
					if (!isNaN(parseInt(p)))
						return t.snapshotItem(p);
				}
				return Reflect.get(...arguments);
			}
		}
	)
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment