【问题标题】:Javascript get XPath of a nodeJavascript 获取节点的 XPath
【发布时间】:2011-02-09 08:21:20
【问题描述】:

是否有在 Javascript 中返回 DOM 元素的 XPath 字符串?

【问题讨论】:

    标签: javascript dom xpath


    【解决方案1】:

    我检查了此处提供的所有解决方案,但没有一个适用于 svg 元素(代码 getElementByXPath(getXPathForElement(elm)) === elm 返回 false 用于 svgpath 元素)

    所以我将Touko's svg fix 添加到trincot's solution 并得到了这个代码:

    function getXPathForElement(element) {
        const idx = (sib, name) => sib 
            ? idx(sib.previousElementSibling, name||sib.localName) + (sib.localName == name)
            : 1;
        const segs = elm => !elm || elm.nodeType !== 1 
            ? ['']
            : elm.id && document.getElementById(elm.id) === elm
                ? [`id("${elm.id}")`]
                : [...segs(elm.parentNode), elm instanceof HTMLElement
                    ? `${elm.localName}[${idx(elm)}]`
                    : `*[local-name() = "${elm.localName}"][${idx(elm)}]`];
        return segs(element).join('/');
    }
    

    不同之处在于,如果元素不是 HTMLElement 的实例(svg 是 SVGElement 但我决定不坚持只检查 svg),它返回 *[local-name() = "tag"][n] 而不是 tag[n]

    示例:

    之前:
    .../div[2]/div[2]/span[1]/svg[1]/path[1]

    之后:
    .../div[2]/div[2]/span[1]/*[local-name() = "svg"][1]/*[local-name() = "path"][1]

    【讨论】:

      【解决方案2】:

      我已经调整了 algorithm Chromium uses 以从下面的 devtools 计算 XPath。

      要使用这种书面形式,您可以致电Elements.DOMPath.xPath(<some DOM node>, false)。最后一个参数控制您是获得较短的“Copy XPath”(如果是true)还是“Copy full XPath”。

      // Copyright 2018 The Chromium Authors. All rights reserved.
      // Use of this source code is governed by a BSD-style license that can be
      // found in the LICENSE file.
      
      Elements = {};
      Elements.DOMPath = {};
      
      /**
       * @param {!Node} node
       * @param {boolean=} optimized
       * @return {string}
       */
      Elements.DOMPath.xPath = function (node, optimized) {
          if (node.nodeType === Node.DOCUMENT_NODE) {
              return '/';
          }
      
          const steps = [];
          let contextNode = node;
          while (contextNode) {
              const step = Elements.DOMPath._xPathValue(contextNode, optimized);
              if (!step) {
                  break;
              }  // Error - bail out early.
              steps.push(step);
              if (step.optimized) {
                  break;
              }
              contextNode = contextNode.parentNode;
          }
      
          steps.reverse();
          return (steps.length && steps[0].optimized ? '' : '/') + steps.join('/');
      };
      
      /**
       * @param {!Node} node
       * @param {boolean=} optimized
       * @return {?Elements.DOMPath.Step}
       */
      Elements.DOMPath._xPathValue = function (node, optimized) {
          let ownValue;
          const ownIndex = Elements.DOMPath._xPathIndex(node);
          if (ownIndex === -1) {
              return null;
          }  // Error.
      
          switch (node.nodeType) {
              case Node.ELEMENT_NODE:
                  if (optimized && node.getAttribute('id')) {
                      return new Elements.DOMPath.Step('//*[@id="' + node.getAttribute('id') + '"]', true);
                  }
                  ownValue = node.localName;
                  break;
              case Node.ATTRIBUTE_NODE:
                  ownValue = '@' + node.nodeName;
                  break;
              case Node.TEXT_NODE:
              case Node.CDATA_SECTION_NODE:
                  ownValue = 'text()';
                  break;
              case Node.PROCESSING_INSTRUCTION_NODE:
                  ownValue = 'processing-instruction()';
                  break;
              case Node.COMMENT_NODE:
                  ownValue = 'comment()';
                  break;
              case Node.DOCUMENT_NODE:
                  ownValue = '';
                  break;
              default:
                  ownValue = '';
                  break;
          }
      
          if (ownIndex > 0) {
              ownValue += '[' + ownIndex + ']';
          }
      
          return new Elements.DOMPath.Step(ownValue, node.nodeType === Node.DOCUMENT_NODE);
      };
      
      /**
       * @param {!Node} node
       * @return {number}
       */
      Elements.DOMPath._xPathIndex = function (node) {
          // Returns -1 in case of error, 0 if no siblings matching the same expression,
          // <XPath index among the same expression-matching sibling nodes> otherwise.
          function areNodesSimilar(left, right) {
              if (left === right) {
                  return true;
              }
      
              if (left.nodeType === Node.ELEMENT_NODE && right.nodeType === Node.ELEMENT_NODE) {
                  return left.localName === right.localName;
              }
      
              if (left.nodeType === right.nodeType) {
                  return true;
              }
      
              // XPath treats CDATA as text nodes.
              const leftType = left.nodeType === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : left.nodeType;
              const rightType = right.nodeType === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : right.nodeType;
              return leftType === rightType;
          }
      
          const siblings = node.parentNode ? node.parentNode.children : null;
          if (!siblings) {
              return 0;
          }  // Root node - no siblings.
          let hasSameNamedElements;
          for (let i = 0; i < siblings.length; ++i) {
              if (areNodesSimilar(node, siblings[i]) && siblings[i] !== node) {
                  hasSameNamedElements = true;
                  break;
              }
          }
          if (!hasSameNamedElements) {
              return 0;
          }
          let ownIndex = 1;  // XPath indices start with 1.
          for (let i = 0; i < siblings.length; ++i) {
              if (areNodesSimilar(node, siblings[i])) {
                  if (siblings[i] === node) {
                      return ownIndex;
                  }
                  ++ownIndex;
              }
          }
          return -1;  // An error occurred: |node| not found in parent's children.
      };
      
      /**
       * @unrestricted
       */
      Elements.DOMPath.Step = class {
          /**
           * @param {string} value
           * @param {boolean} optimized
           */
          constructor(value, optimized) {
              this.value = value;
              this.optimized = optimized || false;
          }
      
          /**
           * @override
           * @return {string}
           */
          toString() {
              return this.value;
          }
      };
      

      【讨论】:

      • 你提供的链接已经失效了,我想他们可能更新了源代码,因为我下载了最新版本,找不到该代码的任何痕迹。但是,您提供的代码做得很好!您能否告知您在哪个文件中找到了原始代码?
      • 用github镜像交换了链接,现在应该可以了。
      • 如果我们想要获取类而不是 xpath 怎么办?你能在这里分享代码stackoverflow.com/questions/60524774/…吗? @dcmorse
      • @dcmorse,能否请您提供一小段代码说明如何使用您在上面发布的代码,谢谢 :),(非 js 开发人员)
      • @octopus ,只需粘贴上面的代码并传递元素 window.onclick = function (e) { // alert(e.target) x = Elements.DOMPath.xPath(e.target) alert (x) }
      【解决方案3】:

      这是一个函数式编程风格的ES6函数:

      function getXPathForElement(element) {
          const idx = (sib, name) => sib 
              ? idx(sib.previousElementSibling, name||sib.localName) + (sib.localName == name)
              : 1;
          const segs = elm => !elm || elm.nodeType !== 1 
              ? ['']
              : elm.id && document.getElementById(elm.id) === elm
                  ? [`id("${elm.id}")`]
                  : [...segs(elm.parentNode), `${elm.localName.toLowerCase()}[${idx(elm)}]`];
          return segs(element).join('/');
      }
      
      function getElementByXPath(path) { 
          return (new XPathEvaluator()) 
              .evaluate(path, document.documentElement, null, 
                              XPathResult.FIRST_ORDERED_NODE_TYPE, null) 
              .singleNodeValue; 
      } 
      
      // Demo:
      const li = document.querySelector('li:nth-child(2)');
      const path = getXPathForElement(li);
      console.log(path);
      console.log(li === getElementByXPath(path)); // true
      <div>
          <table id="start"></table>
          <div>
              <ul><li>option</ul></ul> 
              <span>title</span>
              <ul>
                  <li>abc</li>
                  <li>select this</li>
              </ul>
          </div>
      </div>

      它将使用id 选择器,除非该元素不是第一个具有该ID 的元素。不使用类选择器,因为在交互式网页中,类可能经常更改。

      【讨论】:

      • 而不是 document.querySelector(`#${elm.id}`) 只使用 document.getElementById(elm.id) 因为前者在 id 仅包含数字时在 Chrome 上失败。
      • 只是想指出 Rohit Luthra's 对此分析器的修改以解决 SVG 元素。 stackoverflow.com/a/55793129/3917091.
      【解决方案4】:

      只需在函数getXPathOfElement 中传递元素,您就会得到Xpath

      function getXPathOfElement(elt)
      {
           var path = "";
           for (; elt && elt.nodeType == 1; elt = elt.parentNode)
           {
          idx = getElementIdx(elt);
          xname = elt.tagName;
          if (idx > 1) xname += "[" + idx + "]";
          path = "/" + xname + path;
           }
      
           return path;   
      }
      function getElementIdx(elt)
      {
          var count = 1;
          for (var sib = elt.previousSibling; sib ; sib = sib.previousSibling)
          {
              if(sib.nodeType == 1 && sib.tagName == elt.tagName) count++
          }
      
          return count;
      }
      

      【讨论】:

        【解决方案5】:

        function getElementXPath (element) {
          if (!element) return null
        
          if (element.id) {
            return `//*[@id=${element.id}]`
          } else if (element.tagName === 'BODY') {
            return '/html/body'
          } else {
            const sameTagSiblings = Array.from(element.parentNode.childNodes)
              .filter(e => e.nodeName === element.nodeName)
            const idx = sameTagSiblings.indexOf(element)
        
            return getElementXPath(element.parentNode) +
              '/' +
              element.tagName.toLowerCase() +
              (sameTagSiblings.length > 1 ? `[${idx + 1}]` : '')
          }
        }
        
        console.log(getElementXPath(document.querySelector('#a div')))
        <div id="a">
         <div>def</div>
        </div>

        【讨论】:

        • 你应该在你的答案中添加一个解释。
        【解决方案6】:

        MDN上的函数getXPathForElement给出了类似的解决方案:

        function getXPathForElement(el, xml) {
            var xpath = '';
            var pos, tempitem2;
        
            while(el !== xml.documentElement) {     
                pos = 0;
                tempitem2 = el;
                while(tempitem2) {
                    if (tempitem2.nodeType === 1 && tempitem2.nodeName === el.nodeName) { // If it is ELEMENT_NODE of the same name
                        pos += 1;
                    }
                    tempitem2 = tempitem2.previousSibling;
                }
        
                xpath = "*[name()='"+el.nodeName+"' and namespace-uri()='"+(el.namespaceURI===null?'':el.namespaceURI)+"']["+pos+']'+'/'+xpath;
        
                el = el.parentNode;
            }
            xpath = '/*'+"[name()='"+xml.documentElement.nodeName+"' and namespace-uri()='"+(el.namespaceURI===null?'':el.namespaceURI)+"']"+'/'+xpath;
            xpath = xpath.replace(/\/$/, '');
            return xpath;
        }
        

        XMLSerializer 也值得一试。

        【讨论】:

          【解决方案7】:

          我从另一个例子中重构了这个。它将尝试检查或确定是否存在唯一 id,如果是,则使用这种情况来缩短表达式。

          function createXPathFromElement(elm) { 
              var allNodes = document.getElementsByTagName('*'); 
              for (var segs = []; elm && elm.nodeType == 1; elm = elm.parentNode) 
              { 
                  if (elm.hasAttribute('id')) { 
                          var uniqueIdCount = 0; 
                          for (var n=0;n < allNodes.length;n++) { 
                              if (allNodes[n].hasAttribute('id') && allNodes[n].id == elm.id) uniqueIdCount++; 
                              if (uniqueIdCount > 1) break; 
                          }; 
                          if ( uniqueIdCount == 1) { 
                              segs.unshift('id("' + elm.getAttribute('id') + '")'); 
                              return segs.join('/'); 
                          } else { 
                              segs.unshift(elm.localName.toLowerCase() + '[@id="' + elm.getAttribute('id') + '"]'); 
                          } 
                  } else if (elm.hasAttribute('class')) { 
                      segs.unshift(elm.localName.toLowerCase() + '[@class="' + elm.getAttribute('class') + '"]'); 
                  } else { 
                      for (i = 1, sib = elm.previousSibling; sib; sib = sib.previousSibling) { 
                          if (sib.localName == elm.localName)  i++; }; 
                          segs.unshift(elm.localName.toLowerCase() + '[' + i + ']'); 
                  }; 
              }; 
              return segs.length ? '/' + segs.join('/') : null; 
          }; 
          
          function lookupElementByXPath(path) { 
              var evaluator = new XPathEvaluator(); 
              var result = evaluator.evaluate(path, document.documentElement, null,XPathResult.FIRST_ORDERED_NODE_TYPE, null); 
              return  result.singleNodeValue; 
          } 
          

          【讨论】:

          • 已经测试了这些使用 PHP 的 DOMDocument 和 DOMXPath 对象生成的 XPath——它们似乎工作得很好。
          • 这太棒了!我一直在寻找类似的东西,这确实是我见过的最完整的解决方案。你得到了我的 +1。谢谢!
          • segs 在这里变成了一个全局变量。
          • 这不适用于this page,例如1. 在 Chrome 开发工具中,单击未选择的 DOM 元素以获取价格不是第一个列出的价格。将该元素保存到变量中。 2. 在那个元素上运行算法。 3. 它只带您回到该窗格中的第一个元素。
          • 不完全准确,因为当两个兄弟姐妹具有相同的class 属性时,第一个总是会被选中...
          【解决方案8】:

          节点没有唯一的 XPath,因此您必须确定构建路径的最合适方式是什么。在可用的情况下使用 ID?数字在文档中的位置?相对于其他元素的位置?

          请参阅this answer 中的getPathTo() 了解一种可能的方法。

          【讨论】:

          • 嘿,谢谢,这个功能看起来不错。我提出了另一个更合适和更多背景的问题:stackoverflow.com/questions/2661918/…。回想起来,我应该编辑这个...哎呀哈哈。
          • +1 ???“节点没有唯一的 XPath”(以及可行的替代方案)。
          • XPath 被明确定义为从文档根目录到节点的路径。
          • 我将“没有唯一的 XPath”理解为“给这只猫剥皮的方法有很多”。
          猜你喜欢
          • 2015-03-30
          • 2023-03-27
          • 1970-01-01
          • 2012-09-17
          • 1970-01-01
          • 2016-02-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多