【问题标题】:How to calculate the XPath position of an element using Javascript?如何使用 Javascript 计算元素的 XPath 位置?
【发布时间】:2010-08-11 00:42:08
【问题描述】:

假设我有一个包含不同类型标签的大型 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>

假设您点击了苹果。 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

【问题讨论】:

  • XPath 使用从 1 开始的索引,所以它是 li[2]
  • 谢谢,我已经更改了代码。

标签: javascript jquery html xml xpath


【解决方案1】:

Firebug 可以做到这一点,而且它是开源的 (BSD),因此您可以重复使用 their implementation,它不需要任何库。

第三方编辑

这是上面链接源的摘录。以防万一上面的链接会改变。请查看源代码以受益于更改和更新或提供的完整功能集。

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

【讨论】:

  • 我喜欢 Firebug 实现 (getElementXPath),因为如果元素有 ID,它会优先使用 ID 而不是 xpath 树。
  • @OneWorld 是的,但它只检查您正在获取 xpath 的元素,而不是它的父母。看看我的回答,它会产生一个更像 google chrome 检查器的 xpath。
  • Firebug 的实现在line 1365 中存在一个错误:如果存在 previous 个相同类型的兄弟,它只会添加索引(例如“[3]”)。这是错误的,因为没有索引的 XPath 将匹配该类型的 所有 同级,甚至更远的同级。示例:/p/b 将匹配根目录下的 all p 标签下的 all b 标签。如果 Firebug 没有找到 previous 相同类型的同级,它只会跳过索引。
  • 包含此方法的文件是什么?我需要类似的东西,但萤火虫已移至 github,我找不到方法
  • 在当前版本中,可以在此处找到上述实现:github.com/firebug/firebug/blob/master/extension/content/…
【解决方案2】:

我用来获取与您的情况类似的 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;
}

【讨论】:

  • 效果也很好!谢谢
  • 第 7 行:从未见过这种语法,通常会这样写:id = id &gt; 1 ? ('[' + id + ']') : '';
  • 你是对的,我会像你一样写它,但我只是在复制/粘贴......我前段时间找到了这个脚本,从来没有费心清理它。不管怎样,两种写法都是等价的。
  • BUG:&lt;div/&gt;&lt;div/&gt; 的第一个标签返回 '/div' 而不是 '/div[1]'
【解决方案3】:

小而强大的纯js函数

它返回元素的 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 添加 shim:this MDN page 给出了这样的代码。

用法

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

【讨论】:

    【解决方案4】:

    可以稍微修改 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;
      };
    

    【讨论】:

    • 以下代码是否为以下 html 中的 div 返回正确的 xpath?
    【解决方案5】:

    我刚刚修改了 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;
    };
    

    【讨论】:

      【解决方案6】:

      没有任何内置功能可以获取 HTML 元素的 xpath,但反过来也很常见,例如使用 the jQuery xpath selector

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

      【讨论】:

      • 我实际上也在使用 jQuery 的 XPath 选择器,但需要一种让用户自己生成 XPath 的方法。您链接到的第二页有一些很好的例子。谢谢!
      • 示例 javascript/jQuery 链接不再可用
      【解决方案7】:

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

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

      【讨论】:

      • 我不确定它的作用,但它可能会在以后派上用场。谢谢!
      【解决方案8】:

      如果您需要可靠地确定元素的绝对 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;
      };
      

      【讨论】:

      • 这对我根本不起作用。我从 Range 对象传入一个 startContainer 节点,结果为空。
      【解决方案9】:
      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 + "]";
      }
      

      编辑添加兄弟索引信息

      【讨论】:

      • 感谢您的建议,但此代码似乎没有考虑到类似的兄弟节点。例如。代码返回 'BODY/OL/LI' 而不是 'BODY/OL/LI[2]'。
      • 并不是要挑剔,但兄弟索引信息对于我要解决的问题至关重要。无论如何,感谢您更新您的代码!
      • 这段代码有几个问题:(1)当计算同级索引时,它实际上检查是否有任何同级是事件的目标(而不是检查任何同级是否相同)节点类型作为事件目标),(2)它只考虑最深层次的兄弟索引信息,而不是更进一步的树。
      【解决方案10】:

      使用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]

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-08-20
        • 1970-01-01
        • 2017-01-07
        • 1970-01-01
        • 1970-01-01
        • 2017-02-06
        • 1970-01-01
        • 2017-11-22
        相关资源
        最近更新 更多