48 votes

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, semblable à celui de StackOverflow que vous regardez en ce moment.

Maintenant, disons que vous cliquez sur un élément de la page, à quoi ressemblerait la fonction Javascript qui calcule le XPath le plus basique faisant référence à cet élément spécifique ?

Je sais qu'il y a une infinité de façons de faire référence à cet élément dans XPath, mais je cherche quelque chose qui regarde simplement l'arbre DOM, sans tenir compte des ID, des 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 renverrait le résultat suivant :

/html/body/ol/li[2]

Il s'agit en fait de remonter l'arbre DOM jusqu'à l'élément HTML.

Juste pour clarifier, le gestionnaire d'événement "on-click" n'est pas le problème. Je peux le faire fonctionner. Je ne sais simplement pas comment calculer la position de l'élément dans l'arbre DOM et la représenter sous forme de XPath.

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

PPS Je suis complètement novice en matière de XPath, il se peut donc que j'aie fait une erreur dans l'exemple ci-dessus, mais vous aurez l'idée.

Modifié le 11 août 2010 : Il semble que quelqu'un d'autre ait posé une question similaire : générer/obtenir le Xpath pour un textnode sélectionné

7 votes

XPath utilise une indexation basée sur 1, donc c'est li[2] .

0 votes

Merci, j'ai modifié le code.

37voto

Matthew Flaschen Points 131723

Firebug peut le faire, et c'est un logiciel libre ( BSD ) afin que vous puissiez réutiliser leur mise en œuvre qui ne nécessite aucune bibliothèque.

Montage par un tiers

Ceci est un extrait de la source liée ci-dessus. Juste au cas où le lien ci-dessus changerait. Veuillez consulter la source pour bénéficier des changements 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é quelques lignes 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;
};

0 votes

J'aime l'implémentation de Firebug (getElementXPath), car elle privilégie l'ID par rapport à un arbre xpath si l'élément en possède un.

1 votes

@OneWorld Oui, mais il ne vérifie que l'élément dont vous obtenez le xpath et non ses parents. Voir ma réponse, il produit un xpath plus comme l'inspecteur de google chrome.

1 votes

L'implémentation de Firebug comporte un bogue en ligne 1365 il n'ajoute l'index (par exemple, "[3]") que s'il y a des noms d'utilisateurs. précédent les frères et sœurs du même type. C'est faux car un XPath sans l'index correspondra à todo des frères et sœurs de ce type, voire d'autres. Exemple : /p/b correspondra à todo b balises sous todo p tags sous Root. Firebug saute simplement l'index s'il ne trouve pas précédent des frères et sœurs du même type.

18voto

JCD Points 1633

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

0 votes

Fonctionne aussi parfaitement ! Merci

0 votes

Ligne 7 : je n'ai jamais vu cette syntaxe, d'habitude elle serait écrite comme ceci : id = id > 1 ? ('[' + id + ']') : '';

0 votes

Vous avez raison, je l'aurais écrit comme vous l'avez fait, mais je ne faisais que copier/coller... J'ai trouvé ce script il y a quelque temps et je n'ai jamais pris la peine de le nettoyer. Quoi qu'il en soit, les deux façons de l'écrire sont équivalentes.

17voto

IMos Points 629

Petite, puissante et pure fonction js

Il retourne xpath pour l'élément et 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 une cale pour IE8 qui ne supporte pas la méthode [].filter : cette page MDN donne un tel code.

Utilisation

Obtenir xpath pour le noeud :

var xp = xpath(elementNode)

Exécution de xpath :

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

8voto

DanS Points 4992

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

0 votes

Le code suivant renvoie-t-il un xpath correct pour le div dans le html suivant ?

7voto

Slabko Points 73

Je viens de modifier la solution de DanS afin de l'utiliser avec les textNodes. Très utile pour sérialiser l'objet HTML range.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X