web-dev-qa-db-de.com

Javascript bekommt XPath eines Knotens

Gibt es überhaupt eine XPath-Zeichenfolge eines DOM-Elements in Javascript zurückzugeben?

51
Louis

Es gibt keinen eindeutigen XPath für einen Knoten, daher müssen Sie entscheiden, wie ein Pfad am besten erstellt werden kann. IDs verwenden, wo verfügbar? Ziffernposition im Dokument? Position relativ zu anderen Elementen?

Siehe getPathTo() in diese Antwort für einen möglichen Ansatz.

34
bobince

Ich habe dies anhand eines anderen Beispiels überarbeitet. Es wird versucht zu überprüfen, ob eine eindeutige ID vorhanden ist, und in diesem Fall wird der Ausdruck verkürzt.

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; 
} 
69
stijn de ryck

Hier ist eine funktionale Programmierung Style ES6-Funktion für den Job:

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>

Es wird ein Selektor id verwendet, es sei denn, das Element ist nicht das erste mit dieser ID. Klassenselektoren werden nicht verwendet, da sich Klassen auf interaktiven Webseiten häufig ändern können.

11
trincot

Eine ähnliche Lösung bietet die Funktion getXPathForElement im 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;
}

Auch XMLSerializer könnte einen Versuch wert sein.

3
axsk
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>
2
fenghen

Übergeben Sie einfach das Element in der Funktion getXPathOfElement und Sie erhalten das 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;
}
0
Rohit Luthra