locator - xpath javascript tutorial
Javascript obtiene XPath de un nodo (5)
¿Hay alguna forma de devolver una cadena XPath de un elemento DOM en Javascript?
Aquí hay una función funcional de la función de programación ES6 para el trabajo:
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>
Utilizará un selector de id
, a menos que el elemento no sea el primero con esa identificación. Los selectores de clase no se utilizan, porque en las páginas web interactivas las clases pueden cambiar con frecuencia.
No hay un XPath exclusivo para un nodo, por lo que tendrá que decidir cuál es la forma más adecuada de construir un camino. Usa identificadores cuando estén disponibles? Posición numérica en el documento? Posición relativa a otros elementos?
Ver getPathTo()
en esta respuesta para un posible enfoque.
Refactoré esto de otro ejemplo. Intentará verificar o hay una identidad única y, en ese caso, use ese caso para acortar la expresión.
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;
}
Una solución similar viene dada por la función getXPathForElement en el MDN:
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;
}
También XMLSerializer podría valer la pena intentarlo.
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>