Javascript获得一个节点的XPath

反正有没有在Javascript中返回一个DOM元素的XPathstring?

没有一个独特的XPath节点,所以你必须决定什么是构buildpath的最合适的方法。 在可用的地方使用ID? 文件中的数字位置? 相对于其他元素的位置?

请参阅getPathTo()在这个答案一个可能的方法。

我从另一个例子重构了这个。 它会尝试检查或确定一个唯一的ID,如果是的话,使用这种情况来缩短expression式。

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

这是一个function性的编程风格ES6function:

 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.querySelector(`#${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> 

MDN上的函数getXPathForElement提供了类似的解决scheme:

 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可能值得一试。

 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>