如何使用Xpath选择具有多个类的元素?

31
在上面的XML示例中,我想使用XPath选择所有属于类foo且不在类bar中的书籍。
<?xml version="1.0" encoding="ISO-8859-1"?>
<bookstore>
  <book class="foo">
    <title lang="en">Harry Potter</title>
    <author>J K. Rowling</author>
    <year>2005</year>
    <price>29.99</price>
  </book>
  <book class="foo bar">
    <title lang="en">Harry Potter</title>
    <author>J K. Rowling</author>
    <year>2005</year>
    <price>29.99</price>
  </book>
  <book class="foo bar">
    <title lang="en">Harry Potter</title>
    <author>J K. Rowling</author>
    <year>2005</year>
    <price>29.99</price>
  </book>
</bookstore>

2
好问题,+1。请查看我的答案,其中包含两种不同的XPath 2.0解决方案,其中第一种可能是最有效的,特别是对于非优化的XPath 2.0引擎。 - Dimitre Novatchev
3个回答

39

通过在@class值前后添加空格,您可以测试是否存在" foo "和" bar ",而不必担心它是首位、中间还是末尾,并且不会误判"food"或"barren"等不相关的@class值:

/bookstore/book[contains(concat(' ',@class,' '),' foo ')
        and not(contains(concat(' ',@class,' '),' bar '))]

1
如果@class中包含制表符或换行符,而不是空格,该怎么办?这时就可以使用normalize-space函数(XPath 1.0)了。它可以从字符串中删除前导和尾随的空白字符,并将连续的空白字符替换为单个空格,例如concat(' ',normalize-space(@class),' ') - Steven Pribilinskiy
@Steven Pribilinskiy - 那是不必要的。由于XML解析器对属性值进行规范化的方式,制表符和回车已经被规范化成空格了。http://www.w3.org/TR/xml/#AVNormalize - Mads Hansen

11

虽然我喜欢Mads的解决方案:这里是另一种XPath 2.0的方法:

/bookstore/book[
                 tokenize(@class," ")="foo" 
                 and not(tokenize(@class," ")="bar")
               ]

请注意,以下表达式都是正确的:
("foo","bar")="foo" -> true
("foo","bar")="bar" -> true

XPath 2.0的解决方案加1分。许多事情在2.0中更容易。 - Mads Hansen

4

XPath 2.0:

/*/*[for $s in concat(' ',@class,' ') 
            return 
               matches($s, ' foo ') 
             and 
              not(matches($s, ' bar '))
      ]

这里没有进行分词处理,$s只计算了一次。

甚至可以这样写:

/*/book[@class
          [every $t in tokenize(.,' ') satisfies $t ne 'bar']
          [some  $t in tokenize(.,' ') satisfies $t eq 'foo']
       ]

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