如何计算使用Javascript的元素的XPath位置?

比方说,我有一个大型的HTML文件,其中包含不同types的标签,类似于您现在正在查看的StackOverflow。

现在让我们假设你单击页面上的一个元素,那么Javascript函数看起来是什么样子,它会计算出引用该特定元素的最基本的XPath?

我知道有一种在XPath中引用这个元素的无限方式,但是我正在寻找一些只看DOM树的东西,而不考虑ID,类等等。

例:

<html> <head><title>Fruit</title></head> <body> <ol> <li>Bananas</li> <li>Apples</li> <li>Strawberries</li> </ol> </body> </html> 

假设你点击苹果 。 Javascript函数将返回以下内容:

 /html/body/ol/li[2] 

它基本上只是向上到DOM树一直到HTML元素。

只是为了澄清,“点击”事件处理程序不是问题。 我可以做这个工作。 我只是不知道如何计算DOM树中的元素的位置,并将其表示为XPath。

PS有或没有使用JQuery库的任何答案表示赞赏。

PPS我对XPath完全陌生,所以我甚至可能在上面的例子中犯了一个错误,但是你会明白的。

在2010年8月11日编辑:看起来像别人问了一个类似的问题: 生成/获取选定文本节点的Xpath

Firebug可以做到这一点,它是开源的( BSD ),所以你可以重用他们的实现 ,这不需要任何库。

第三方编辑

这是来自上面链接源的摘录。 以防万一上面的链接会改变。 请检查源代码以从更改和更新中受益,或者提供完整的function集。

 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; } 

可以稍微修改萤火虫实施来检查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的解决scheme,以便与textNodes一起使用。 序列化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; }; 

小而强大的纯jsfunction

它返回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)+']' : '') } 

可能你需要为IE8添加一个不支持[] .filter方法的填充程序: 这个MDN页面给出了这样的代码。

用法

获取节点的xpath:

 var xp = xpath(elementNode) 

执行xpath:

 var iterator = xpath("//h2") var el = iterator.iterateNext(); while (el) { // work with element el = iterator.iterateNext(); } 

没有任何内容可以获取HTML元素的xpath,但是相反,例如使用jQuery xpathselect器则很常见。

如果您需要确定HTML元素的xpath,您将不得不提供一个自定义函数来执行此操作。 这里有几个例子javascript / jQuery impls来计算xpath。

只是为了好玩,一个XPath 2.0一行的实现:

 string-join(ancestor-or-self::*/concat(name(), '[', for $x in name() return count(preceding-sibling::* [name() = $x]) + 1, ']'), '/') 

如果您需要可靠地确定元素的绝对XPath ,则下面的解决scheme是可取的。

其他一些答案要么部分地依赖于元素id(这是不可靠的,因为可能有多个具有相同id的元素),或者他们生成的XPath实际上指定的元素多于给定的元素(在某些情况下错误地忽略了兄弟指数) 。

通过解决上述问题,代码已经从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; }; 
 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 + "]"; } 

编辑添加同胞索引信息

看看我的例子,至less会尝试缩短expression式,如果有一个唯一的ID。 Javascript获得一个节点的XPath