PrintTicket, names, and XPath

There's an easy mistake to make when writing XPath queries on PrintTickets, and I want to set the record straight on how to write this query correctly to save you some pain.  Here's the set-up.  I have a PrintTicket that looks like this, and I want to find the PageMediaSizeFeature:

 
<psf:PrintTicket
  xmlns:psf="https://.../printschemaframework"
  xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="https://www.w3.org/2001/XMLSchema" 
  xmlns:psk="https://.../printschemakeywords" version="1" >
    <psf:Feature name="psk:PageMediaSize">...</psf:Feature>
...
</psf:PrintTicket>

So, the easiest thing to do would be to write an XPath query.  I create a DOM object, load my XML, set the selection namepsaces for psk and psf, and then make a query that looks like this:

   /* THIS IS INCORRECT! */
  "psf:PrintTicket/psf:Feature[@name='psk:PageMediaSize']"

This simple query is going to work great about 90% of the time.  It's going to fail as soon as someone sends my application a document that associates a different prefix with the PrintSchema namespace URI, because XPath doesn't know that the value in the name attribute is actually a QName.  It thinks it's looking for the literal string with starting with the characters 'p', 's', 'k' and ':'.

It is possible to correct this query in XPath.  The syntax to correctly break out the namespace isn't trivial, but once you've got it, you don't need to look work it all out again and probably don't really want to (just like that perl code I wrote back in college). Here it is:

   /* THIS IS THE CORRECT VERSION */
  "psf:PrintTicket/psf:Feature
    [substring-after(@name,':')='PageMediaSize']
    [name(namespace::*[.='https://.../printschemakeywords'])
       =substring-before(@name,':')]"

If you stare at it for a minute, and stop seeing all hte embedded smily emoticons, it's pretty easy to work out the first two lines. Find the feature where the part of the name after the ':' is PageMediaSize. The second bit is tougher. It'll take a bit of digging in an XPath spec to really understand if you're not already an XPath expert, but in short, it compares the prefix declared for the PrintSchema URI against the prefix on the current element's name attribute.

You can re-use this same technique for option, property, and parameter names as well.

[edit: corrected typo in xpath query]