Jak mogę dopasować atrybut, który zawiera określony ciąg?

442

Mam problem z wybieraniem węzłów według atrybutu, gdy atrybuty zawierają więcej niż jedno słowo. Na przykład:

<div class="atag btag" />

To jest moje wyrażenie xpath:

//*[@class='atag']

Wyrażenie działa z

<div class="atag" />

ale nie w poprzednim przykładzie. Jak mogę wybrać <div>?

crazyrails
źródło
9
Myślę, że warto zauważyć, że „atag btag” to pojedynczy atrybut, a nie dwa. Próbujesz dopasować podciągi w Xpath.
skaffman
3
Tak, masz rację - właśnie tego chcę.
crazyrails,
1
Dlatego powinieneś użyć selektora CSS ... div.ataglub div.btag. Super proste, bez dopasowywania ciągów i WAY szybsze (i lepiej obsługiwane w przeglądarkach). XPath (w stosunku do HTML) należy sprowadzić do tego, co jest przydatne do ... znajdowania elementów według zawartego tekstu i nawigacji DOM.
JeffC

Odpowiedzi:

486

Oto przykład, który wyszukuje elementy div, których className zawiera atag:

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

Oto przykład, który znajduje elementy div, których className zawiera atagi btag:

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

Jednak znajdzie również częściowe dopasowania, takie jak class="catag bobtag".

Jeśli nie chcesz częściowych dopasowań, zobacz odpowiedź Bobina poniżej.

surupa123
źródło
123
@ Redbeard: To dosłowna odpowiedź, ale nie zawsze to, do czego powinno dążyć rozwiązanie dopasowane do klasy. W szczególności pasowałby <div class="Patagonia Halbtagsarbeit">, który zawiera ciągi docelowe, ale nie jest div z podanymi klasami.
bobince
3
Będzie to działać w prostych scenariuszach - ale uważaj, jeśli chcesz użyć tej odpowiedzi w szerszym kontekście, z mniejszą kontrolą lub bez kontroli nad wartościami atrybutów, które sprawdzasz. Prawidłowa odpowiedź to bobince.
Oliver
16
Przepraszamy, to nie pasuje do klasy, pasuje do podłańcucha
Timo Huovinen
5
jest to po prostu błędne, ponieważ stwierdza również: <div class = "annatag bobtag"> którego nie powinien.
Aleksiej Winogradow
6
Pytanie brzmiało: „zawiera określony ciąg” „nie” pasuje do pewnej klasy ”
alzacki
303

Odpowiedź mjv to dobry początek, ale się nie powiedzie, jeśli atag nie jest wymienioną pierwszą nazwą klasy.

Zwykle podejście jest raczej nieporęczne:

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

działa to tak długo, jak klasy są oddzielone tylko spacjami, a nie innymi formami białych znaków. Tak jest prawie zawsze. Jeśli tak nie jest, musisz uczynić go jeszcze bardziej nieporęcznym:

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

