如何在包含特定字符串的属性上进行匹配?

480

我在选取具有多个单词属性的节点时遇到了问题。例如:

<div class="atag btag" />

这是我的xpath表达式:

//*[@class='atag']

这个表达式可以匹配

<div class="atag" />

但无法匹配前面的例子。我该如何选择 <div> 元素?


9
值得指出的是,“atag btag”是一个单一的属性,而不是两个。您正在尝试在xpath中进行子字符串匹配。 - skaffman
4
没错,你说得对 - 那就是我想要的。 - crazyrails
相关:https://dev59.com/Omoy5IYBdhLWcg3wCpzQ和https://dev59.com/p3I-5IYBdhLWcg3w8NSB。 - Timo Huovinen
2
这就是为什么你应该使用CSS选择器... div.atagdiv.btag。非常简单,不需要字符串匹配,速度更快(并且在浏览器中得到更好的支持)。XPath(针对HTML)应该被归类为有用的内容... 通过包含的文本查找元素和进行DOM导航。 - JeffC
10个回答

547

这里有一个示例,查找类名包含 atag 的 div 元素:

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

以下是一个示例,它查找类名包含 atagbtag 的 div 元素:

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

然而,它也会找到类似 class="catag bobtag" 的部分匹配。

如果您不想要部分匹配,请参见下面bobince的答案。


126
@Redbeard:这是一个字面上的答案,但通常不是类匹配解决方案的目标。特别是它会匹配<div class="Patagonia Halbtagsarbeit">,虽然包含目标字符串,但并不是具有给定类名的div元素。 - bobince
3
这个方法对于简单的情况可以奏效 - 但是如果您想在属性值检查上没有或很少控制的更广泛的环境中使用这个答案,请小心。正确的答案是 bobince 的。 - Oliver
17
抱歉,它不匹配一个类,它匹配一个子字符串。 - Timo Huovinen
7
它明显是错误的,因为它也找到了:<div class="annatag bobtag"> 这不应该发生。 - Alexei Vinogradov
7
这个问题是关于“包含特定字符串”,而不是“匹配特定类”的。 - Alsatian

323

mjv的回答是一个不错的开始,但如果atag不是第一个列出的类名,则会失败。

通常的做法是相当笨重的:

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

只要类名之间仅由空格分隔,而不是其他形式的空白字符,这段代码就能正常工作。这几乎总是成立的。如果有可能不成立,你就需要让它变得更加复杂:

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

按类名类似于空格分隔的字符串进行选择是一个非常普遍的情况,令人惊讶的是XPath没有像CSS3的“[class〜=“atag”]”那样的特定函数。


62
巴,XPath需要一些修复。 - Randy L
13
如果存在类似于“atagnumbertwo”的CSS类,而你不想选择它,那么@Redbeard supra123的回答存在问题。尽管我承认这可能不太可能发生 (: - drevicko
7
@crazyrails: 你能否将这个答案选为正确答案?这将有助于未来的搜索者找到问题描述的正确解决方案。谢谢! - Oliver
2
@cha0site:是的,他们可以在XPath 2.0及其后续版本中实现。这个答案是在XPath 2.0正式发布之前编写的。请参见https://dev59.com/gnM_5IYBdhLWcg3waSX9#12165032或https://dev59.com/gnM_5IYBdhLWcg3waSX9#12165195。 - LarsH
1
不要像我一样,在这个例子中删除你正在寻找的类周围的空格;它们实际上很重要。否则,它可能看起来可以工作,但会失去意义。 - CTS_AE
显示剩余7条评论

42
尝试这个://*[contains(@class, 'atag')]

3
如果类名是grabatagonabag会怎样?(提示:它仍然匹配。) - Wayne

41

编辑: 查看bobince的解决方案,该方案使用contains而不是start-with,并使用一个技巧来确保比较在完整标记的级别上进行(以免“atag”模式被视为另一个“tag”的一部分)。

虽然 "atag btag" 对于类属性来说是奇怪的值,但仍然尝试:

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

如果你的XPath引擎支持starts-with命令,那么你可以使用这个命令。我记得JVM 6好像不支持它。 - Mohamed Faramawi
10
CSS类属性经常会指定多个值,这是CSS的常规做法。 - skaffman
7
@mjv无法保证该名称会出现在类属性的开头。 - Alan Krueger
@thuktun @skaffman。非常感谢你们的评论。我已经按照bobince的解决方案进行了“重定向”。 - mjv
不适用于<div class="btag atag">,它相当于上面的内容。 - Alexei Vinogradov

32

一个有效的 2.0 XPath:

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

或者使用一个变量:

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

如果 @class 有多个元素,这怎么能起作用呢?因为它将返回一个单词列表,将其与字符串进行比较会导致 错误的基数 - Alexis Wilke
3
根据规范(http://www.w3.org/TR/xpath20/#id-general-comparisons),“一般比较”是存在量化比较,可应用于任意长度的操作数序列。我已在尝试过的每个2.0处理器中成功使用过它。 - Daniel Haley
2
请注意,在XPath 3.1中,这可以简化为//*[tokenize(@class)=$classname] - Michael Kay
2
为了完整起见,如果您足够幸运地使用一个支持模式感知的XPath处理器,并且@class具有列表值类型,那么您可以简单地编写 //*[@class=$classname] - Michael Kay

28

请注意,如果你可以假定你感兴趣的类名不是另一个可能的类名的子字符串,那么bobince的答案可能过于复杂。 如果这是真的,你可以简单地使用包含函数进行子字符串匹配。 以下代码将匹配其类包含子字符串“atag”的任何元素:

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

如果上面的假设不成立,子字符串匹配将会匹配到你不想要的元素。在这种情况下,你必须找到单词边界。通过使用空格分隔符来找到类名边界,bobince的第二个答案找到了精确的匹配:

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

这将匹配atag而不是matag


这就是我一直在寻找的解决方案。它清楚地在class ='hello test world'中找到了“test”,并且不匹配“hello test-test world”。由于我只使用XPath 1.0,没有RegEx,这是唯一可行的解决方案。 - Jan Stanicek
这与@bobince的答案有何不同? - Nakilon
@Nakilon,最完整的解决方案是我在这里提出的第二个,与bobince的第二个答案相同。然而,第一个解决方案更简单易懂,更容易阅读,但只有在您的类名不能成为彼此子字符串的情况下才正确。第二个方案更具通用性,但如果假设对您特定的应用程序合理,则第一个方案更可取。 - Brent Atkinson

8
要补充Bobince的答案... 如果你使用的工具/库使用Xpath 2.0,你也可以这样做:
//*[count(index-of(tokenize(@class, '\s+' ), $classname)) = 1]

显然需要使用count()函数,因为index-of()函数返回字符串中匹配的每个索引的一个序列。


1
我想你的意思是不应该在引号中放置$classname变量,因为它现在是一个字符串。 - Alexis Wilke
1
除了字符串字面量'$classname'之外,终于有一个正确的(兼容JavaScript)getElementsByClassName实现了。 - Joel Mellon
1
这个过于复杂了。请参考@DanielHaley的回答,获取正确的XPath 2.0答案。 - Michael Kay

4
你可以尝试以下内容:

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


0

我来这里寻找 Ranorex Studio 9.0.1 的解决方案。目前还没有 contains() 方法,但我们可以使用正则表达式,例如:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接