There is a brute force solution. I will demonstrate two attributes instead of three.
(
// web: link [@text! = 'Login' and @href! = 'Login.php'
and not (// web: link [@text = 'Login' or @href = 'Login.php'])]
| // web: link [@text! = 'Login' and @href = 'Login.php'
and not (// web: link [@text = 'Login'])]
| // web: link [@text = 'Login' and @href! = 'Login.php'
and not (// web: link [@text = 'Login' and @href = 'Login.php'])]
| // web: link [@text = 'Login' and @href = 'Login.php']
)[1]
That is, select all links where no attribute matches, but only if there is no link that matches better. Then select all links with a smaller attribute attribute, but only when there are no links with excellent attribute matching. Select links where only the first attribute matches, but only if there are no links in which both attributes match. Then select the links where both attributes match. Only one of these four conjuncts will be nonempty, so the | operator never combines anything. Finally, select the first link in document order if any of these node-sets has more than one element.
The reason I used only two attributes instead of three is because I did not want to enter all eight cases. You can omit the first case if you are not interested in any links, if at least one of the attributes does not match.
This is a situation where you might be better off simply selecting all the candidates for the much simpler query shown by Jeff , and then using different code to rank the results after, where you can more easily use iteration and variables to select the best candidate.
If you can use XPath 2 , you can use the comma operator (or the concat function ) to join the node sequences (which replace node-sets). Try this for example:
(
// web: link [@text = 'Login' and @href = 'Login.php' and @index = 0]
, // web: link [@text = 'Login' and @href = 'Login.php' and @index! = 0]
, // web: link [@text = 'Login' and @href! = 'Login.php' and @index = 0]
, // web: link [@text = 'Login' and @href! = 'Login.php' and @index! = 0]
, // web: link [@text! = 'Login' and @href = 'Login.php' and @index = 0]
, // web: link [@text! = 'Login' and @href = 'Login.php' and @index! = 0]
, // web: link [@text! = 'Login' and @href! = 'Login.php' and @index = 0]
, // web: link [@text! = 'Login' and @href! = 'Login.php' and @index! = 0]
)[1]
Aside, here's an easy way to rank each link, which makes them pretty simple. Imagine a bit field, one bit for each attribute that you want to test. If the first attribute matches, set the left-most bit, otherwise leave it open. If the second attribute matches, set the next most significant bit, etc. So, for your example, you get the following bit values:
011 link A: text = 'Sign In', href = 'Login.php', index = 0
100 link B: text = 'Login', href = 'Signin.php', index = 15
110 link C: text = 'Login', href = 'Login.php', index = 22
To select the best match, treat the bit fields as binary numbers. Link A has a rating of 3, link B has a rating of 4, and link C has a 6. (This is a bit like CSS selectors ). This is a way to model ordering criteria, but now that Iโve typed all this, I donโt quite understand that this leads to any short solution in XPath.