Как я могу выбрать элемент с несколькими классами с помощью Xpath?

В приведенном выше примере xml я хотел бы выбрать все книги, принадлежащие классу foo, а не в строке класса, используя xpath.

<?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>

Ответ 1

Добавив значение @class в начало и конец пробела, вы можете проверить наличие "foo" и "bar", а не беспокоиться о том, было ли оно первым, средним или последним, и любые ложные положительные удары по "еда" или "бесплодные" значения @class:

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

Ответ 2

Хотя мне нравится решение Mads: Вот еще один подход для XPath 2.0:

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

Обратите внимание, что следующие выражения являются истинными:

("foo","bar")="foo" -> true
("foo","bar")="bar" -> true

Ответ 3

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']
       ]