web-dev-qa-db-fra.com

Javascript récupère XPath d'un nœud

Est-il possible de renvoyer une chaîne XPath d'un élément DOM en Javascript?

51
Louis

Il n'y a pas un XPath unique à un nœud, vous devrez donc décider quelle est la façon la plus appropriée de construire un chemin. Utiliser des identifiants lorsqu'ils sont disponibles? Position numérique dans le document? Position par rapport aux autres éléments?

Voir getPathTo() dans cette réponse pour une approche possible.

34
bobince

J'ai refactorisé cela à partir d'un autre exemple. Il tentera de vérifier ou il y a à coup sûr un identifiant unique et si c'est le cas, utilisez ce cas pour raccourcir l'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; 
} 
69
stijn de ryck

Voici une fonction programmation fonctionnelle style ES6 pour le travail:

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>

Il utilisera un sélecteur id, sauf si l'élément n'est pas le premier avec cet identifiant. Les sélecteurs de classe ne sont pas utilisés, car dans les pages Web interactives, les classes peuvent changer souvent.

11
trincot

Une solution similaire est donnée par la fonction getXPathForElement sur le 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;
}

XMLSerializer pourrait également valoir la peine d'être essayé.

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

Passez simplement l'élément dans la fonction getXPathOfElement et vous obtiendrez le 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