(Wybór ciągów oddzielonych spacjami od nazwy klasy jest tak częstym przypadkiem, że zaskakujące jest to, że nie ma dla niego określonej funkcji XPath, takiej jak „[class ~ =" atag ”] CSS3.)

Bobin
źródło
58
bah, xpath potrzebuje poprawek
Randy L
13
Odpowiedź Redbeard supra123 jest problematyczna, jeśli istnieje klasa css, taka jak „atagnumbertwo”, której nie chcesz wybierać, choć przyznam, że może to nie być prawdopodobne (:
drevicko
7
@crazyrails: Czy możesz zaakceptować tę odpowiedź jako prawidłową? Pomoże to przyszłym wyszukiwarkom w znalezieniu właściwego rozwiązania problemu opisanego w pytaniu. Dziękuję Ci!
Oliver
2
@ cha0site: Tak, mogą, w XPath 2.0 i następnych. Ta odpowiedź została napisana, zanim XPath 2.0 stał się oficjalny. Zobacz stackoverflow.com/a/12165032/423105 lub stackoverflow.com/a/12165195/423105
LarsH
1
Nie bądź taki jak ja i usuń spacje wokół klasy, której szukasz w tym przykładzie; są właściwie ważne. W przeciwnym razie może to wyglądać na działanie, ale pokonuje cel.
CTS_AE
40

Spróbuj tego: //*[contains(@class, 'atag')]

SelenUser
źródło
Co jeśli nazwa klasy to grabatagonabag? (Wskazówka: nadal będzie pasować.)
Wayne
38

EDIT : patrz rozwiązanie bobince, który używa zawierający zamiast start-z , wraz z trick, aby zapewnić porównanie odbywa się na poziomie pełnej tokena (bo w „atag” wzór można znaleźć jako część innego „tag”).

„atag btag” to nieparzysta wartość atrybutu class, ale nie mniej, spróbuj:

//*[starts-with(@class,"atag")]
mjv
źródło
możesz tego użyć, jeśli twój silnik XPath obsługuje komendę start-with, na przykład JVM 6 nie obsługuje tego, o ile pamiętam
Mohamed Faramawi,
10
@mjv: Atrybut klasy CSS często określa wiele wartości. Tak powstaje CSS.
skaffman
7
@mjv Nie możesz zagwarantować, że ta nazwa pojawi się na początku atrybutu class.
Alan Krueger
@thuktun @skaffman. Dzięki, świetne komentarze. „Przekierowałem” odpowiednio do rozwiązania bobince.
mjv,
Nie działa dla <div class = "btag atag">, co odpowiada powyższemu
Aleksiej Winogradow
30

2.0 XPath, który działa:

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

lub ze zmienną:

//*[tokenize(@class,'\s+')=$classname]
Daniel Haley
źródło
Jak to może działać, jeśli @classma więcej niż jeden element? Ponieważ zwróci listę słów i porównanie ich z łańcuchem zakończy się niepowodzeniem z niewłaściwą licznością .
Alexis Wilke,
3
@AlexisWilke - Ze specyfikacji ( w3.org/TR/xpath20/#id-general-comparisons ): Porównania ogólne to porównania skwantyfikowane egzystencjalnie, które można zastosować do sekwencji argumentów dowolnej długości. Działa w każdym procesorze 2.0, którego próbowałem.
Daniel Haley,
1
Zauważ też, że w XPath 3.1 można to uprościć//*[tokenize(@class)=$classname]
Michael Kay,
1
I dla kompletności, jeśli masz szczęście, że używasz procesora XPath z rozpoznawaniem schematów, a jeśli @class ma typ ceniony na liście, możesz po prostu napisać//*[@class=$classname]
Michael Kay
21

Pamiętaj, że odpowiedź Bobina może być zbyt skomplikowana, jeśli możesz założyć, że interesująca Cię nazwa klasy nie jest podciągiem innej możliwej nazwy klasy . Jeśli to prawda, możesz po prostu użyć dopasowania podciągu za pomocą funkcji zawiera. Następujące elementy będą pasować do każdego elementu, którego klasa zawiera podłańcuch „atag”:

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

Jeśli powyższe założenie nie zostanie spełnione, dopasowanie podłańcucha będzie pasowało do elementów, których nie zamierzasz. W takim przypadku musisz znaleźć granice słów. Używając ograniczników spacji, aby znaleźć granice nazw klas, druga odpowiedź Bobina znajduje dokładne dopasowanie:

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

To będzie pasować atagi nie matag.

Brent Atkinson
źródło
To było rozwiązanie, którego szukałem. Wyraźnie znajduje „test” w klasie = „hello test world” i nie pasuje do „hello test-test world”. Ponieważ używam tylko XPath 1.0 i nie mam RegEx, jest to jedyne rozwiązanie, które działa.
Jan Stanicek
7

Aby dodać do odpowiedzi bobince ... Jeśli dowolne narzędzie / biblioteka, której używasz, korzysta z Xpath 2.0, możesz również to zrobić:

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

count () jest najwyraźniej potrzebne, ponieważ index-of () zwraca sekwencję każdego indeksu, do którego pasuje w ciągu.

armyofda12mnkeys
źródło
1
Przypuszczam, że nie chciałeś umieszczać $classnamezmiennej między cudzysłowami? Ponieważ tak jest, to jest ciąg.
Alexis Wilke,
1
Wreszcie poprawna implementacja getElementsByClassName (zgodna z JavaScript), '$classname'oczywiście oprócz literału ciągu.
Joel Mellon,
1
Jest to rażąco nadmiernie skomplikowane. Zobacz odpowiedź @ DanielHaley, aby uzyskać poprawną odpowiedź XPath 2.0.
Michael Kay,
5

Możesz wypróbować następujące

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

Umesh Chhabra
źródło
0

Przybyłem tutaj, szukając rozwiązania dla Ranorex Studio 9.0.1. Nie ma tam jeszcze pliku zawierającego (). Zamiast tego możemy użyć wyrażeń regularnych takich jak:

div[@class~'atag']
Jarno Argillander
źródło
-1

Aby linki zawierające wspólny adres URL musiały być konsolowane w zmiennej. Następnie spróbuj po kolei.

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();
}
użytkownik3906232
źródło