Jak obliczyć pozycję XPath elementu za pomocą Javascript?

Załóżmy, że mam duży plik HTML z różnymi rodzajami tagów, podobny do tego, na który patrzysz w tej chwili.

Załóżmy teraz, że klikasz element na stronie, jak wyglądałaby funkcja Javascript, która oblicza najbardziej podstawową ścieżkę XPath, która odnosi się do tego konkretnego elementu?

Wiem, że istnieje nieskończona ilość sposobów odniesienia się do tego elementu w XPath, ale szukam czegoś, co po prostu patrzy na drzewo DOM, bez względu na Id, klasy, itd.

Przykład:

<html>
<head><title>Fruit</title></head>
<body>
<ol>
  <li>Bananas</li>
  <li>Apples</li>
  <li>Strawberries</li>
</ol>
</body>
</html>

Powiedzmy, że klikasz na jabłka. Funkcja Javascript zwróci:

/html/body/ol/li[2]

To w zasadzie działa w górę drzewa DOM aż do elementu HTML.

Dla jasności, obsługa zdarzeń "on-click" nie stanowi problemu. Dam radę. Po prostu nie jestem pewien, jak obliczyć pozycję elementu w drzewie DOM i przedstawić go jako XPath.

PS Każda odpowiedź z lub bez korzystanie z biblioteki JQuery jest doceniane.

PPS Jestem zupełnie nowy w XPath, więc może nawet popełniłem błąd w powyższym przykładzie, ale wpadniesz na pomysł.

Edit at August 11, 2010: wygląda na to, że ktoś inny zadał podobne pytanie: Wygeneruj / Pobierz Xpath dla wybranego textnode

Author: Community, 2010-08-11

10 answers

Firebug może to zrobić, i jest to open source ( BSD ), więc możesz ponownie użyć ich implementacji, która nie wymaga żadnych bibliotek.

3rd party edit

To jest wyciąg z linkowanego źródła powyżej. Na wszelki wypadek powyższy link się zmieni. Sprawdź źródło, aby skorzystać ze zmian i aktualizacji lub pełnego zestawu funkcji.

Xpath.getElementXPath = function(element)
{
    if (element && element.id)
        return '//*[@id="' + element.id + '"]';
    else
        return Xpath.getElementTreeXPath(element);
};

Powyższy kod wywołuje tę funkcję. Uwaga dodałem trochę zawijania linii, aby uniknąć poziomego przewijania bar

Xpath.getElementTreeXPath = function(element)
{
    var paths = [];  // Use nodeName (instead of localName) 
    // so namespace prefix is included (if any).
    for (; element && element.nodeType == Node.ELEMENT_NODE; 
           element = element.parentNode)
    {
        var index = 0;
        var hasFollowingSiblings = false;
        for (var sibling = element.previousSibling; sibling; 
              sibling = sibling.previousSibling)
        {
            // Ignore document type declaration.
            if (sibling.nodeType == Node.DOCUMENT_TYPE_NODE)
                continue;

            if (sibling.nodeName == element.nodeName)
                ++index;
        }

        for (var sibling = element.nextSibling; 
            sibling && !hasFollowingSiblings;
            sibling = sibling.nextSibling)
        {
            if (sibling.nodeName == element.nodeName)
                hasFollowingSiblings = true;
        }

        var tagName = (element.prefix ? element.prefix + ":" : "") 
                          + element.localName;
        var pathIndex = (index || hasFollowingSiblings ? "[" 
                   + (index + 1) + "]" : "");
        paths.splice(0, 0, tagName + pathIndex);
    }

    return paths.length ? "/" + paths.join("/") : null;
};
 31
Author: Matthew Flaschen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-30 11:15:42

Funkcja, której używam, aby uzyskać XPath podobny do twojej sytuacji, używa jQuery:

function getXPath( element )
{
    var xpath = '';
    for ( ; element && element.nodeType == 1; element = element.parentNode )
    {
        var id = $(element.parentNode).children(element.tagName).index(element) + 1;
        id > 1 ? (id = '[' + id + ']') : (id = '');
        xpath = '/' + element.tagName.toLowerCase() + id + xpath;
    }
    return xpath;
}
 18
Author: JCD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-11 01:12:25

Implementację firebug można nieznacznie zmodyfikować, aby sprawdzić element.id dalej w górę drzewa dom:

  /**
   * Gets an XPath for an element which describes its hierarchical location.
   */
  var getElementXPath = function(element) {
      if (element && element.id)
          return '//*[@id="' + element.id + '"]';
      else
          return getElementTreeXPath(element);
  };

  var getElementTreeXPath = function(element) {
      var paths = [];

      // Use nodeName (instead of localName) so namespace prefix is included (if any).
      for (; element && element.nodeType == 1; element = element.parentNode)  {
          var index = 0;
          // EXTRA TEST FOR ELEMENT.ID
          if (element && element.id) {
              paths.splice(0, 0, '/*[@id="' + element.id + '"]');
              break;
          }

          for (var sibling = element.previousSibling; sibling; sibling = sibling.previousSibling) {
              // Ignore document type declaration.
              if (sibling.nodeType == Node.DOCUMENT_TYPE_NODE)
                continue;

              if (sibling.nodeName == element.nodeName)
                  ++index;
          }

          var tagName = element.nodeName.toLowerCase();
          var pathIndex = (index ? "[" + (index+1) + "]" : "");
          paths.splice(0, 0, tagName + pathIndex);
      }

      return paths.length ? "/" + paths.join("/") : null;
  };
 8
Author: DanS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-21 07:41:32

Funkcja Small, powerfull i pure-JS

Zwraca xpath dla elementu i iterator elementów dla xpath.

