Last active
June 26, 2025 19:56
-
-
Save James-E-A/093d3835cdaa991e6fad0e360dd9e6c6 to your computer and use it in GitHub Desktop.
Get element(s) by xpath
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Get an element by XPath. | |
* Includes workaround for browser bug not allowing SVG elements to be selected; use the virtual "_" prefix to reference the context node's namespace. | |
* @param {string} expression - A string representing the XPath to be evaluated. | |
* @param {Node|Document} [contextNode] - The context node. | |
* @param {XPathEvaluator|Document} [document] - The document via which evaluate() will be called. Defaults to the context node's document, if specified; otherwise globalThis.document. Required if context node and globalThis.document are both absent. | |
* @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 {?Node} | |
*/ | |
export function getElementByXPath(expression, contextNode, document, ordered=true) { | |
if (document === undefined) document = (contextNode ? contextNode.getRootNode() : globalThis.document); // TypeError if bad parameters | |
if (contextNode === undefined) contextNode = document; | |
var result = document.evaluate( | |
expression, | |
contextNode, | |
(prefix) => (prefix === '_' ? contextNode.namespaceURI : document.lookupNamespaceURI(prefix)), | |
ordered ? XPathResult.FIRST_ORDERED_NODE_TYPE : XPathResult.ANY_UNORDERED_NODE_TYPE | |
); | |
return result.singleNodeValue || null; | |
} | |
/** | |
* Get multiple elements by XPath. | |
* Includes workaround for browser bug not allowing SVG elements to be selected; use the virtual "_" prefix to reference the context node's namespace. | |
* @param {string} expression - A string representing the XPath to be evaluated. | |
* @param {Node|Document} [contextNode] - The context node. | |
* @param {XPathEvaluator|Document} [document] - The document via which evaluate() will be called. Defaults to the context node's document, if specified; otherwise globalThis.document. Required if context node and globalThis.document are both absent. | |
* @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.<Node>|Iterable.<Node>} | |
*/ | |
export function getElementsByXPath(expression, contextNode, document, ordered=true, snapshot=true) { | |
if (document === undefined) document = (contextNode ? contextNode.getRootNode() : globalThis.document); // TypeError if bad parameters | |
if (contextNode === undefined) contextNode = document; | |
var result = document.evaluate( | |
expression, | |
contextNode, | |
(prefix) => (prefix === '_' ? contextNode.namespaceURI : document.lookupNamespaceURI(prefix)), | |
snapshot ? (ordered ? XPathResult.ORDERED_NODE_SNAPSHOT_TYPE : XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE) | |
: (ordered ? XPathResult.ORDERED_NODE_ITERATOR_TYPE : XPathResult.UNORDERED_NODE_ITERATOR_TYPE) | |
); | |
return xpathresultToNativeType(result); | |
} | |
/** | |
* @param {string} expression | |
* @param {Node|Document} [contextNode] | |
* @param {XPathEvaluator|Document} [document] | |
* @param {number} [resultType] | |
* @returns {?Node|Array.<Node>|Iterable.<Node>|?number|?string|?boolean} | |
*/ | |
export function getByXPath(expression, contextNode, document, resultType=XPathResult.ANY_TYPE) { | |
if (document === undefined) document = (contextNode ? contextNode.getRootNode() : globalThis.document); // TypeError if bad parameters | |
if (contextNode === undefined) contextNode = document; | |
var result = document.evaluate( | |
expression, | |
contextNode, | |
(prefix) => (prefix === '_' ? contextNode.namespaceURI : document.lookupNamespaceURI(prefix)), | |
resultType | |
); | |
return xpathresultToNativeType(result); | |
} | |
/** | |
* Convert any XPathResult object into its corresponding Javascript object. | |
* @param {XPathResult} result | |
* @returns {?Node|Array.<Node>|Iterable.<Node>|?number|?string|?boolean} | |
*/ | |
export function xpathresultToNativeType(result) { | |
switch (result.resultType) { | |
case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE: | |
case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE: | |
// XPath Snapshot, as JS Array | |
return Array.from( | |
// TODO benchmark further | |
{ length: result.snapshotLength }, | |
(_0, i) => result.snapshotItem(i) | |
); | |
case XPathResult.UNORDERED_NODE_ITERATOR_TYPE: | |
case XPathResult.ORDERED_NODE_ITERATOR_TYPE: | |
// XPath Iterator, as JS Generator | |
return (function* (xp_it) { | |
// TODO benchmark | |
for ( | |
var value = xp_it.iterateNext() ; | |
value !== null ; | |
value = xp_it.iterateNext() | |
) { | |
yield value; | |
} | |
})(result); | |
case XPathResult.ANY_UNORDERED_NODE_TYPE: | |
case XPathResult.FIRST_ORDERED_NODE_TYPE: | |
// Single node value | |
return result.singleNodeValue || null; | |
case XPathResult.NUMBER_TYPE: | |
return result.numberValue ?? null; | |
case XPathResult.STRING_TYPE: | |
return result.stringValue ?? null; | |
case XPathResult.BOOLEAN_TYPE: | |
return result.booleanValue ?? null; | |
default: | |
throw new DOMException(`Unsupported XPathResult type: ${result.resultType}`, 'NotSupportedError'); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
TODO maybe benchmark the billion different ways one could call
Array.from