|
There are no translations available.
When we try to find all elements with css class 'foo', the following XPath expression:
//div[@class='foo']
will not match the following example:
<div class='foo bar'>
The XPath expression above will not return multi-class elements. You will find the solution discussed, for the problem here and here. I have replaced spaces by dashes ("-"), in order to get a better grip on things.
The XPath expression that solves the issue is:
//div[contains(concat('-',@class,'-'),'-foo-')]
Why exactly does this expression work?
Let's look at the definitions for the functions contains() and concat():
| contains(string1,string2) |
Returns true if string1 contains string2, otherwise it returns false. |
| concat(string,string,...) |
Returns the concatenation of the strings. |
We try a few examples for the XPath expression:
| other |
contains("-other-","-foo-") |
false |
| -other- |
contains("--other--","-foo-") |
false |
| foo |
contains("-foo-","-foo-") |
true |
| foo- |
contains("-foo--","-foo-") |
true |
| -foo |
contains("--foo-","-foo-") |
true |
| -foo- |
contains("--foo--","-foo-") |
true |
| foo-bar |
contains("-foo-bar-","-foo-") |
true |
| bar-foo |
contains("-bar-foo-","-foo-") |
true |
| bar-foo-other |
contains("-bar-foo-other-","-foo-") |
true |
It seems to work for all the examples above.
But then again, how do we know that it will work for all possible examples? The general cases for which it has to work, are:
| Case |
Example |
General regular expression |
the value is at the beginning of the space-separated list, optionally preceded by spaces
|
foo-bar |
-*foo-+.* |
the value is at the end of the space-separated list, optionally followed by spaces
|
bar-foo |
.*-+foo-* |
the value is in the middle of the space-separated list
|
bar-foo-other |
.*-+foo-+.* |
In order to prove that the XPath expression is correct, we need to prove that the following expressions are true:
| beginning |
contains("--*foo-+.*-","-foo-") |
| end |
contains("-.*-+foo-*-","-foo-") |
| middle |
contains("-.*-+foo-+.*-","-foo-") |
From the definition of the function contains(), we can derive the rule:
x contains y when x = .*y.*
We must demonstrate that the matches for regular expressions on the left are fully-contained subsets of the matches for the regular expression on the right:
|
left |
right |
| beginning |
--*foo-+.*- |
.*-foo-.* |
| end |
-.*-+foo-*- |
|
| middle |
-.*-+foo-+.*- |
|
Therefore, we must demonstrate that the prefixes are a subset of the right-left prefix and the right suffixes are a subset of the right prefix for the regular expression on the right:
|
prefix |
|
suffix |
|
|
left |
right |
left |
right |
| beginning |
--* |
.*- |
-+.*- |
-.* |
| end |
-.*-+ |
|
-*- |
|
| middle |
-.*-+ |
|
-+.*- |
|
We represent the subset operator as <=. Any regular expression E is always a subset of the Any expression, that is, .* :
E <= .*
Now, we can construct the proof:
|
left |
right |
| beginning |
--* <= .*- -*- <= .*- -* <= .* |
-+.*- <= -.* +.*- <= .* |
| end |
-.*-+ <= .*- -.*-*- <= .*- -.*-* <= .*
|
-*- <= -.* *- <= .* |
| middle |
(same as end) |
(same as beginning) |
Therefore, the XPath expression for finding multi-class elements by CSS class is correct.
I guess that this is probably my favourite mathematical proof up till now. I wonder if anybody else concocted this stuff before (I suspect someone must have...), because then I should to credit that person for coming up with such very ugly table, before I did. I really had to share this with you guys :-)
|