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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3454526/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 04:41:32  来源:igfitidea点击:

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

javascriptjqueryhtmlxmlxpath

提问by Marc

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. The Javascript function would return the following:

假设您单击Apples。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 日编辑:看起来其他人问了类似的问题:为选定的文本节点生成/获取 Xpath

采纳答案by Matthew Flaschen

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

回答by JCD

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

回答by imos

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 pagegives such code.

可能您需要为不支持 [].filter 方法的 IE8 添加一个 shim:这个 MDN 页面提供了这样的代码。

Usage

用法

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

回答by DanS

The firebug implementation can be modified slightly to check for element.id further up the dom tree:

可以稍微修改 firebug 实现以检查 dom 树中的 element.id:

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

回答by Slabko

I have just modified DanS' solution in order to use it with textNodes. Very useful to serialize HTML range object.

我刚刚修改了 DanS 的解决方案,以便将它与 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;
};

回答by krock

There is nothing built in to get the xpath of an HTML element, but the reverse is common for example using the 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. Here are a couple of example javascript/jQuery implsto calculate the xpath.

如果需要确定 HTML 元素的 xpath,则必须提供自定义函数来执行此操作。这里有几个示例 javascript/jQuery impls来计算 xpath。

回答by wadim

The solution below is preferable if you need to reliably determine the absolute XPathof 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 的元素),或者它们生成的 XPath 实际指定的元素比给定的元素多(在某些情况下错误地省略了同级索引) .

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

回答by wadim

Just for fun, an XPath 2.0 one lineimplementation:

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

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

回答by NSukonny

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]

回答by Robusto

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

编辑添加兄弟索引信息