Як я можу зіставити атрибут, який містить певний рядок?


442

У мене виникають проблеми з вибором вузлів за атрибутом, коли атрибути містять більше одного слова. Наприклад:

<div class="atag btag" />

Це мій вираз xpath:

//*[@class='atag']

Вираз працює з

<div class="atag" />

але не для попереднього прикладу. Як я можу вибрати <div>?


9
Варто зазначити, я думаю, що "atag btag" - це один атрибут, а не два. Ви намагаєтеся виконати відповідність підрядків у xpath.
skaffman

3
Так, ти маєш рацію - ось що я хочу.
crazyrails


1
Ось чому ви повинні використовувати селектор CSS ... div.atagабо div.btag. Супер просте, не збігання рядків, і ШЛИШЕ швидше (і краще підтримується в браузерах). XPath (проти HTML) слід перенести на те, що корисно для ... пошуку елементів за текстом, що міститься, та для навігації DOM.
JeffC

Відповіді:


486

Ось приклад, який знаходить елементи div, чий className містить atag:

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

Ось приклад, який знаходить елементи div, чий className містить atagта btag:

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

Однак вони також знайдуть часткові відповідники на кшталт class="catag bobtag".

Якщо ви не хочете часткових матчів, дивіться відповідь bobince нижче.


123
@Redbeard: Це буквальна відповідь, але не зазвичай те, на що має спрямовуватися рішення, що відповідає класу. Зокрема, він би відповідав <div class="Patagonia Halbtagsarbeit">, який містить цільові рядки, але не є поділом із даними класами.
bobince

3
Це буде працювати для простих сценаріїв, але слідкуйте, чи хочете ви використовувати цю відповідь у більш широких контекстах з меншим або відсутнім контролем над значеннями атрибутів, на які ви перевіряєте. Правильна відповідь - це бобінець.
Олівер

16
Вибачте, це не відповідає класу, воно відповідає підрядку
Timo Huovinen

5
це абсолютно неправильно, оскільки він також знаходить: <div class = "annatag bobtag"> чого він не повинен.
Олексій Виноградов

6
Питання було "містить певний рядок" не "відповідає певному класу"
Ельзас

303

Відповідь mjv - це гарний початок, але не вдасться, якщо atag не буде вказаним першим іменем класу.

Звичайний підхід - досить громіздкий:

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

це працює до тих пір, поки класи розділені лише пробілами, а не іншими формами пробілів. Це майже завжди так. Якщо це може бути не так, вам доведеться все-таки зробити його ще більш важким:

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

(Вибір рядків, поділених простором, поділених простором, такий звичайний випадок, що дивно, що для нього немає специфічної функції XPath, як 'CSS3' [class ~ = "atag"] '.)


58
так, xpath потребує виправлень
Randy L

13
Відповідь @ Rededard supra123 є проблематичною, якщо є клас css на зразок "atagnumbertwo", який ви не бажаєте вибирати, хоча я визнаю, це може бути малоймовірним (:
drevicko

7
@crazyrails: Чи можете ви прийняти цю відповідь як правильну відповідь? Це допоможе майбутнім шукачам визначити правильне рішення проблеми, описаної вашим запитанням. Дякую!
Олівер

2
@ cha0site: Так, вони могли, в XPath 2.0 та наступних. Ця відповідь була написана ще до того, як XPath 2.0 став офіційним. Див stackoverflow.com/a/12165032/423105 або stackoverflow.com/a/12165195/423105
LarsH

1
Не будьте схожими на мене і видаліть пробіли навколо класу, який ви шукаєте в цьому прикладі; вони насправді важливі. Інакше це може здатися спрацьовим, але перемагає мету.
CTS_AE

40

спробуйте це: //*[contains(@class, 'atag')]


Що робити, якщо назва класу grabatagonabag? (Підказка: все одно буде відповідати.)
Уейн

38

EDIT : см рішення bobince, який використовує містить замість запуску, з , поряд з трюком , щоб забезпечити порівняння робиться на рівні повних маркерів (щоб в «ATAG» шаблон можна знайти як частина іншого «тега»).

"atag btag" - непарне значення для атрибута класу, але ніколи не намагайтеся:

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

Ви можете використовувати це, якщо ваш движок XPath підтримує команду
start

10
@mjv: В атрибуті класу CSS прийнято вказати кілька значень. Ось так робиться CSS.
skaffman

7
@mjv Ви не можете гарантувати, що це ім’я з’явиться на початку атрибута класу.
Алан Крюгер

@thuktun @skaffman. Дякую, чудові коментарі. Я відповідно "перенаправився" на розчин bobince.
mjv

Не працює для <div class = "btag atag">, що еквівалентно вище
Олексій Виноградов

30

2.0 XPath, який працює:

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

або зі змінною:

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

Як це може працювати, якщо @classмає більше одного елемента? Тому що він поверне список слів і порівняння цього рядка не вдається з помилковою кардинальністю .
Алексіс Вілке

3
@AlexisWilke - зі специфікації ( w3.org/TR/xpath20/#id-general-comparisons ): загальні порівняння - це екзистенційно кількісні порівняння, які можуть бути застосовані до послідовностей операндів будь-якої довжини. Це працює у кожному 2.0 процесорі, який я пробував.
Даніель Хейлі

1
Зауважте також, що в XPath 3.1 це можна спростити до//*[tokenize(@class)=$classname]
Майкл Кей

1
І для повноти, якщо вам пощастило використовувати процесор XPath, що обізнаний із схемами, і якщо @class має типовий список, тоді ви можете просто написати//*[@class=$classname]
Michael Kay

21

Майте на увазі, що відповідь bobince може бути надмірно складною, якщо ви можете припустити, що ім'я класу, яке вас цікавить, не є підрядком іншого можливого імені класу . Якщо це правда, ви можете просто використовувати відповідність підрядків за допомогою функції містить. Далі буде відповідати будь-який елемент, клас якого містить підрядку 'atag':

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

Якщо припущення вище не відповідає, відповідність підрядків буде відповідати елементам, які ви не маєте наміру. У цьому випадку ви повинні знайти межі слова. Використовуючи роздільники простору для пошуку меж імені класу, друга відповідь bobince знаходить точні збіги:

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

Це буде відповідати atagі ні matag.


Це рішення, яке я шукав. Він чітко знаходить "тест" у класі = "привіт світ тесту" та не відповідає "світ привіт-тест-тесту". Оскільки я використовую лише XPath 1.0 і не маю RegEx, це єдине рішення, яке працює.
Ян Станічек

7

Щоб додати відповідь bobince ... Якщо будь-який інструмент / бібліотека, яку ви використовуєте, використовує Xpath 2.0, ви також можете зробити це:

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

count (), мабуть, потрібен, тому що index-of () повертає послідовність кожного індексу, на який він відповідає в рядку.


1
Я думаю, ви мали намір НЕ ставити $classnameзмінну між цитатами? Бо як воно є, це рядок.
Алексіс Вілке

1
Нарешті, правильна (сумісна з JavasScript) реалізація getElementsByClassName ... окрім рядкового літералу, '$classname'звичайно.
Джоел Меллон

1
Це вкрай складно. Про правильну відповідь XPath 2.0 див. Відповідь @ DanielHaley.
Майкл Кей


0

Я прийшов сюди шукати рішення для Ranorex Studio 9.0.1. Там ще немає вмісту (). Натомість ми можемо використовувати регулярний вираз:

div[@class~'atag']

-1

Для посилань, що містять загальний URL, потрібно консолювати змінну. Потім спробуйте це послідовно.

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();
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.