Подтвердить что ты не робот

Как вычислить позицию XPath элемента с помощью Javascript?

Скажем, у меня есть большой HTML файл с разными тегами, похожий на StackOverflow, который вы сейчас просматриваете.

Теперь скажем, вы нажмете элемент на странице, как бы выглядела функция Javascript, которая вычисляет самый базовый XPath, который относится к этому конкретному элементу?

Я знаю, что есть бесконечные способы ссылаться на этот элемент в XPath, но я ищу что-то, что просто смотрит на дерево DOM, не обращая внимания на идентификаторы, классы и т.д.

Пример:

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

Скажем, вы нажимаете "Яблоки". Функция Javascript вернет следующее:

/html/body/ol/li[2]

В основном это будет работать вверх по дереву DOM вплоть до элемента HTML.

Чтобы уточнить, обработчик событий "on-click" не является проблемой. Я могу сделать эту работу. Я просто не уверен, как вычислить позицию элемента в дереве DOM и представить его как XPath.

PS Любой ответ с использованием библиотеки JQuery или без нее приветствуется.

ПФС Я совершенно новичок в XPath, поэтому я мог бы даже ошибиться в приведенном выше примере, но вы поймете эту идею.

Редактировать в 11 августа 2010 года. Похоже, что кто-то другой задал аналогичный вопрос: сгенерировать/получить Xpath для выбранного текстового файла

4b9b3361

Ответ 1

Firebug может это сделать, и он открывает исходный код (BSD), чтобы вы могли повторно использовать их реализация, которая не требует каких-либо библиотек.

Редактирование третьей стороны

Это выдержка из связанного источника выше. На всякий случай ссылка выше изменится. Пожалуйста, проверьте источник, чтобы воспользоваться изменениями и обновлениями или предоставленным полным набором функций.

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

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

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

Ответ 2

Функция, которую я использую для получения XPath, похожей на вашу ситуацию, использует 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;
}

Ответ 3

Реализация firebug может быть немного изменена, чтобы проверить element.id далее на дерево 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;
  };

Ответ 4

Малая, мощная и чистая функция js

Он возвращает xpath для итератора элементов и элементов для 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)+']' : '')
}

Вероятно, вам нужно будет добавить прокладку для IE8, которая не поддерживает метод [].filter: эта страница MDN дает такие код.

Использование

Получение xpath для node:
var xp = xpath(elementNode)
Выполнение xpath:
var iterator = xpath("//h2")
var el = iterator.iterateNext();
while (el) {
  // work with element
  el = iterator.iterateNext();
}

Ответ 5

Я только что изменил решение DanS, чтобы использовать его с textNodes. Очень полезно сериализовать объект диапазона 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

Нет ничего встроенного, чтобы получить xpath элемента HTML, но обратное распространено, например, используя селектор xpath jQuery.

Если вам нужно определить xpath для HTML-элемента, вам нужно будет предоставить настраиваемую функцию для этого. Вот несколько пример javascript/jQuery impls для вычисления xpath.

Ответ 7

Просто для удовольствия, реализация XPath 2.0 с одной строкой:

string-join(ancestor-or-self::*/concat(name(),
                                       '[',
                                       for $x in name() 
                                          return count(preceding-sibling::*
                                                          [name() = $x]) 
                                                 + 1,
                                       ']'),
            '/')

Ответ 8

Решение ниже предпочтительнее, если вам нужно надежно определить абсолютный XPath элемента.

Некоторые другие ответы частично зависят от идентификатора элемента (который не является надежным, поскольку потенциально могут быть несколько элементов с идентичными идентификаторами) или они генерируют XPaths, которые фактически указывают больше элементов, чем тот, который указан (ошибочно исключая индекс sibling в определенные обстоятельства).

Код был адаптирован из исходного кода Firebug, устраняя вышеупомянутые проблемы.

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

Ответ 9

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 + "]";
}

ИЗМЕНИТЬ ДОБАВИТЬ ИНФОРМАЦИЮ ПОСТАВЩИКОВ SIBLING

Ответ 10

см. мой пример, который, по крайней мере, попытается сократить выражение, если есть уникальный идентификатор. Javascript получить XPath node