Reputation: 994
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
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
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
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