Como posso corresponder em um atributo que contém uma determinada string?

442

Estou com problemas para selecionar nós por atributo quando os atributos contêm mais de uma palavra. Por exemplo:

<div class="atag btag" />

Esta é a minha expressão xpath:

//*[@class='atag']

A expressão trabalha com

<div class="atag" />

mas não para o exemplo anterior. Como posso selecionar o <div>?

crazyrails
fonte
9
Vale ressaltar, eu acho, que "atag btag" é um atributo único, não dois. Você está tentando fazer a correspondência de substring no xpath.
21410 skaffman
3
Sim, você está certo - é isso que eu quero.
crazyrails
1
É por isso que você deve usar um seletor de CSS ... div.atagou div.btag. Super simples, sem correspondência de cordas e MUITO MAIS rápido (e com melhor suporte nos navegadores). XPath (contra HTML) deve ser relegado para o que é útil para ... encontrar elementos por texto contido e para navegação DOM.
21818 JeffC

Respostas:

486

Aqui está um exemplo que encontra elementos div cujo className contém atag:

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

Aqui está um exemplo que encontra elementos div cujo className contém atage btag:

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

No entanto, também encontrará correspondências parciais como class="catag bobtag".

Se você não deseja correspondências parciais, veja a resposta de bobince abaixo.

surupa123
fonte
123
@ Redbeard: É uma resposta literal, mas geralmente não é o que uma solução de correspondência de classe deve buscar. Em particular, corresponderia <div class="Patagonia Halbtagsarbeit">, que contém as seqüências de destino, mas não é uma div com as classes especificadas.
bobince
3
Isso funcionará para cenários simples - mas cuidado se você quiser usar esta resposta em contextos mais amplos, com menos ou nenhum controle sobre os valores de atributo que você está procurando. A resposta correta é de bobince.
Oliver
16
Desculpe, isso não corresponder a uma classe, que corresponde a um substring
Timo Huovinen
5
está claramente errado, pois também encontra: <div class = "annatag bobtag"> o que não deveria.
Alexei Vinogradov
6
A questão era "contém uma determinada string" não "corresponde a uma determinada classe"
Alsaciano
303

A resposta do mjv é um bom começo, mas falhará se atag não for o primeiro nome de classe listado.

A abordagem usual é a bastante pesada:

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

isso funciona desde que as classes sejam separadas apenas por espaços, e não outras formas de espaço em branco. Este é quase sempre o caso. Se não for, você precisa torná-lo ainda mais pesado:

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

(Selecionar por seqüências separadas por espaço, como o nome da classe, é um caso tão comum que surpreende que não exista uma função XPath específica para ela, como '[class ~ = "atag"]' do CSS3.)

