Comment puis-je faire correspondre un attribut contenant une certaine chaîne?


442

J'ai un problème pour sélectionner des nœuds par attribut lorsque les attributs contiennent plus d'un mot. Par exemple:

<div class="atag btag" />

Voici mon expression xpath:

//*[@class='atag']

L'expression fonctionne avec

<div class="atag" />

mais pas pour l'exemple précédent. Comment puis-je sélectionner le <div>?


9
Il convient de souligner, je pense, que "atag btag" est un attribut unique, pas deux. Vous essayez de faire une correspondance de sous-chaîne dans xpath.
skaffman

3
Oui, vous avez raison - c'est ce que je veux.
crazyrails


1
C'est pourquoi vous devez utiliser un sélecteur CSS ... div.atagou div.btag. Super simple, pas de correspondance de chaîne, et beaucoup plus rapide (et mieux pris en charge dans les navigateurs). XPath (contre HTML) devrait être relégué à ce qui est utile pour ... trouver des éléments par du texte contenu et pour la navigation DOM.
JeffC

Réponses:


486

Voici un exemple qui trouve des éléments div dont className contient atag:

//div[contains(@class, 'atag')]

Voici un exemple qui trouve des éléments div dont className contient ataget btag:

//div[contains(@class, 'atag') and contains(@class ,'btag')]

Cependant, il trouvera également des correspondances partielles comme class="catag bobtag".

Si vous ne voulez pas de correspondances partielles, voir la réponse de bobince ci-dessous.


123
@Redbeard: C'est une réponse littérale, mais ce n'est généralement pas le but d'une solution d'appariement de classes. En particulier, il correspondrait <div class="Patagonia Halbtagsarbeit">, qui contient les chaînes cibles mais n'est pas un div avec les classes données.
bobince

3
Cela fonctionnera pour des scénarios simples - mais faites attention si vous souhaitez utiliser cette réponse dans des contextes plus larges avec moins ou pas de contrôle sur les valeurs d'attribut que vous recherchez. La bonne réponse est celle de Bobince.
Oliver

16
Désolé, cela ne correspond pas à une classe, il correspond à une sous
Timo Huovinen

5
c'est tout simplement faux car il trouve aussi: <div class = "annatag bobtag"> ce qu'il ne devrait pas faire.
Alexei Vinogradov

6
La question était "contient une certaine chaîne" ne "correspond" pas à une certaine classe "
Alsacien

303

La réponse de mjv est un bon début mais échouera si atag n'est pas le premier nom de classe répertorié.

L'approche habituelle est plutôt lourde:

//*[contains(concat(' ', @class, ' '), ' atag ')]

cela fonctionne tant que les classes sont séparées par des espaces uniquement, et non par d'autres formes d'espaces. C'est presque toujours le cas. Si ce n'est pas le cas, vous devez le rendre encore plus lourd:

//*[contains(concat(' ', normalize-space(@class), ' '), ' atag ')]

