web-dev-qa-db-fra.com

Comment calculer la position XPath d'un élément en utilisant Javascript?

Disons que j'ai un gros fichier HTML avec différents types de balises, similaire à celui de StackOverflow que vous regardez en ce moment.

Supposons maintenant que vous cliquiez sur un élément de la page, à quoi ressemblerait la fonction Javascript qui calcule le XPath le plus basique qui se réfère à cet élément spécifique?

Je sais qu'il existe une infinité de façons de se référer à cet élément dans XPath, mais je cherche quelque chose qui ne regarde que l'arborescence DOM, sans égard pour les ID, les classes, etc.

Exemple:

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

Disons que vous cliquez sur Pommes . La fonction Javascript retournerait ce qui suit:

/html/body/ol/li[2]

Il fonctionnerait simplement vers le haut de l'arborescence DOM jusqu'à l'élément HTML.

Juste pour clarifier, le gestionnaire d'événements "au clic" n'est pas le problème. Je peux faire ça. Je ne sais pas comment calculer la position de l'élément dans l'arborescence DOM et le représenter comme XPath.

PS Toute réponse avec ou sans l'utilisation de la bibliothèque JQuery est appréciée.

PPS J'ai complètement nouveau sur XPath, donc j'ai peut-être même fait une erreur dans l'exemple ci-dessus, mais vous aurez l'idée.

Modifier le 11 août 2010: On dirait que quelqu'un d'autre a posé une question similaire: générer/obtenir le Xpath pour un nœud de texte sélectionné

40
Marc

Firebug peut le faire, et c'est open source ( BSD ) afin que vous puissiez réutiliser leur implémentation , qui ne nécessite aucune bibliothèque.

Modification tierce

Ceci est un extrait de la source liée ci-dessus. Juste au cas où le lien ci-dessus changerait. Veuillez vérifier la source pour bénéficier des modifications et des mises à jour ou de l'ensemble des fonctionnalités fournies.

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

Le code ci-dessus appelle cette fonction. Attention, j'ai ajouté un retour à la ligne pour éviter la barre de défilement horizontale

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;
};
33
Matthew Flaschen

Une fonction que j'utilise pour obtenir un XPath similaire à votre situation, il utilise 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
JCD

Fonction petite, puissante et pure-js

Il renvoie xpath pour l'élément et l'itérateur d'éléments pour 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)+']' : '')
}

Vous devrez probablement ajouter un shim pour IE8 qui ne prend pas en charge la méthode [] .filter: cette page MDN donne un tel code.

Usage

var xp = xpath(elementNode)
var iterator = xpath("//h2")
var el = iterator.iterateNext();
while (el) {
  // work with element
  el = iterator.iterateNext();
}
12
imos

L'implémentation de Firebug peut être légèrement modifiée pour vérifier element.id plus haut dans l'arborescence 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
DanS

Je viens de modifier la solution de DanS afin de l'utiliser avec textNodes. Très utile pour sérialiser un objet de plage 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;
};
7
Slabko

Il n'y a rien de construit pour obtenir le xpath d'un élément HTML, mais l'inverse est courant, par exemple en utilisant le sélecteur jQuery xpath .

Si vous devez déterminer le xpath d'un élément HTML, vous devrez fournir une fonction personnalisée pour ce faire. Voici quelques exemple impls javascript/jQuery pour calculer le xpath.

4
krock

La solution ci-dessous est préférable si vous devez déterminer de manière fiable l'XPath absol d'un élément.

Certaines autres réponses reposent en partie sur l'ID d'élément (qui n'est pas fiable car il peut potentiellement y avoir plusieurs éléments avec des ID identiques) ou génèrent des XPath qui spécifient en fait plus d'éléments que celui donné (en omettant par erreur l'index des frères dans certaines circonstances) .

Le code a été adapté du code source de Firebug en corrigeant les problèmes mentionnés ci-dessus.

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
wadim

Juste pour le plaisir, une implémentation XPath 2.0 une ligne:

string-join(ancestor-or-self::*/concat(name(),
                                       '[',
                                       for $x in name() 
                                          return count(preceding-sibling::*
                                                          [name() = $x]) 
                                                 + 1,
                                       ']'),
            '/')
3
user357812
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 + "]";
}

MODIFIÉ POUR AJOUTER DES INFORMATIONS D'INDEX DE SIBLING

1
Robusto

Utilisez https://github.com/KajeNick/jquery-get-xpath

<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="../src/jquery-get-xpath.js"></script> 

<script>
    jQuery(document).ready(function ($) {

        $('body').on('click', 'ol li', function () {
           let xPath = $(this).jGetXpath();

           console.log(xPath);
        });

    });
</script>

La console affichera: /html/body/ol/li [2]

0
NSukonny