简体   繁体   English

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

[英]How to calculate the XPath position of an element using Javascript?

Let's say I have a large HTML file with different kinds of tags, similar to the StackOverflow one you're looking at right now.假设我有一个包含不同类型标签的大型 HTML 文件,类似于您现在正在查看的 StackOverflow 文件。

Now let's say you click an element on the page, what would the Javascript function look like that calculates the most basic XPath that refers to that specific element?现在假设您单击页面上的一个元素,计算引用该特定元素的最基本 XPath 的 Javascript 函数会是什么样子?

I know there are an infinite ways of refering to that element in XPath, but I'm looking for something that just looks at the DOM tree, with no regard for IDs, classes, etc.我知道在 XPath 中有无数种引用该元素的方法,但我正在寻找只查看 DOM 树的东西,而不考虑 ID、类等。

Example:例子:

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

Let's say you click on Apples .假设您点击了Apples The Javascript function would return the following: Javascript 函数将返回以下内容:

/html/body/ol/li[2]

It would basically just work its way upward the DOM tree all the way to the HTML element.它基本上会沿着 DOM 树向上一直工作到 HTML 元素。

Just to clarify, the 'on-click' event-handler isn't the problem.澄清一下,“单击”事件处理程序不是问题所在。 I can make that work.我可以做到这一点。 I'm just not sure how to calculate the element's position within the DOM tree and represent it as an XPath.我只是不确定如何计算元素在 DOM 树中的位置并将其表示为 XPath。

PS Any answer with or without the use of the JQuery library is appreciated. PS 任何使用或不使用 JQuery 库的答案都值得赞赏。

PPS I completely new to XPath, so I might even have made a mistake in the above example, but you'll get the idea. PPS 我对 XPath 完全陌生,所以我什至可能在上面的例子中犯了错误,但你会明白的。

Edit at August 11, 2010: Looks like somebody else asked a similar question: generate/get the Xpath for a selected textnode 2010 年 8 月 11 日编辑:看起来有人问了类似的问题: generate/get the Xpath for a selected textnode

Firebug can do this, and it's open source ( BSD ) so you can reuse their implementation , which does not require any libraries. Firebug 可以做到这一点,它是开源的 ( BSD ),因此您可以重用它们的实现,不需要任何库。

3rd party edit第三者编辑

This is an extract from the linked source above.这是上面链接源的摘录。 Just in case the link above will change.以防万一上面的链接会改变。 Please check the source to benefit from changes and updates or the full featureset provided.请检查来源以从更改和更新或提供的完整功能集中获益。

Xpath.getElementXPath = function(element)
{
    if (element && element.id)
        return '//*[@id="' + element.id + '"]';
    else
        return Xpath.getElementTreeXPath(element);
};

Above code calls this function.上面的代码调用了这个函数。 Attention i added some line-wrapping to avoid horizontal scroll bar注意我添加了一些换行以避免水平滚动条

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

A function I use to get an XPath similar to your situation, it uses jQuery:我用来获取与您的情况类似的 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;
}

Small, powerfull and pure-js function小而强大的纯js函数

It returns xpath for the element and elements iterator for xpath.它返回元素的 xpath 和 xpath 的元素迭代器。

https://gist.github.com/iimos/e9e96f036a3c174d0bf4 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)+']' : '')
}

Probably you will need to add a shim for IE8 that don't support the [].filter method: this MDN page gives such code.可能您需要为不支持 [].filter 方法的 IE8 添加垫片: 此 MDN 页面提供了此类代码。

Usage用法

Getting xpath for node: 获取节点的 xpath:
 var xp = xpath(elementNode)
Executing xpath: 执行 xpath:
 var iterator = xpath("//h2") var el = iterator.iterateNext(); while (el) { // work with element el = iterator.iterateNext(); }

The firebug implementation can be modified slightly to check for element.id further up the dom tree:可以稍微修改 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;
  };

I have just modified DanS' solution in order to use it with textNodes.我刚刚修改了 DanS 的解决方案,以便将其与文本节点一起使用。 Very useful to serialize HTML range object.序列化 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;
};

There is nothing built in to get the xpath of an HTML element, but the reverse is common for example usingthe jQuery xpath selector .没有内置任何东西来获取 HTML 元素的 xpath,但相反的情况很常见,例如使用jQuery xpath 选择器

If you need to determine the xpath of an HTML element you will have to provide a custom function to do this.如果需要确定 HTML 元素的 xpath,则必须提供自定义函数来执行此操作。 Here are a couple of example javascript/jQuery impls to calculate the xpath.这里有几个示例 javascript/jQuery impls来计算 xpath。

The solution below is preferable if you need to reliably determine the absolute XPath of an element.如果您需要可靠地确定元素的绝对 XPath ,则以下解决方案更可取。

Some other answers either rely partly on the element id (which is not reliable since there can potentially be multiple elements with identical ids) or they generate XPaths that actually specify more elements than the one given (by erroneously omitting the sibling index in certain circumstances).其他一些答案要么部分依赖于元素 id(这是不可靠的,因为可能有多个具有相同 id 的元素),要么它们生成的 XPaths 实际上指定了比给定元素更多的元素(在某些情况下错误地省略了同级索引) .

The code has been adapted from Firebug's source code by fixing the above-mentioned problems.该代码已通过修复上述问题改编自 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;
};

Just for fun, an XPath 2.0 one line implementation:只是为了好玩,一个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 + "]";
}

EDITED TO ADD SIBLING INDEX INFORMATION编辑以添加兄弟索引信息

Use https://github.com/KajeNick/jquery-get-xpath使用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>

Console will show: /html/body/ol/li[2]控制台将显示: /html/body/ol/li[2]

I have came across this problem and found it hard to solve fully.我遇到过这个问题,发现很难完全解决。 as in my case it was giving half xpath.就我而言,它给出了一半的 xpath。 so i modified it a little to give full path.所以我稍微修改了一下以提供完整路径。 here is my answer.这是我的答案。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM