Skip to content

Instantly share code, notes, and snippets.

@antfu
Created October 7, 2020 09:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save antfu/160d585fb1a963207b607c869574033b to your computer and use it in GitHub Desktop.
Save antfu/160d585fb1a963207b607c869574033b to your computer and use it in GitHub Desktop.
// port form https://github.com/bermi/element-xpath
// Create a safe reference to the getElementXpath object for use below.
const getElementXpath = function (el, callback) {
var err, result, nodes, i, n, segments, siblings, id_count;
try {
if (callback && (typeof callback !== "function")) {
throw new Error("Invalid callback supplied");
}
// We need to get all the tags on the document,
nodes = getNodes(el);
segments = [];
while (el && el.nodeType === 1) {
if (el.hasAttribute("id")) {
segments = addIdSegments(el, nodes, segments);
if (typeof segments === 'string') {
result = segments;
break;
}
} else if (el.hasAttribute("class")) {
segments = addClassSegment(el, nodes, segments);
} else {
segments = addSiblingSegment(el, nodes, segments);
}
el = el.parentNode;
}
if (!result && segments.length) {
result = "/" + segments.join("/");
}
} catch (err) {
// On sync mode the error will be included on the callback
// remove if only async is supported
if (!callback) {
throw err;
}
}
// Async mode, remove condition if only async is supported
if (callback) {
callback(err, result);
} else {
return result;
}
};
// Returns a list of nodes on the document
// you might want to memoizee or throttle this method
// if your DOM will not change between getElementXpath calls
const getNodes = function (el) {
return document.getElementsByTagName("*");
}
// Adds ID segments to the segments array
// if the current element has an ID it will return a string
// with the element path
function addIdSegments(el, nodes, segments) {
var id_count = 0,
n = 0;
while (n < nodes.length) {
if (nodes[n].hasAttribute("id") && nodes[n].id === el.id) {
id_count = id_count + 1;
}
if (id_count > 1) {
break;
}
n = n + 1;
}
if (id_count === 1) {
// The target element has an ID, that's the last node we need
if (false && segments.length === 0) {
segments.unshift("//[@id=\"" + el.getAttribute("id") + "\"]");
} else {
segments.unshift("id(\"" + el.getAttribute("id") + "\")");
}
return segments.join("/");
} else {
segments.unshift(el.localName.toLowerCase() + "[@id=\"" + el.getAttribute("id") + "\"]");
}
return segments;
}
// Gets the element positions among it's siblings
function addSiblingSegment(el, nodes, segments) {
var i = 1,
siblings = el.previousSibling;
while (siblings) {
if (siblings.localName === el.localName) {
i = i + 1;
}
siblings = siblings.previousSibling;
}
segments.unshift(el.localName.toLowerCase() + "[" + i + "]");
return segments;
}
function addClassSegment(el, nodes, segments) {
segments.unshift(el.localName.toLowerCase() + "[contains(concat(\" \", @class, \" \"),\" " + el.getAttribute("class") + " \")]");
return segments;
}
console.log(getElementXpath(document.querySelector('html')))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment