¿Cómo puedo hacer coincidir un atributo que contiene una cadena determinada?

442

Tengo problemas para seleccionar nodos por atributo cuando los atributos contienen más de una palabra. Por ejemplo:

<div class="atag btag" />

Esta es mi expresión xpath:

//*[@class='atag']

La expresión funciona con

<div class="atag" />

pero no para el ejemplo anterior. ¿Cómo puedo seleccionar el <div>?

Crazyrails
fuente
99
Vale la pena señalar, creo, que "atag btag" es un atributo único, no dos. Estás intentando hacer una coincidencia de subcadenas en xpath.
skaffman
3
Sí, tienes razón, eso es lo que quiero.
crazyrails
1
Es por eso que debe usar un selector CSS ... div.atago div.btag. Súper simple, sin coincidencia de cadenas, y MUCHO más rápido (y mejor admitido en los navegadores). XPath (contra HTML) debe relegarse a lo que es útil para ... encontrar elementos por texto contenido y para navegación DOM.
JeffC

Respuestas:

486

Aquí hay un ejemplo que encuentra elementos div cuyo className contiene atag:

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

Aquí hay un ejemplo que encuentra elementos div cuyo className contiene atagy btag:

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

Sin embargo, también encontrará coincidencias parciales como class="catag bobtag".

Si no desea coincidencias parciales, consulte la respuesta de bobince a continuación.

surupa123
fuente
123
@Redbeard: es una respuesta literal, pero no suele ser a lo que debe apuntar una solución de coincidencia de clases. En particular, coincidiría <div class="Patagonia Halbtagsarbeit">, lo que contiene las cadenas de destino pero no es un div con las clases dadas.
bobince
3
Esto funcionará para escenarios simples, pero tenga cuidado si desea usar esta respuesta en contextos más amplios con menos o ningún control sobre los valores de los atributos que está buscando. La respuesta correcta es la de bobince.
Oliver
16
Lo sentimos, esto no coincide con una clase, coincide con una subcadena
Timo Huovinen
55
es simplemente incorrecto, ya que también encuentra: <div class = "annatag bobtag"> que no debería.
Alexei Vinogradov
66
La pregunta era "contiene una cadena determinada" no "coincide con una clase determinada"
Alsaciano
303

La respuesta de mjv es un buen comienzo, pero fallará si atag no es el primer nombre de clase en la lista.

El enfoque habitual es bastante difícil de manejar:

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

esto funciona siempre que las clases estén separadas solo por espacios, y no por otras formas de espacios en blanco. Este es casi siempre el caso. Si no es así, debe hacerlo aún más difícil de manejar:

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

(La selección por cadenas separadas por espacios como nombres de clase es un caso tan común que sorprende que no haya una función XPath específica para ella, como '[class ~ = "atag"]' de CSS3).

