[英]How to calculate the XPath position of an element using Javascript?
假设我有一个包含不同类型标签的大型 HTML 文件,类似于您现在正在查看的 StackOverflow 文件。
现在假设您单击页面上的一个元素,计算引用该特定元素的最基本 XPath 的 Javascript 函数会是什么样子?
我知道在 XPath 中有无数种引用该元素的方法,但我正在寻找只查看 DOM 树的东西,而不考虑 ID、类等。
例子:
<html>
<head><title>Fruit</title></head>
<body>
<ol>
<li>Bananas</li>
<li>Apples</li>
<li>Strawberries</li>
</ol>
</body>
</html>
假设您点击了Apples 。 Javascript 函数将返回以下内容:
/html/body/ol/li[2]
它基本上会沿着 DOM 树向上一直工作到 HTML 元素。
澄清一下,“单击”事件处理程序不是问题所在。 我可以做到这一点。 我只是不确定如何计算元素在 DOM 树中的位置并将其表示为 XPath。
PS 任何使用或不使用 JQuery 库的答案都值得赞赏。
PPS 我对 XPath 完全陌生,所以我什至可能在上面的例子中犯了错误,但你会明白的。
2010 年 8 月 11 日编辑:看起来有人问了类似的问题: generate/get the Xpath for a selected textnode
Firebug 可以做到这一点,它是开源的 ( BSD ),因此您可以重用它们的实现,不需要任何库。
这是上面链接源的摘录。 以防万一上面的链接会改变。 请检查来源以从更改和更新或提供的完整功能集中获益。
Xpath.getElementXPath = function(element)
{
if (element && element.id)
return '//*[@id="' + element.id + '"]';
else
return Xpath.getElementTreeXPath(element);
};
上面的代码调用了这个函数。 注意我添加了一些换行以避免水平滚动条
Xpath.getElementTreeXPath = function(element)
{
var paths = []; // Use nodeName (instead of localName)
// so namespace prefix is included (if any).
for (; element && element.nodeType == Node.ELEMENT_NODE;
element = element.parentNode)
{
var index = 0;
var hasFollowingSiblings = false;
for (var sibling = element.previousSibling; sibling;
sibling = sibling.previousSibling)
{
// Ignore document type declaration.
if (sibling.nodeType == Node.DOCUMENT_TYPE_NODE)
continue;
if (sibling.nodeName == element.nodeName)
++index;
}
for (var sibling = element.nextSibling;
sibling && !hasFollowingSiblings;
sibling = sibling.nextSibling)
{
if (sibling.nodeName == element.nodeName)
hasFollowingSiblings = true;
}
var tagName = (element.prefix ? element.prefix + ":" : "")
+ element.localName;
var pathIndex = (index || hasFollowingSiblings ? "["
+ (index + 1) + "]" : "");
paths.splice(0, 0, tagName + pathIndex);
}
return paths.length ? "/" + paths.join("/") : null;
};
我用来获取与您的情况类似的 XPath 的函数,它使用 jQuery:
function getXPath( element )
{
var xpath = '';
for ( ; element && element.nodeType == 1; element = element.parentNode )
{
var id = $(element.parentNode).children(element.tagName).index(element) + 1;
id > 1 ? (id = '[' + id + ']') : (id = '');
xpath = '/' + element.tagName.toLowerCase() + id + xpath;
}
return xpath;
}
它返回元素的 xpath 和 xpath 的元素迭代器。
https://gist.github.com/iimos/e9e96f036a3c174d0bf4
function xpath(el) {
if (typeof el == "string") return document.evaluate(el, document, null, 0, null)
if (!el || el.nodeType != 1) return ''
if (el.id) return "//*[@id='" + el.id + "']"
var sames = [].filter.call(el.parentNode.children, function (x) { return x.tagName == el.tagName })
return xpath(el.parentNode) + '/' + el.tagName.toLowerCase() + (sames.length > 1 ? '['+([].indexOf.call(sames, el)+1)+']' : '')
}
可能您需要为不支持 [].filter 方法的 IE8 添加垫片: 此 MDN 页面提供了此类代码。
var xp = xpath(elementNode)
执行 xpath:
var iterator = xpath("//h2") var el = iterator.iterateNext(); while (el) { // work with element el = iterator.iterateNext(); }
可以稍微修改 firebug 实现以检查 element.id 在 dom 树的更上层:
/**
* Gets an XPath for an element which describes its hierarchical location.
*/
var getElementXPath = function(element) {
if (element && element.id)
return '//*[@id="' + element.id + '"]';
else
return getElementTreeXPath(element);
};
var getElementTreeXPath = function(element) {
var paths = [];
// Use nodeName (instead of localName) so namespace prefix is included (if any).
for (; element && element.nodeType == 1; element = element.parentNode) {
var index = 0;
// EXTRA TEST FOR ELEMENT.ID
if (element && element.id) {
paths.splice(0, 0, '/*[@id="' + element.id + '"]');
break;
}
for (var sibling = element.previousSibling; sibling; sibling = sibling.previousSibling) {
// Ignore document type declaration.
if (sibling.nodeType == Node.DOCUMENT_TYPE_NODE)
continue;
if (sibling.nodeName == element.nodeName)
++index;
}
var tagName = element.nodeName.toLowerCase();
var pathIndex = (index ? "[" + (index+1) + "]" : "");
paths.splice(0, 0, tagName + pathIndex);
}
return paths.length ? "/" + paths.join("/") : null;
};
我刚刚修改了 DanS 的解决方案,以便将其与文本节点一起使用。 序列化 HTML 范围对象非常有用。
/**
* Gets an XPath for an node which describes its hierarchical location.
*/
var getNodeXPath = function(node) {
if (node && node.id)
return '//*[@id="' + node.id + '"]';
else
return getNodeTreeXPath(node);
};
var getNodeTreeXPath = function(node) {
var paths = [];
// Use nodeName (instead of localName) so namespace prefix is included (if any).
for (; node && (node.nodeType == 1 || node.nodeType == 3) ; node = node.parentNode) {
var index = 0;
// EXTRA TEST FOR ELEMENT.ID
if (node && node.id) {
paths.splice(0, 0, '/*[@id="' + node.id + '"]');
break;
}
for (var sibling = node.previousSibling; sibling; sibling = sibling.previousSibling) {
// Ignore document type declaration.
if (sibling.nodeType == Node.DOCUMENT_TYPE_NODE)
continue;
if (sibling.nodeName == node.nodeName)
++index;
}
var tagName = (node.nodeType == 1 ? node.nodeName.toLowerCase() : "text()");
var pathIndex = (index ? "[" + (index+1) + "]" : "");
paths.splice(0, 0, tagName + pathIndex);
}
return paths.length ? "/" + paths.join("/") : null;
};
没有内置任何东西来获取 HTML 元素的 xpath,但相反的情况很常见,例如使用jQuery xpath 选择器。
如果需要确定 HTML 元素的 xpath,则必须提供自定义函数来执行此操作。 这里有几个示例 javascript/jQuery impls来计算 xpath。
如果您需要可靠地确定元素的绝对 XPath ,则以下解决方案更可取。
其他一些答案要么部分依赖于元素 id(这是不可靠的,因为可能有多个具有相同 id 的元素),要么它们生成的 XPaths 实际上指定了比给定元素更多的元素(在某些情况下错误地省略了同级索引) .
该代码已通过修复上述问题改编自 Firebug 的源代码。
getXElementTreeXPath = function( element ) {
var paths = [];
// Use nodeName (instead of localName) so namespace prefix is included (if any).
for ( ; element && element.nodeType == Node.ELEMENT_NODE; element = element.parentNode ) {
var index = 0;
for ( var sibling = element.previousSibling; sibling; sibling = sibling.previousSibling ) {
// Ignore document type declaration.
if ( sibling.nodeType == Node.DOCUMENT_TYPE_NODE ) {
continue;
}
if ( sibling.nodeName == element.nodeName ) {
++index;
}
}
var tagName = element.nodeName.toLowerCase();
// *always* include the sibling index
var pathIndex = "[" + (index+1) + "]";
paths.unshift( tagName + pathIndex );
}
return paths.length ? "/" + paths.join( "/") : null;
};
只是为了好玩,一个XPath 2.0 一行实现:
string-join(ancestor-or-self::*/concat(name(),
'[',
for $x in name()
return count(preceding-sibling::*
[name() = $x])
+ 1,
']'),
'/')
function getPath(event) {
event = event || window.event;
var pathElements = [];
var elem = event.currentTarget;
var index = 0;
var siblings = event.currentTarget.parentNode.getElementsByTagName(event.currentTarget.tagName);
for (var i=0, imax=siblings.length; i<imax; i++) {
if (event.currentTarget === siblings[i] {
index = i+1; // add 1 for xpath 1-based
}
}
while (elem.tagName.toLowerCase() != "html") {
pathElements.unshift(elem.tagName);
elem = elem.parentNode;
}
return pathElements.join("/") + "[" + index + "]";
}
编辑以添加兄弟索引信息
使用https://github.com/KajeNick/jquery-get-xpath
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="../src/jquery-get-xpath.js"></script>
<script>
jQuery(document).ready(function ($) {
$('body').on('click', 'ol li', function () {
let xPath = $(this).jGetXpath();
console.log(xPath);
});
});
</script>
控制台将显示: /html/body/ol/li[2]
我遇到过这个问题,发现很难完全解决。 就我而言,它给出了一半的 xpath。 所以我稍微修改了一下以提供完整路径。 这是我的答案。
window.onclick = (e) => {
let pathArr = e.path;
let element = pathArr[0];
var xpath = '';
if(pathArr.length<=2 && pathArr[0].nodeType!=1){
for (let i = 0; i < pathArr.length - 1 && pathArr[i].nodeType == 1; i++) {
element = pathArr[i];
var id = $(element.parentNode).children(element.tagName).index(element) + 1;
id > 1 ? (id = '[' + id + ']') : (id = '');
xpath = '/' + element.tagName.toLowerCase() + id + xpath;
}
}
else{
xpath="/html/document"
}
return xpath;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.