1

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.

5
  • Please, do your homework and at least provide real, well-formed and meaningful XML document. Commented Dec 20, 2011 at 14:18
  • What is it you want, Dimitre? This is my real XML. I didn't think the content was relevant. Commented Dec 20, 2011 at 14:31
  • @swornabsent : Dimitre is right, your XML is not well formed. The way you write your lang attribute is not XML. You should update your question to make it valid (e.g. lang="..."). Commented Dec 20, 2011 at 14:36
  • Ok, sorry to confuse. There's more going on here than is relevant to this particular question, and I was just trying to supply the relevant template and so opted for some shorthand. Naturally the content is of lang='en' (or whatever). Commented Dec 20, 2011 at 14:39
  • @swornabsent: Even if you fix the issue with the attribute, would you bother to fill the elements with content? What you have provided looks more like a "skeleton" of a real XML document. Commented Dec 20, 2011 at 14:50

3 Answers 3

6

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks Vincent, that clears things up. I got ahead of myself.
the same logic works also when you want to test something on next siblings i.e. ... and contains(text(), 'Some text 1') and ../td[4] = 'Some text ...
1

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.

2 Comments

Thanks Dimitre. This has been a bit skunkworks, and I initially had some server-side PHP logic locating the desired parent element depending on what kind of results were produced. This is helpful, as have been your other responses.
@swornabsent: You are welcome. I just updated my answer to give you an idea about the requested "universal syntax" -- enjoy :)
1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.