bobince
fonte
57
bah, xpath precisa de algumas correções
Randy L
13
A resposta de supra123 @Redbeard é problemático se há uma classe css como "atagnumbertwo" que você não quer selecionar, embora eu admito isso pode não ser provável (:
drevicko
7
@crazyrails: Você poderia aceitar esta resposta como a resposta correta? Isso ajudará futuros pesquisadores a identificar a solução correta para o problema descrito por sua pergunta. Obrigado!
Oliver
2
@ cha0site: Sim, eles poderiam, no XPath 2.0 e nos seguintes. Esta resposta foi escrita antes do XPath 2.0 se tornar oficial. Veja stackoverflow.com/a/12165032/423105 ou stackoverflow.com/a/12165195/423105
LarsH
1
Não seja como eu e remova os espaços ao redor da classe que você está procurando neste exemplo; eles são realmente importantes. Caso contrário, pode parecer funcionar, mas derrota o objetivo.
CTS_AE 25/09
40

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

SelenUser
fonte
E se o nome da classe for grabatagonabag? (Dica: ainda vai combinar.)
Wayne
38

EDIT : veja a solução de bobince que usa contém em vez de iniciar , juntamente com um truque para garantir que a comparação seja feita no nível de um token completo (para que o padrão 'atag' não seja encontrado como parte de outra 'tag').

"atag btag" é um valor ímpar para o atributo de classe, mas, no entanto, tente:

//*[starts-with(@class,"atag")]
mjv
fonte
você pode usar isto se o seu motor de XPath suporta os starts-com comando, por exemplo JVM 6 não apoiá-lo tanto quanto eu me lembro
Mohamed Faramawi
10
@mjv: É comum que um atributo de classe CSS especifique vários valores. É assim que o CSS é feito.
21410 skaffman
7
@mjv Você não pode garantir que esse nome apareça no início do atributo de classe.
27715 Alan Krueger
@thuktun @skaffman. Obrigado, ótimos comentários. Eu 'redirecionei' para bobince solução em conformidade.
Mjv 6/10/09
Não funciona para <div class = "btag atag"> equivalente a acima
Alexei Vinogradov
30

Um XPath 2.0 que funciona:

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

ou com uma variável:

//*[tokenize(@class,'\s+')=$classname]
Daniel Haley
fonte
Como isso pode funcionar se @classtiver mais de um elemento? Porque ele retornará uma lista de palavras e a comparação com uma string falhará com cardinalidade errada .
Alexis Wilke
3
@AlexisWilke - A partir da especificação ( w3.org/TR/xpath20/#id-general-comparisons ): Comparações gerais são comparações existencialmente quantificadas que podem ser aplicadas a sequências de operandos de qualquer tamanho. Ele funcionou em todos os processadores 2.0 que eu tentei.
Daniel Haley
1
Observe também que, no XPath 3.1, isso pode ser simplificado para//*[tokenize(@class)=$classname]
Michael Kay
1
E para completar, se você é o suficiente sorte de estar usando um processador XPath schema-cientes, e se @class tem um tipo de valor de lista, então você pode simplesmente escrever//*[@class=$classname]
Michael Kay
21

Esteja ciente de que a resposta de bobince pode ser muito complicada se você puder assumir que o nome da classe em que você está interessado não é uma subcadeia de outro nome de classe possível . Se isso for verdade, você pode simplesmente usar a correspondência de substring através da função contains. A seguir, corresponderá a qualquer elemento cuja classe contenha a subcadeia 'atag':

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

Se a suposição acima não for válida, uma correspondência de substring corresponderá aos elementos que você não pretende. Nesse caso, você precisa encontrar os limites da palavra. Usando os delimitadores de espaço para encontrar os limites do nome da classe, a segunda resposta de bobince encontra as correspondências exatas:

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

Isso vai combinar atage não matag.

Brent Atkinson
fonte
Esta é a solução que eu estava procurando. Ele encontra claramente 'test' em class = 'hello test world' e não corresponde a 'hello test-test world'. Como eu uso apenas o XPath 1.0 e não tenho RegEx, essa é apenas a solução que funciona.
Jan Stanicek
7

Para adicionar à resposta de bobince ... Se qualquer ferramenta / biblioteca que você usar usar o Xpath 2.0, você também poderá fazer isso:

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

Aparentemente, count () é necessário porque index-of () retorna uma sequência de cada índice com o qual corresponde na string.

armyofda12mnkeys
fonte
1
Suponho que você pretendia NÃO colocar a $classnamevariável entre aspas? Porque, como é, é uma string.
Alexis Wilke
1
Finalmente, uma implementação correta (compatível com JavasScript) de getElementsByClassName ... além da string literal, é '$classname'claro.
Joel Mellon
1
Isso é extremamente complicado. Consulte a resposta de @ DanielHaley para obter a resposta correta do XPath 2.0.
Michael Kay
5

Você pode tentar o seguinte

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

Umesh Chhabra
fonte
0

Eu vim aqui procurando solução para o Ranorex Studio 9.0.1. Ainda não há contains (). Em vez disso, podemos usar regex como:

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

Para os links que contêm url comum tem que consolar em uma variável. Em seguida, tente sequencialmente.

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();
}
user3906232
fonte