2
votes

Given the following html:

<div>
    <div id="t1" foo="bar" class="myClass">cello</div>
    <div id="t2" class="myClass">melo there</div>
    <div id="t3" class="myClass">quello</div>
</div>
<div>
    <div id="t4" foo="bar" class="myClass">cello</div>
    <div id="t5" class="myClass">melo there</div>
    <div id="t6" class="myClass">quello</div>
</div>

With jquery (in 1.11.1 and probably other versions), we can use the following expression:

div.myClass:not([foo=bar]:last)

It will return t1, t2, t3, t5, t6. In other words, it selects all div with class=myClass, and removes all of those divs which has an attribute foo with value bar, except for the last one of those divs.

Now, I'm trying to find the equivalent xpath expression with xpath 1.0. Does anybody know if an xpath equivalent expression is possible, and if so, what would that xpath expression be?

So far, here are the xpath expressions that I tried (using php), none of them returns 1,2,3,5,6 as it should:

$x = "//div[@class='myClass'][not(self::node()[@foo='bar'])]"; // 2,3,5,6
$x = "//div[@class='myClass'][not(self::node()[@foo='bar'][position()=last()])]"; // 2,3,5,6
$x = "//div[@class='myClass'][not(self::node()[@foo='bar'] and position()=last() )]"; // 1,2,3,4,5,6
$x = "//div[@class='myClass'][not(self::node()[@foo='bar'] and position()=1 )]"; // 2,3,5,6
$x = "//div[@class='myClass'][not(self::node()[@foo='bar'])[position()=last()]]"; //  DOMXPath::query(): Invalid type
$x = "//div[@class='myClass'][not(self::node()[@foo='bar'])][position()=last()]"; //  3,6
$x = "//div[@class='myClass'][not(self::node()[@foo='bar'])][position()<last()]"; //  2,5
$x = "//div[@class='myClass'][not(self::node()[@foo='bar'])][position()=1]"; //  2,5
$x = "//div[@class='myClass'][not(self::node()[@foo='bar'][4])]"; // 1,2,3,4,5,6
$x = "//div[@class='myClass'][not(self::node()[@foo='bar'][1])]"; // 2,3,5,6
1
I think the problem is to do with translating jQuery's (or rather Sizzle's) :last pseudo to XPath which - and I could be wrong here - deals only in positions within DOMs, not positions in the returned stack (which is what :last does. Don't see an obvious solution to this without approaching it with different logic or using multiple nodesets. - Mitya
can you please post your expected ouput - Raghavendra
@raghavendra: He said t1, t2, t3, t5, t6 - LarsH

1 Answers

5
votes
//div[@class='myClass']
  [not(count(. | (//div[@class='myClass' and @foo='bar'])[last()]) = 1)]

or to put it another way if you don't require a literal not:

//div[@class='myClass']
  [count(. | (//div[@class='myClass' and @foo='bar'])[last()]) > 1]

These rely on the fact that in XPath 1.0, the way to test whether a node selected by one expression is the same node as the one selected by another expression is to ask whether

count(A | B) = 1

If A and B select the same node, then the union | of them is just one node. Conversely, if the union contains more than one node, then A and B are not the same node.