2010-04-18 6 views

ответ

27

Не существует уникального XPath для узла, поэтому вам придется решить, что является наиболее подходящим способом построения пути. Использовать идентификаторы, если они доступны? Позиция позиции в документе? Позиция относительно других элементов?

См. getPathTo() в this answer для одного возможного подхода.

+1

Эй, спасибо, что выглядит как хорошая функция. Я сделал другой вопрос более подходящим и более контекстом: http://stackoverflow.com/questions/2661918/javascript-crazy-idea-finding-a-node. В ретроспективе я должен был отредактировать этот ... oops lol. – Louis

+0

+1 «Не существует уникального XPath для узла» (и возможных альтернатив). – dakab

+0

XPath определен как путь к узлу из корня документа. –

61

Я реорганизовал это из другого примера. Он попытается проверить или, безусловно, уникальный идентификатор, и если это так, используйте этот случай, чтобы сократить выражение.

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

Протестировали XPaths эти продукты, используя объекты DOMDocument и DOMXPath от PHP - они, похоже, работают очень хорошо. – tonyhb

+0

Это замечательно! Я искал что-то вроде этого какое-то время, и это действительно самое полное решение, которое я видел. У тебя есть +1. Благодаря! –

+1

'segs' становится глобальной переменной здесь. – mattsven

2

Аналогичное решение задается функцией getXPathForElement на 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 Также может быть стоит попробовать.

+0

Ссылка getXPathForElement не ссылается на функцию getXPathForElement? – TheBlastOne

5

Вот функциональное программирование стиля функция 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.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>

будет использовать селектор id, если элемент не является первым с этим идентификатором. Селекторы классов не используются, потому что в интерактивных веб-страницах классы могут часто меняться.

0

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>

+1

Вам следует добавить объяснение в свой ответ. – Sand