有没有办法在Javascript中返回DOM元素的XPath字符串?
我从另一个示例中进行了重构。它将尝试检查是否存在唯一的ID,如果是,则使用该用例缩短表达式。
请注意,如果其中一个节点具有使用相同class
属性值的兄弟节点,则XPath将无法区分您要选择哪个兄弟节点。
function createXPathFromElement(elm) {
var allNodes = document.getElementsByTagName('*');
for (var segs = []; elm && elm.nodeType == 1; elm = elm.parentNode)
{
if (elm.hasAttribute('id')) {
var uniqueIdCount = 0;
for (var n=0;n < allNodes.length;n++) {
if (allNodes[n].hasAttribute('id') && allNodes[n].id == elm.id) uniqueIdCount++;
if (uniqueIdCount > 1) break;
};
if ( uniqueIdCount == 1) {
segs.unshift('id("' + elm.getAttribute('id') + '")');
return segs.join('/');
} else {
segs.unshift(elm.localName.toLowerCase() + '[@id="' + elm.getAttribute('id') + '"]');
}
} else if (elm.hasAttribute('class')) {
segs.unshift(elm.localName.toLowerCase() + '[@class="' + elm.getAttribute('class') + '"]');
} else {
for (i = 1, sib = elm.previousSibling; sib; sib = sib.previousSibling) {
if (sib.localName == elm.localName) i++; };
segs.unshift(elm.localName.toLowerCase() + '[' + i + ']');
};
};
return segs.length ? '/' + segs.join('/') : null;
};
function lookupElementByXPath(path) {
var evaluator = new XPathEvaluator();
var result = evaluator.evaluate(path, document.documentElement, null,XPathResult.FIRST_ORDERED_NODE_TYPE, null);
return result.singleNodeValue;
}
一个节点没有唯一的XPath路径,因此您需要决定构建路径的最合适方式。是否使用可用的ID?文档中的数字位置?相对于其他元素的位置?
查看这个答案中的getPathTo()
,其中提供了一种可能的方法。
function getXPathForElement(element) {
const idx = (sib, name) => sib
? idx(sib.previousElementSibling, name||sib.localName) + (sib.localName == name)
: 1;
const segs = elm => !elm || elm.nodeType !== 1
? ['']
: elm.id && document.getElementById(elm.id) === elm
? [`id("${elm.id}")`]
: [...segs(elm.parentNode), `${elm.localName.toLowerCase()}[${idx(elm)}]`];
return segs(element).join('/');
}
function getElementByXPath(path) {
return (new XPathEvaluator())
.evaluate(path, document.documentElement, null,
XPathResult.FIRST_ORDERED_NODE_TYPE, null)
.singleNodeValue;
}
// Demo:
const li = document.querySelector('li:nth-child(2)');
const path = getXPathForElement(li);
console.log(path);
console.log(li === getElementByXPath(path)); // true
<div>
<table id="start"></table>
<div>
<ul><li>option</ul></ul>
<span>title</span>
<ul>
<li>abc</li>
<li>select this</li>
</ul>
</div>
</div>
它将使用 id
选择器,除非该元素不是具有该 id 的第一个元素。不使用类选择器,因为在交互式网页中类可能经常更改。
document.getElementById(elm.id)
代替document.querySelector(`#${elm.id}`)
,因为当id只包含数字时,前者在Chrome上会失败。 - erdosRohit Luthra
对这个答案进行的修改,以解决SVG元素的问题。https://dev59.com/tnE85IYBdhLWcg3wr1ge#55793129。 - Regular Joid("mG61Hd")/div[2]/div[1]/div[2]/div[12]/div[1]/div[1]/div[2]/div[1]/div[4]/div[1]/div[1]/div[2]
<-- 这不会成为一个非常可靠的选择器。 - thdoanElements.DOMPath.xPath(<some DOM node>, false)
。最后一个参数控制您是否获取更短的“复制XPath”(如果为true
)或“复制完整的XPath”。// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
Elements = {};
Elements.DOMPath = {};
/**
* @param {!Node} node
* @param {boolean=} optimized
* @return {string}
*/
Elements.DOMPath.xPath = function (node, optimized) {
if (node.nodeType === Node.DOCUMENT_NODE) {
return '/';
}
const steps = [];
let contextNode = node;
while (contextNode) {
const step = Elements.DOMPath._xPathValue(contextNode, optimized);
if (!step) {
break;
} // Error - bail out early.
steps.push(step);
if (step.optimized) {
break;
}
contextNode = contextNode.parentNode;
}
steps.reverse();
return (steps.length && steps[0].optimized ? '' : '/') + steps.join('/');
};
/**
* @param {!Node} node
* @param {boolean=} optimized
* @return {?Elements.DOMPath.Step}
*/
Elements.DOMPath._xPathValue = function (node, optimized) {
let ownValue;
const ownIndex = Elements.DOMPath._xPathIndex(node);
if (ownIndex === -1) {
return null;
} // Error.
switch (node.nodeType) {
case Node.ELEMENT_NODE:
if (optimized && node.getAttribute('id')) {
return new Elements.DOMPath.Step('//*[@id="' + node.getAttribute('id') + '"]', true);
}
ownValue = node.localName;
break;
case Node.ATTRIBUTE_NODE:
ownValue = '@' + node.nodeName;
break;
case Node.TEXT_NODE:
case Node.CDATA_SECTION_NODE:
ownValue = 'text()';
break;
case Node.PROCESSING_INSTRUCTION_NODE:
ownValue = 'processing-instruction()';
break;
case Node.COMMENT_NODE:
ownValue = 'comment()';
break;
case Node.DOCUMENT_NODE:
ownValue = '';
break;
default:
ownValue = '';
break;
}
if (ownIndex > 0) {
ownValue += '[' + ownIndex + ']';
}
return new Elements.DOMPath.Step(ownValue, node.nodeType === Node.DOCUMENT_NODE);
};
/**
* @param {!Node} node
* @return {number}
*/
Elements.DOMPath._xPathIndex = function (node) {
// Returns -1 in case of error, 0 if no siblings matching the same expression,
// <XPath index among the same expression-matching sibling nodes> otherwise.
function areNodesSimilar(left, right) {
if (left === right) {
return true;
}
if (left.nodeType === Node.ELEMENT_NODE && right.nodeType === Node.ELEMENT_NODE) {
return left.localName === right.localName;
}
if (left.nodeType === right.nodeType) {
return true;
}
// XPath treats CDATA as text nodes.
const leftType = left.nodeType === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : left.nodeType;
const rightType = right.nodeType === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : right.nodeType;
return leftType === rightType;
}
const siblings = node.parentNode ? node.parentNode.children : null;
if (!siblings) {
return 0;
} // Root node - no siblings.
let hasSameNamedElements;
for (let i = 0; i < siblings.length; ++i) {
if (areNodesSimilar(node, siblings[i]) && siblings[i] !== node) {
hasSameNamedElements = true;
break;
}
}
if (!hasSameNamedElements) {
return 0;
}
let ownIndex = 1; // XPath indices start with 1.
for (let i = 0; i < siblings.length; ++i) {
if (areNodesSimilar(node, siblings[i])) {
if (siblings[i] === node) {
return ownIndex;
}
++ownIndex;
}
}
return -1; // An error occurred: |node| not found in parent's children.
};
/**
* @unrestricted
*/
Elements.DOMPath.Step = class {
/**
* @param {string} value
* @param {boolean} optimized
*/
constructor(value, optimized) {
this.value = value;
this.optimized = optimized || false;
}
/**
* @override
* @return {string}
*/
toString() {
return this.value;
}
};
2022年08月14日更新:这里有一个TypeScript版本。
MDN上的函数getXPathForElement提供了类似的解决方案。
以下函数允许传入一个元素和一个XML文档,以找到一条唯一的字符串XPath表达式,以引导回该元素。
请注意,该函数适用于XML文档,由于HTML对nodeName
值的大写化,在HTML文档上可能无法正常工作...
此外,这个函数可能不会产生一个"唯一的字符串XPath";它既不是在任何情况下都能定位到给定元素的唯一XPath,也不是生成的XPath只能识别一个元素(此XPath仅通过元素名称进行搜索,因此可能会识别具有相同元素名称的多个兄弟元素)。
function getXPathForElement(el, xml) {
var xpath = '';
var pos, tempitem2;
while(el !== xml.documentElement) {
pos = 0;
tempitem2 = el;
while(tempitem2) {
if (tempitem2.nodeType === 1 && tempitem2.nodeName === el.nodeName) { // If it is ELEMENT_NODE of the same name
pos += 1;
}
tempitem2 = tempitem2.previousSibling;
}
xpath = "*[name()='"+el.nodeName+"' and namespace-uri()='"+(el.namespaceURI===null?'':el.namespaceURI)+"']["+pos+']'+'/'+xpath;
el = el.parentNode;
}
xpath = '/*'+"[name()='"+xml.documentElement.nodeName+"' and namespace-uri()='"+(el.namespaceURI===null?'':el.namespaceURI)+"']"+'/'+xpath;
xpath = xpath.replace(/\/$/, '');
return xpath;
}
另外XMLSerializer也值得一试。
document.documentElement.nodeName
返回 大写的 'HTML'
如文档所述。但是,如果您使用 /*[name()='HTML']
进行搜索,则没有结果。document.evaluate("/*[name()='HTML']", document.documentElement).iterateNext()
会产生 null
。而如果您使用小写的 /*[name()='html']
,则会找到结果。XPath name()
函数返回 QName,似乎区分大小写!。 - Nate Andersonel.localName
属性 代替 el.nodeName
,以及 local-name()
XPath 函数 代替 name()
XPath 函数。 - Nate AndersonnodeName
属性在HTML中是大写的,但"在XML / XHTML文档中可能以不同的大小写形式出现)"; 稍后它说:“在XML DOM树中元素的标记名称以与它们在原始XML文件中编写的大小写形式相同的方式返回”。 - Nate Andersonfunction getElementXPath (element) {
if (!element) return null
if (element.id) {
return `//*[@id=${element.id}]`
} else if (element.tagName === 'BODY') {
return '/html/body'
} else {
const sameTagSiblings = Array.from(element.parentNode.childNodes)
.filter(e => e.nodeName === element.nodeName)
const idx = sameTagSiblings.indexOf(element)
return getElementXPath(element.parentNode) +
'/' +
element.tagName.toLowerCase() +
(sameTagSiblings.length > 1 ? `[${idx + 1}]` : '')
}
}
console.log(getElementXPath(document.querySelector('#a div')))
<div id="a">
<div>def</div>
</div>
我查看了这里提供的所有解决方案,但是它们都不能与svg
元素一起使用(对于svg
或path
元素,代码getElementByXPath(getXPathForElement(elm)) === elm
返回false
)。
因此,我将Touko的svg修复添加到trincot的解决方案中,得到了以下代码:
function getXPathForElement(element) {
const idx = (sib, name) => sib
? idx(sib.previousElementSibling, name||sib.localName) + (sib.localName == name)
: 1;
const segs = elm => !elm || elm.nodeType !== 1
? ['']
: elm.id && document.getElementById(elm.id) === elm
? [`id("${elm.id}")`]
: [...segs(elm.parentNode), elm instanceof HTMLElement
? `${elm.localName}[${idx(elm)}]`
: `*[local-name() = "${elm.localName}"][${idx(elm)}]`];
return segs(element).join('/');
}
HTMLElement
的实例(SVG是SVGElement
,但我决定不仅检查SVG),它返回的是*[local-name() = "tag"][n]
而不是tag[n]
。
之前:
.../div[2]/div[2]/span[1]/svg[1]/path[1]
之后:
.../div[2]/div[2]/span[1]/*[local-name() = "svg"][1]/*[local-name() = "path"][1]
只需将元素传递给函数getXPathOfElement
,您就会得到Xpath
。
function getXPathOfElement(elt)
{
var path = "";
for (; elt && elt.nodeType == 1; elt = elt.parentNode)
{
idx = getElementIdx(elt);
xname = elt.tagName;
if (idx > 1) xname += "[" + idx + "]";
path = "/" + xname + path;
}
return path;
}
function getElementIdx(elt)
{
var count = 1;
for (var sib = elt.previousSibling; sib ; sib = sib.previousSibling)
{
if(sib.nodeType == 1 && sib.tagName == elt.tagName) count++
}
return count;
}
通过给定的DOM元素获取xPath
此函数返回完整的xPath选择器(不包含任何id或类)。 当网站生成随机的id或类时,这种类型的选择器非常有用。
function getXPath(element) {
// Selector
let selector = '';
// Loop handler
let foundRoot;
// Element handler
let currentElement = element;
// Do action until we reach html element
do {
// Get element tag name
const tagName = currentElement.tagName.toLowerCase();
// Get parent element
const parentElement = currentElement.parentElement;
// Count children
if (parentElement.childElementCount > 1) {
// Get children of parent element
const parentsChildren = [...parentElement.children];
// Count current tag
let tag = [];
parentsChildren.forEach(child => {
if (child.tagName.toLowerCase() === tagName) tag.push(child) // Append to tag
})
// Is only of type
if (tag.length === 1) {
// Append tag to selector
selector = `/${tagName}${selector}`;
} else {
// Get position of current element in tag
const position = tag.indexOf(currentElement) + 1;
// Append tag to selector
selector = `/${tagName}[${position}]${selector}`;
}
} else {
//* Current element has no siblings
// Append tag to selector
selector = `/${tagName}${selector}`;
}
// Set parent element to current element
currentElement = parentElement;
// Is root
foundRoot = parentElement.tagName.toLowerCase() === 'html';
// Finish selector if found root element
if(foundRoot) selector = `/html${selector}`;
}
while (foundRoot === false);
// Return selector
return selector;
}
segs
在此处成为全局变量。 - mattsven