Https://gist.github.com/iimos/e9e96f036a3c174d0bf4

function xpath(el) {
  if (typeof el == "string") return document.evaluate(el, document, null, 0, null)
  if (!el || el.nodeType != 1) return ''
  if (el.id) return "//*[@id='" + el.id + "']"
  var sames = [].filter.call(el.parentNode.children, function (x) { return x.tagName == el.tagName })
  return xpath(el.parentNode) + '/' + el.tagName.toLowerCase() + (sames.length > 1 ? '['+([].indexOf.call(sames, el)+1)+']' : '')
}

Prawdopodobnie będziesz musiał dodać shim dla IE8, który nie obsługuje [].metoda filtrowania: ta strona MDN daje taki kod.

Użycie

Getting xpath for node:
var xp = xpath(elementNode)
Wykonywanie xpath:
var iterator = xpath("//h2")
var el = iterator.iterateNext();
while (el) {
  // work with element
  el = iterator.iterateNext();
}
 7
Author: imos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-06 07:21:34

Właśnie zmodyfikowałem rozwiązanie, aby używać go z textNodes. Bardzo przydatne do serializacji obiektu zakresu HTML.

/**
 * Gets an XPath for an node which describes its hierarchical location.
 */
var getNodeXPath = function(node) {
    if (node && node.id)
        return '//*[@id="' + node.id + '"]';
    else
        return getNodeTreeXPath(node);
};

var getNodeTreeXPath = function(node) {
    var paths = [];

    // Use nodeName (instead of localName) so namespace prefix is included (if any).
    for (; node && (node.nodeType == 1 || node.nodeType == 3) ; node = node.parentNode)  {
        var index = 0;
        // EXTRA TEST FOR ELEMENT.ID
        if (node && node.id) {
            paths.splice(0, 0, '/*[@id="' + node.id + '"]');
            break;
        }

        for (var sibling = node.previousSibling; sibling; sibling = sibling.previousSibling) {
            // Ignore document type declaration.
            if (sibling.nodeType == Node.DOCUMENT_TYPE_NODE)
                continue;

            if (sibling.nodeName == node.nodeName)
                ++index;
        }

        var tagName = (node.nodeType == 1 ? node.nodeName.toLowerCase() : "text()");
        var pathIndex = (index ? "[" + (index+1) + "]" : "");
        paths.splice(0, 0, tagName + pathIndex);
    }

    return paths.length ? "/" + paths.join("/") : null;
};
 6
Author: Slabko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-24 19:42:08

Nie ma nic wbudowanego, aby uzyskać xpath elementu HTML, ale odwrotność jest powszechna na przykład za pomocą selektora jQuery XPath .

Jeśli chcesz określić xpath elementu HTML, musisz podać w tym celu niestandardową funkcję. Oto kilka przykładów skryptów javascript/jQuery do obliczenia ścieżki xpath.

 4
Author: krock,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-11 01:02:33

Just for fun, An XPath 2.0 one line implementacja:

string-join(ancestor-or-self::*/concat(name(),
                                       '[',
                                       for $x in name() 
                                          return count(preceding-sibling::*
                                                          [name() = $x]) 
                                                 + 1,
                                       ']'),
            '/')
 3
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-11 15:18:10

Poniższe rozwiązanie jest lepsze, jeśli musisz wiarygodnie określić absolutną ścieżkę XPath elementu.

Niektóre inne odpowiedzi albo opierają się częściowo na identyfikatorze elementu (który nie jest wiarygodny, ponieważ potencjalnie może być wiele elementów o identycznych identyfikatorach), albo generują ścieżki XPath, które w rzeczywistości określają więcej elementów niż podany (przez błędne pominięcie indeksu rodzeństwa w pewnych okolicznościach).

Kod został zaadaptowany z kodu źródłowego Firebuga przez naprawianie wyżej wymienionych problemów.

getXElementTreeXPath = function( element ) {
    var paths = [];

    // Use nodeName (instead of localName) so namespace prefix is included (if any).
    for ( ; element && element.nodeType == Node.ELEMENT_NODE; element = element.parentNode )  {
        var index = 0;

        for ( var sibling = element.previousSibling; sibling; sibling = sibling.previousSibling ) {
            // Ignore document type declaration.
            if ( sibling.nodeType == Node.DOCUMENT_TYPE_NODE ) {
                continue;
            }

            if ( sibling.nodeName == element.nodeName ) {
                ++index;
            }
        }

        var tagName = element.nodeName.toLowerCase();

        // *always* include the sibling index
        var pathIndex = "[" + (index+1) + "]";

        paths.unshift( tagName + pathIndex );
    }

    return paths.length ? "/" + paths.join( "/") : null;
};
 3
Author: wadim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-06 11:03:50
function getPath(event) {
  event = event || window.event;

  var pathElements = [];
  var elem = event.currentTarget;
  var index = 0;
  var siblings = event.currentTarget.parentNode.getElementsByTagName(event.currentTarget.tagName);
  for (var i=0, imax=siblings.length; i<imax; i++) {
      if (event.currentTarget === siblings[i] {
        index = i+1; // add 1 for xpath 1-based
      }
  }


  while (elem.tagName.toLowerCase() != "html") {
    pathElements.unshift(elem.tagName);
    elem = elem.parentNode;
  }
  return pathElements.join("/") + "[" + index + "]";
}

EDITED TO ADD SIBLING INDEX INFORMATION

 1
Author: Robusto,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-11 14:02:59

Zobacz mój przykład, który przynajmniej spróbuje skrócić wyrażenie, Jeśli istnieje unikalny identyfikator. Javascript get XPath of a node

 1
Author: stijn de ryck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:29:50