swornabsent
swornabsent

Reputation: 994

XPath query with variable multiple conditions

Long-time listener, first-time caller. I'm relatively new to XPath and looked at several other threads here and elsewhere but I can't seem to get a query working, any help would be great.

I have XML as follows:

<catalog>
  <book pgid="28054" lang="en">
    <title>The Brothers Karamazov</title>
    <author>Dostoyevsky, Fyodor</author>
    <friendly_title>The Brothers Karamazov by Fyodor Dostoyevsky</friendly_title>
    <file>
      <type>ePub</type>
      <path>cache/generated/28054/</path>
      <name>pg28054.epub</name>
      <size>800</size>
    </file>
    <file>
      <type>PDF</type>
      <path>2/8/0/5/28054/</path>
      <name>28054-pdf.pdf</name>
      <size>5829</size>
    </file>
    <file>
      <type compression="zipped">PDF</type>
      <path>2/8/0/5/28054/</path>
      <name>28054-pdf.zip</name>
      <size>1693</size>
    </file>
    <file>
      <type encoding="utf-8" compression="zipped">Text</type>
      <path>2/8/0/5/28054/</path>
      <name>28054-0.zip</name>
      <size>726</size>
    </file>
  </book>
</catalog>

(catalog is the root element, and in this example there are no <contributor> elements)

I have the query working on author, contributor, title, and language searches, but I am getting hung up on adding a file type condition. This query to find books with author OR contributor containing "Dostoyevsky" and title containing "Brothers" with language "en" is working (i.e. giving expected results), but if there's a better way to write it I'm all ears:

/catalog//book/*[(contains(self::author,'Dostoyevsky') or contains(self::contributor,'Dostoyevsky')) and contains(../title,'Brothers') and ../@lang = 'en']

What I can't get to work is limiting the query results to files of a certain type, i.e. appending and ../file/type='PDF' or something. Didn't have any luck with | unions either.

Thanks in advance.

Oh, and if it matters, the query needs to be built dynamically (from form input), so it needs to retain a universal syntax that would work with any number of user-supplied criteria.

Upvotes: 1

Views: 30129

Answers (3)

First Zero
First Zero

Reputation: 22356

A simpler way for doing this is to filter for the two/three conditions separately and join them,

<xsl:for-each select="//catalog//title[contains(., 'Dostoyevsky')] | //catalog//author[contains(., 'Brothers')]">

</xsl:for-each>

The pipe (|) will combine each result

Upvotes: 1

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243459

This query to find books with author OR contributor containing "Dostoyevsky" and title containing "Brothers" with language "en" is working (i.e. giving expected results), but if there's a better way to write it I'm all ears:

/catalog//book/*[(contains(self::author,'Dostoyevsky') or contains(self::contributor,'Dostoyevsky')) and

contains(../title,'Brothers') and ../@lang = 'en']

The XPath expression above is not only quite imperfect and absolutely un-formatted and unreadable, but, more importantly, it doesn't (as stated) select any book element at all (it may select an author). Also, the // pseudo-operator isn't necessary and may significantly reduce the efficiency of XPath evaluation on any real world moderate to large XML document.

Here is an XPath expression that selects what you want:

 /catalog/book
    [@lang='en'
    and
     file/type='PDF'
    and
     *[self::author
      or
       self::contributor
       ]
        [contains(., 'Dostoyevsky')]
       and
         contains(title, 'Brothers')
     ]

Oh, and if it matters, the query needs to be built dynamically (from form input), so it needs to retain a universal syntax that would work with any number of user-supplied criteria.

This "universal syntax may look something like this:

/*/book
   [
    contains(*[name() = $pName1], $pString1) 
   and
    contains(*[name() = $pName2], $pString2) 
 . . . . . .
   and
    contains(*[name() = $pNameK], $pStringK) 
    ]

where $pName1, $pName2, ..., $pNameK should be substituted by the names of the fields the end user has specified in the search form, and

$pString1, $pString2, ..., $pStringK should be substituted by the data that the user has indicated should be contained in the corresponding fields.

Upvotes: 1

Vincent Biragnet
Vincent Biragnet

Reputation: 2998

If I get you right, this should work :

/catalog[file/type='PDF']//book/*[(contains(self::author,'Dostoyevsky') or contains(self::contributor,'Dostoyevsky')) and contains(../title,'Brothers') and ../@lang = 'en']

Note that the filter is directly on the catalog element.

If you try to get book elements, maybe you should use /catalog[file/type=...]//book[test1][test2][test3]... with your different constraints. Each new test act as a filter.

Upvotes: 6

Related Questions