Javascript获取节点的XPath

IT技术 javascript dom xpath
2021-01-29 08:18:10

无论如何要在Javascript中返回DOM元素的XPath字符串?

6个回答

我从另一个例子重构了这个。它将尝试检查或确定是否有唯一的 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; 
} 
这很棒!我一直在寻找这样的东西,这确实是我见过的最完整的解决方案。你得到了我的 +1。谢谢!
2021-03-14 08:18:10
这在此页面上不起作用,例如 1. 在 Chrome 开发工具中,单击未选择的 DOM 元素以获取该价格不是第一个列出的价格的价格。将该元素保存到变量中。2. 在那个元素上运行算法。3. 它仅将您带回到该窗格中的第一个元素。
2021-03-17 08:18:10
不完全准确,因为当两个兄弟姐妹具有相同的class属性时,第一个总是会被选中......
2021-04-03 08:18:10
segs 在这里成为全局变量。
2021-04-05 08:18:10
已经使用 PHP 的 DOMDocument 和 DOMXPath 对象测试了这些生成的 XPath——它们似乎工作得很好。
2021-04-06 08:18:10

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

getPathTo()这个答案的一个可能的方法。

我认为“没有唯一的 XPath”意味着“有很多方法可以给这只猫剥皮”。
2021-03-12 08:18:10
+1 𝑓𝑜𝑟 “节点没有唯一的 XPath”(以及可行的替代方案)。
2021-03-25 08:18:10
嘿,谢谢,看起来不错的功能。我做了另外一个问题更合适,更方面:stackoverflow.com/questions/2661918/...回想起来,我应该编辑这个...oop大声笑。
2021-04-05 08:18:10
XPath 被很好地定义为从文档根节点到节点的路径。
2021-04-08 08:18:10

这是该作业函数式编程风格 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 的元素。不使用类选择器,因为在交互式网页中,类可能经常更改。

只是想指出Rohit Luthra's对此分析仪的修改以解决 SVG 元素。stackoverflow.com/a/55793129/3917091
2021-03-18 08:18:10
而不是document.querySelector(`#${elm.id}`)仅仅使用,document.getElementById(elm.id)因为当 id 仅包含数字时,前者在 Chrome 上失败。
2021-03-25 08:18:10

我已经调整了Chromium 用于从下面的 devtools 计算 XPath算法

要使用这个原样,您可以调用Elements.DOMPath.xPath(<some DOM node>, false). 最后一个参数控制您是否获得较短的“复制 XPath”(如果true)或“复制完整 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;
    }
};
您提供的链接不再有效,我认为他们可能更新了源代码,因为我下载了最新版本并且找不到此代码的任何痕迹。但是,您提供的代码做得很好!你能告诉我你找到了哪个文件的原始代码吗?
2021-03-19 08:18:10
如果我们想要获取类而不是 xpath 怎么办?你能在这里分享我的代码stackoverflow.com/questions/60524774/...吗?@dcmorse
2021-03-21 08:18:10
@dcmorse,您能否提供一段简短的代码块,说明如何使用您在上面发布的代码,谢谢:),(非 js 开发人员)
2021-03-30 08:18:10
用github镜像交换了链接,现在应该可以工作了。
2021-04-05 08:18:10

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>

您应该在答案中添加解释。
2021-03-23 08:18:10