Cédric Bignon
Cédric Bignon

Reputation: 13022

Multiple conditions (and operator) in XPath

I have the following XML:

<bookstore>
    <books>
        <book name="book1" title="Title 1" author="Author 1"/>
        <book name="book2" title="Title 2" author="Author 2"/>
        <book name="book3" title="Title 1" author="Author 2"/>
    </books>

    <myBooks>
        <book name="Test1" title="Title 1" author="Author 1"/>
        <book name="Test2" title="Title 2" author="Author 1"/>
        <book name="Test3" title="Title 1" author="Author 2"/>
    </myBooks>
</bookstore>

I want to get all name of book in myBooks that have not a corresponding book in books (title and author).

So, for the example, I want to retrieve: the book "Test2" because the pair ("Title 2", "Author 1") does not exist in books.

So far, I have:

//myBooks/book[not(@title = //books/book/@title and @author = //books/book/@author)]

But, of course, in that case, the above XPath does not work because the combination ("Title 2", "Author 1") exists (from "book2" and "book1").

How can I apply the and operator on the same node?

Upvotes: 13

Views: 52479

Answers (3)

alecxe
alecxe

Reputation: 473873

You can concatenate attribute values using concat() and compare them:

//myBooks/book[not(concat(@*[name() = "title" or name() = "author"], ",") = concat(//books/book/@*[name() = "title" or name() = "author"], ","))]

For every book in myBook it gets the concatenation of title and author attributes and compares it with the same concatenation for every book in books.

Demo (using xmllint):

$ xmllint input.xml --xpath '//myBooks/book[not(concat(@*[name() = "title" or name() = "author"], ",") = concat(//books/book/@*[name() = "title" or name() = "author"], ","))]'
<book name="Test2" title="Title 2" author="Author 1"/>

Upvotes: 4

Martin Honnen
Martin Honnen

Reputation: 167571

Assuming XSLT I would define a key <xsl:key name="book" match="books/book" use="concat(@title, '|', @author)"/> and then in the predicate you can check [not(key('book', concat(@title, '|', @author)))].

Upvotes: 3

Ian Roberts
Ian Roberts

Reputation: 122364

It depends on the version of XPath.

You can't do this in a single XPath 1.0 expression because it would require a structure like

/bookstore/myBooks/book[/bookstore/books/book[
                  @title = _outer-predicate_/@title and
                  @author = _outer-predicate_/@author]]/@name

i.e. you'd need to capture the book being tested by the outer predicate so you can compare it to the inner one. Pure XPath 1.0 doesn't support this, you'd have to iterate over the content of myBooks yourself (e.g. with an xsl:for-each, as you've tagged your question "xslt").

In XPath 2.0 you can do it in one expression with an explicit quantifier

/bookstore/myBooks/book[not(some $book in /bookstore/books/book satisfies
       ($book/@author = @author and $book/@title = @title))]/@name

Upvotes: 15

Related Questions