(La sélection par des chaînes séparées par un espace de type nom de classe est un cas si courant qu'il est surprenant qu'il n'y ait pas de fonction XPath spécifique pour elle, comme «[class ~ =" atag "]» de CSS3.)


58
bah, xpath a besoin de quelques corrections
Randy L

13
La réponse de @Redbeard supra123 est problématique s'il existe une classe css comme "atagnumbertwo" que vous ne voulez pas sélectionner, bien que j'admette que cela ne soit pas probable (:
drevicko

7
@crazyrails: Pourriez-vous s'il vous plaît accepter cette réponse comme la bonne réponse? Cela aidera les futurs chercheurs à identifier la bonne solution au problème décrit par votre question. Je vous remercie!
Oliver

2
@ cha0site: Oui, ils le pouvaient, dans XPath 2.0 et suivants. Cette réponse a été écrite avant que XPath 2.0 ne devienne officiel. Voir stackoverflow.com/a/12165032/423105 ou stackoverflow.com/a/12165195/423105
LarsH

1
Ne soyez pas comme moi et supprimez les espaces autour de la classe que vous recherchez dans cet exemple; ils sont en fait importants. Sinon, cela peut sembler fonctionner, mais cela va à l'encontre de l'objectif.
CTS_AE

40

essaye ça: //*[contains(@class, 'atag')]


Et si le nom de la classe est grabatagonabag? (Indice: cela correspondra toujours.)
Wayne

38

EDIT : voir la solution de bobince qui utilise contient plutôt que de commencer , avec une astuce pour s'assurer que la comparaison est effectuée au niveau d'un jeton complet (de peur que le modèle `` atag '' ne soit trouvé dans le cadre d'une autre `` balise '').

"atag btag" est une valeur étrange pour l'attribut class, mais jamais le moins, essayez:

//*[starts-with(@class,"atag")]

vous pouvez l'utiliser si votre moteur XPath prend en charge la commande
start

10
@mjv: Il est courant qu'un attribut de classe CSS spécifie plusieurs valeurs. Voilà comment se fait CSS.
skaffman

7
@mjv Vous ne pouvez pas garantir que ce nom apparaîtra au début de l'attribut de classe.
Alan Krueger

@thuktun @skaffman. Merci, bons commentaires. J'ai 'redirigé' vers la solution bobince en conséquence.
mjv

Ne fonctionne pas pour <div class = "btag atag"> qui est équivalent à ce qui précède
Alexei Vinogradov

30

Un XPath 2.0 qui fonctionne:

//*[tokenize(@class,'\s+')='atag']

ou avec une variable:

//*[tokenize(@class,'\s+')=$classname]

Comment cela peut-il fonctionner s'il @classa plus d'un élément? Parce qu'il va retourner une liste de mots et la comparer à une chaîne échoue avec une mauvaise cardinalité .
Alexis Wilke

3
@AlexisWilke - D'après la spécification ( w3.org/TR/xpath20/#id-general-comparisons ): les comparaisons générales sont des comparaisons quantifiées existentiellement qui peuvent être appliquées à des séquences d'opérandes de n'importe quelle longueur. Cela fonctionne dans tous les processeurs 2.0 que j'ai essayés.
Daniel Haley

1
Notez également que dans XPath 3.1, cela peut être simplifié pour//*[tokenize(@class)=$classname]
Michael Kay

1
Et pour être complet, si vous avez la chance d'utiliser un processeur XPath sensible au schéma, et si @class a un type de valeur de liste, vous pouvez simplement écrire//*[@class=$classname]
Michael Kay

21

Sachez que la réponse de bobince pourrait être trop compliquée si vous pouvez supposer que le nom de classe qui vous intéresse n'est pas une sous-chaîne d'un autre nom de classe possible . Si cela est vrai, vous pouvez simplement utiliser la correspondance de sous-chaîne via la fonction contains. Ce qui suit correspondra à tout élément dont la classe contient la sous-chaîne 'atag':

//*[contains(@class,'atag')]

Si l'hypothèse ci-dessus ne se vérifie pas, une correspondance de sous-chaîne correspondra aux éléments que vous n'avez pas l'intention. Dans ce cas, vous devez trouver les limites des mots. En utilisant les délimiteurs d'espace pour trouver les limites des noms de classe, la deuxième réponse de bobince trouve les correspondances exactes:

//*[contains(concat(' ', normalize-space(@class), ' '), ' atag ')]

Cela correspondra ataget non matag.


C'est la solution que je cherchais. Il trouve clairement «test» dans class = «hello test world» et ne correspond pas à «hello test-test world». Étant donné que j'utilise uniquement XPath 1.0 et que je n'ai pas de RegEx, c'est la seule solution qui fonctionne.
Jan Stanicek

7

Pour ajouter à la réponse de bobince ... Si n'importe quel outil / bibliothèque que vous utilisez utilise Xpath 2.0, vous pouvez également le faire:

//*[count(index-of(tokenize(@class, '\s+' ), $classname)) = 1]

count () est apparemment nécessaire car index-of () renvoie une séquence de chaque index auquel il a une correspondance dans la chaîne.


1
Je suppose que vous vouliez PAS mettre la $classnamevariable entre guillemets? Parce que tel qu'il est, c'est une chaîne.
Alexis Wilke

1
Enfin, une implémentation correcte (compatible JavasScript) de getElementsByClassName ... en dehors du littéral '$classname'de chaîne bien sûr.
Joel Mellon

1
C'est extrêmement compliqué. Voir la réponse de @ DanielHaley pour la bonne réponse XPath 2.0.
Michael Kay


0

Je suis venu ici à la recherche d'une solution pour Ranorex Studio 9.0.1. Il n'y a pas encore de contient (). Au lieu de cela, nous pouvons utiliser l'expression régulière comme:

div[@class~'atag']

-1

Pour les liens qui contiennent des URL communes doivent se consoler dans une variable. Essayez ensuite séquentiellement.

webelements allLinks=driver.findelements(By.xpath("//a[contains(@href,'http://122.11.38.214/dl/appdl/application/apk')]"));
int linkCount=allLinks.length();
for(int i=0; <linkCount;i++)
{
    driver.findelement(allLinks[i]).click();
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.