Created
October 7, 2020 09:34
-
-
Save antfu/160d585fb1a963207b607c869574033b to your computer and use it in GitHub Desktop.
This file contains 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
// 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