bobince
fuente
57
bah, xpath necesita algunas soluciones
Randy L
13
La respuesta de @Redbeard supra123 es problemática si hay una clase css como "atagnumbertwo" que no desea seleccionar, aunque admito que esto puede no ser probable (:
drevicko
77
@crazyrails: ¿Podría aceptar esta respuesta como la respuesta correcta? Eso ayudará a los futuros buscadores a identificar la solución correcta al problema descrito por su pregunta. ¡Gracias!
Oliver
2
@ cha0site: Sí, podrían, en XPath 2.0 y siguientes. Esta respuesta fue escrita antes de que XPath 2.0 se hiciera oficial. Ver stackoverflow.com/a/12165032/423105 o stackoverflow.com/a/12165195/423105
LarsH
1
No seas como yo y elimina los espacios alrededor de la clase que estás buscando en este ejemplo; son realmente importantes De lo contrario, puede parecer que funciona, pero vence el propósito.
CTS_AE
40

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

SelenUser
fuente
¿Qué pasa si el nombre de la clase es grabatagonabag? (Sugerencia: aún coincidirá.)
Wayne
38

EDITAR : vea la solución de bobince que usa contiene en lugar de comenzar con , junto con un truco para garantizar que la comparación se realice al nivel de un token completo (para que el patrón 'atag' no se encuentre como parte de otra 'etiqueta').

"atag btag" es un valor impar para el atributo de clase, pero no obstante, intente:

//*[starts-with(@class,"atag")]
mjv
fuente
se puede utilizar este si su motor XPath soporta las starts-con comando, por ejemplo JVM 6 no lo soporta Por lo que yo recuerdo
Mohamed Faramawi
10
@mjv: es común que un atributo de clase CSS especifique varios valores. Así es como se hace CSS.
skaffman
77
@mjv No puede garantizar que ese nombre aparezca al comienzo del atributo de clase.
Alan Krueger
@thuktun @skaffman. Gracias, buenos comentarios. Me 'redirigió' a la solución bobince en consecuencia.
mjv
No funciona para <div class = "btag atag"> que es equivalente a lo anterior
Alexei Vinogradov
30

Un 2.0 XPath que funciona:

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

o con una variable:

//*[tokenize(@class,'\s+')=$classname]
Daniel Haley
fuente
¿Cómo puede funcionar esto si @classtiene más de un elemento? Porque va a devolver una lista de palabras y compararla con una cadena falla con una cardinalidad incorrecta .
Alexis Wilke
3
@AlexisWilke - De la especificación ( w3.org/TR/xpath20/#id-general-comparisons ): Las comparaciones generales son comparaciones cuantificadas existencialmente que pueden aplicarse a secuencias de operandos de cualquier longitud. Funcionó en todos los procesadores 2.0 que he probado.
Daniel Haley
1
Tenga en cuenta también, en XPath 3.1 esto se puede simplificar a//*[tokenize(@class)=$classname]
Michael Kay
1
Y para completar, si tiene la suerte de estar usando un procesador XPath con reconocimiento de esquema, y ​​si @class tiene un tipo de valor de lista, entonces simplemente puede escribir//*[@class=$classname]
Michael Kay
21

Tenga en cuenta que la respuesta de bobince podría ser demasiado complicada si puede suponer que el nombre de la clase que le interesa no es una subcadena de otro posible nombre de clase . Si esto es cierto, simplemente puede usar la coincidencia de subcadenas a través de la función contiene. Lo siguiente coincidirá con cualquier elemento cuya clase contenga la subcadena 'atag':

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

Si la suposición anterior no se cumple, una coincidencia de subcadena coincidirá con elementos que no desea. En este caso, debe encontrar los límites de las palabras. Al usar los delimitadores de espacio para encontrar los límites del nombre de clase, la segunda respuesta de bobince encuentra las coincidencias exactas:

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

Esto coincidirá atagy no matag.

Brent Atkinson
fuente
Esta es la solución que estaba buscando. Claramente encuentra 'test' en clase = 'hello test world' y no coincide con 'hello test-test world'. Como solo uso XPath 1.0 y no tengo RegEx, esta es la única solución que funciona.
Jan Stanicek, el
7

Para agregar a la respuesta de bobince ... Si cualquier herramienta / biblioteca que usa usa Xpath 2.0, también puede hacer esto:

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

count () aparentemente es necesario porque index-of () devuelve una secuencia de cada índice con el que tiene una coincidencia en la cadena.

Armyofda12mnkeys
fuente
1
¿Supongo que querías NO poner la $classnamevariable entre comillas? Porque como es, eso es una cadena.
Alexis Wilke
1
Finalmente, una implementación correcta (compatible con JavasScript) de getElementsByClassName ... aparte del literal '$classname'de cadena, por supuesto.
Joel Mellon
1
Esto es extremadamente complicado. Consulte la respuesta de @ DanielHaley para obtener la respuesta correcta de XPath 2.0.
Michael Kay
5

Puedes probar lo siguiente

By.CssSelector("div.atag.btag")

Umesh Chhabra
fuente
0

Vine aquí buscando una solución para Ranorex Studio 9.0.1. No hay contiene () allí todavía. En cambio, podemos usar expresiones regulares como:

div[@class~'atag']
Jarno Argillander
fuente
-1

Para los enlaces que contiene url común hay que consolar en una variable. Luego inténtalo secuencialmente.

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();
}
usuario3906232
fuente