Как я могу сопоставить атрибут, содержащий определенную строку?

442

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

<div class="atag btag" />

Это мое выражение xpath:

//*[@class='atag']

Выражение работает с

<div class="atag" />

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

crazyrails
источник
9
Я думаю, стоит отметить, что «atag btag» - это один атрибут, а не два. Вы пытаетесь выполнить сопоставление подстроки в xpath.
Скаффман
3
Да, вы правы - это то, что я хочу.
crazyrails
Связанный: stackoverflow.com/questions/8808921/… и stackoverflow.com/questions/1604471/…
Тимо Хуовинен
1
Вот почему вы должны использовать селектор CSS ... div.atagили div.btag. Супер просто, не соответствует строке, и ПУТЬ быстрее (и лучше поддерживается в браузерах). XPath (против HTML) должен быть отнесен к тому, что полезно для ... поиска элементов по содержанию текста и для навигации по DOM.
Джефф

Ответы:

486

Вот пример, который находит элементы div, чье className содержит atag:

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

Вот пример, который находит элементы div, чье className содержит atagи btag:

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

Тем не менее, он также найдет частичные совпадения, как class="catag bobtag".

Если вы не хотите частичных совпадений, см. Ответ Бобинса ниже.

surupa123
источник
123
@Redbeard: Это буквальный ответ, но не всегда то, к чему должно стремиться решение для сопоставления классов. В частности, он будет соответствовать <div class="Patagonia Halbtagsarbeit">, который содержит целевые строки, но не является div с заданными классами.
бобинце
3
Это будет работать для простых сценариев - но будьте осторожны, если вы хотите использовать этот ответ в более широком контексте с меньшим или отсутствующим контролем над значениями атрибутов, которые вы проверяете. Правильный ответ Бобинса.
Оливер
16
Извините, это не соответствует классу, это соответствует подстроке
Тимо Хуовинен
5
это явно неправильно, так как он находит также: <div class = "annatag bobtag"> что не должно.
Алексей Виноградов
6
Вопрос был «содержит определенную строку», а не «соответствует определенному классу»
Эльзасский
303

Ответ mjv - хорошее начало, но не удастся, если atag - не первое имя класса в списке.

Обычный подход довольно громоздкий:

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

это работает до тех пор, пока классы разделены только пробелами, а не другими формами пробелов. Это почти всегда так. Если это не так, вы должны сделать его еще более громоздким:

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

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

bobince
источник
58
Ба, xpath нужны некоторые исправления
Рэнди Л
13
Ответ @Redbeard 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')]

SelenUser
источник
Что если имя класса grabatagonabag? (Подсказка: все равно совпадет.)
Уэйн
38

РЕДАКТИРОВАТЬ : см. Решение Бобинса, которое использует содержит, а не начать , а также хитрость, чтобы убедиться, что сравнение выполняется на уровне полного токена (чтобы шаблон «atag» не был найден как часть другого «тега»).

«atag btag» является нечетным значением для атрибута класса, но тем не менее попробуйте:

//*[starts-with(@class,"atag")]
MJV
источник
вы можете использовать это, если ваш движок XPath поддерживает команду старт-с, например, JVM 6 не поддерживает ее, насколько я помню,
Мохамед Фарамави,
10
@mjv: Атрибут класса CSS обычно указывает несколько значений. Вот как CSS делается.
Скаффман
7
@mjv Вы не можете гарантировать, что это имя появится в начале атрибута класса.
Алан Крюгер
@thuktun @skaffman. Спасибо, отличные комментарии. Я «перенаправлен» к соответствующему решению.
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]
Майкл Кей,
21

Помните, что ответ Бобинса может быть слишком сложным, если вы можете предположить, что интересующее вас имя класса не является подстрокой другого возможного имени класса . Если это так, вы можете просто использовать сопоставление подстроки с помощью функции contains. Следующее будет соответствовать любому элементу, чей класс содержит подстроку 'atag':

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

Если приведенное выше предположение не выполняется, совпадение подстроки будет соответствовать элементам, которые вы не намереваетесь. В этом случае вы должны найти границы слова. Используя пробелы для определения границ имени класса, второй ответ Бобинса находит точные совпадения:

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

Это будет соответствовать atagи нет matag.

Брент Аткинсон
источник
Это решение, которое я искал. Он явно находит «test» в классе = «hello test world» и не соответствует «hello test-test world». Поскольку я использую только XPath 1.0 и у меня нет RegEx, это единственное решение, которое работает.
Ян Станичек
7

Чтобы добавить ответ Бобинса ... Если какой-либо инструмент / библиотека, которую вы используете, использует Xpath 2.0, вы также можете сделать это:

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

count (), очевидно, необходим, потому что index-of () возвращает последовательность каждого индекса, с которым у него есть совпадение в строке.

armyofda12mnkeys
источник
1
Я полагаю, вы хотели НЕ помещать $classnameпеременную между кавычками? Потому что как есть, это строка.
Алексис Уилке
1
Наконец, правильная (совместимая с JavasScript) реализация getElementsByClassName ... помимо строкового литерала, '$classname'конечно.
Джоэл Меллон
1
Это чрезвычайно сложно. См. Ответ @ DanielHaley для правильного ответа XPath 2.0.
Майкл Кей,
5

Вы можете попробовать следующее

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

Умеш Чхабра
источник
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();
}
user3906232
источник