0

Here is my document:

<library>
  <reserved>
    <book>Don Quixot</book>
  </reserved>
  <all>
    <book>Don Quixot</book>
    <book>War and Peace</book>
  </all>
</library>

I'm trying to select all books that are not reserved now:

/library/all/book[not(/library/reserved/book[.=current()])]

This doesn't work, since current() inside the second pair of squared brackets already is inside the scope of reserved elements. What should I use instead of current()?

yegor256
  • 102,010
  • 123
  • 446
  • 597

1 Answers1

3

I'm trying to select all books that are not reserved now:

Answer:

/library/all/book[not(text() = /library/reserved/book/text())]

My previous answer did not work in all cases (see @MichaelKay's comment).

The reason why my struck-out answer does not work is because the predicate is trying to find at least one node in the node-set selected with reserved/book/text() with a text that is not equal to the current text(). This is different from finding a book where there does not exist a reserved book with the same text.

The predicate in my new answer will attempt to find a reserved book with the same text, and upon failing to do so (whether there are no reserved books or none of the reserved books have the same text, not() will negate the false result and return the book that is not reserved.

See this answer on another question for further explanation.

From the spec:

If one object to be compared is a node-set and the other is a string, then the comparison will be true if and only if there is a node in the node-set such that the result of performing the comparison on the string-value of the node and the other string is true.

The != operator will attempt to return true if it finds at least one node with a different text. The = operator will attempt to return true if at least one node has the same text. Both will return false if the node-set is empty. Therefore, the solution is to return true if there are no reserved books (empty node-set), or if none of the reserved books have the same text, ergo not(text() = /library/reserved/book/text()).

If it is guaranteed that no two <book> elements in each list will have the same text content, then this will work:

/library/all/book[text() != ../../reserved/book/text()]

George Aristy
  • 1,373
  • 15
  • 17
  • Wrong. That finds a book if there is a book with a different title that is reserved. You can see this gives the wrong answer if you test it on a file where there are no reserved books, or two reserved books. You want /library/all/book[not(text() = ../../reserved/book/text())] – Michael Kay Jan 14 '18 at 22:30
  • @MichaelKay you are correct. I'm currently checking and cannot find explanation for why `not(text() = ../../reserved/book/text())` behaves different from the answer I provided. – George Aristy Jan 14 '18 at 22:39