
Get XPath from the element using JavaScript
Learn how to extract XPath from any DOM element using JavaScript, enhancing your web scraping and automation skills.
To get the XPath of an element using JavaScript, you can use an approach that traverses up the DOM tree from the target element, constructing the XPath string as it goes.
Custom function to get XPath
The provided JavaScript function getXpath is designed to generate an XPath expression that uniquely identifies a given DOM element (el). It traverses up the DOM tree from the starting element, collecting information about its type and any siblings of the same type, to construct a relative XPath expression.
Note that this function is also SVG friendly by using *[name()='svg'] for namespace sensitivity and it is very useful in XML-heavy contexts.
Here is an implementation written in JavaScript and optimized for performance:
public static getXPath(el: Node | null): string {
if ((el instanceof Node) === false) {
return '';
}
const escapeXPath = (name: string): string => {
return name.replace(/([:*])/g, '\\$1');
};
const escapeXPathString = (value: string): string => {
if (value.includes('"') === false) {
return `"${value}"`;
}
if (value.includes('\'') === false) {
return `'${value}'`;
}
// Fallback for strings containing both ' and "
return `concat(${value
.split('"')
.map((part, i) => {
return (i === 0 ? `"${part}"` : `,'"',"${part}"`);
}
)
.join('')})`;
};
const isUniqueId = (el: Element): boolean => {
if (!el.id || !el.ownerDocument) {
return false;
}
return el.ownerDocument.querySelectorAll(`#${CSS.escape(el.id)}`).length === 1;
};
// Skip parser-recovery garbage from malformed HTML
const isIgnorableElement = (el: Element): boolean => {
if (el.nodeName.toLowerCase() === 'html' || el.nodeName.toLowerCase() === 'body') {
return false;
}
if (el.id) {
return false;
}
// invalid tag names like: metacontent="home
return !(/^[a-z][a-z0-9-]*$/i).test(el.nodeName);
};
const getSameNameSiblings = (parent: ParentNode, node: Node): Node[] => {
const result: ChildNode[] = [];
const targetName: string = node.nodeName;
for (const child of parent.childNodes) {
if (child.nodeName === targetName) {
result.push(child);
}
}
return result;
};
let element: Node | null = el;
let parent: ParentNode | null = element.parentNode ?? element.ownerDocument;
let relativePath = '';
while (parent && element) {
// Anchor on nearest ancestor with unique ID
if (element instanceof Element && isUniqueId(element)) {
return `//*[@id=${escapeXPathString(element.id)}]${relativePath}`;
}
switch (element.nodeType) {
case Node.ELEMENT_NODE: {
const el = element as Element;
if (isIgnorableElement(el)) {
element = parent as Node;
parent = element.parentNode ?? element.ownerDocument;
continue;
}
const tag: string = el.namespaceURI === 'http://www.w3.org/2000/svg' ?
`*[name()='${el.tagName.toLowerCase()}']` :
escapeXPath(el.tagName).toLowerCase();
const sames: Node[] = getSameNameSiblings(parent, element);
if (sames.length === 1) {
relativePath = `/${tag}${relativePath}`;
} else {
const index = sames.indexOf(element) + 1;
relativePath = `/${tag}[${index}]${relativePath}`;
}
break;
}
case Node.TEXT_NODE: {
const texts = Array.from(parent.childNodes)
.filter((node: ChildNode): boolean => {
return node.nodeType === Node.TEXT_NODE;
});
if (texts.length === 1) {
relativePath = `/text()${relativePath}`;
} else {
relativePath = `/text()[${texts.indexOf(element as ChildNode) + 1}]${relativePath}`;
}
break;
}
case Node.COMMENT_NODE: {
const comments = Array.from(parent.childNodes)
.filter((node: ChildNode): boolean => {
return node.nodeType === Node.COMMENT_NODE;
});
relativePath = `/comment()[${comments.indexOf(element as ChildNode) + 1}]${relativePath}`;
break;
}
case Node.ATTRIBUTE_NODE: {
relativePath = `/@${escapeXPath(element.nodeName).toLowerCase()}${relativePath}`;
break;
}
default:
break;
}
element = parent as Node;
parent = element.parentNode ?? element.ownerDocument;
}
// Fallback (should rarely happen)
return `.//${relativePath.replace(/^\/+/, '')}`;
}Breakdown of how it works
This function generates an XPath for a DOM node by traversing upward from the target to the root, building path segments for elements, text nodes, comments, and attributes. It optimizes the path by anchoring on elements with unique IDs when possible, handles malformed HTML by skipping invalid elements, and properly escapes special characters and quotes. The result is a compact, robust XPath that can uniquely locate the node in the document structure.
Additional tools
There are several tools that can be handy when working with XPath:
- CSS to XPath: the tool allow you to convert CSS selector to XPath.
- CSS Selector to XPath Converter: a tool designed to convert CSS selectors into XPath expressions.
- XPather: XPath online real-time tester, evaluator, and generator for XML and HTML.
Comments