簡體   English   中英

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

[英]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;
}

小而強大的純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 添加墊片: 此 MDN 頁面提供了此類代碼。

用法

獲取節點的 xpath:
 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM