merged branch lazyhammer/issue-8068 (PR #8072)

This PR was merged into the 2.3 branch.

Discussion
----------

[CssSelector] Fix :nth-last-child() translation

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #8068
| License       | MIT
| Doc PR        | n/a

Commits
-------

2d9027d [CssSelector] Fix :nth-last-child() translation
This commit is contained in:
Fabien Potencier 2013-05-19 20:59:12 +02:00
commit f723bda26e
3 changed files with 51 additions and 26 deletions

View File

@ -47,7 +47,7 @@ class CssSelectorTest extends \PHPUnit_Framework_TestCase
array('h1', "h1"),
array('foo|h1', "foo:h1"),
array('h1, h2, h3', "h1 | h2 | h3"),
array('h1:nth-child(3n+1)', "*/*[name() = 'h1' and ((position() -1) mod 3 = 0 and position() >= 1)]"),
array('h1:nth-child(3n+1)', "*/*[name() = 'h1' and (position() - 1 >= 0 and (position() - 1) mod 3 = 0)]"),
array('h1 > p', "h1/p"),
array('h1#foo', "h1[@id = 'foo']"),
array('h1.foo', "h1[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"),

View File

@ -101,12 +101,11 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
array('e[foo*="bar"]', "e[@foo and contains(@foo, 'bar')]"),
array('e[hreflang|="en"]', "e[@hreflang and (@hreflang = 'en' or starts-with(@hreflang, 'en-'))]"),
array('e:nth-child(1)', "*/*[name() = 'e' and (position() = 1)]"),
array('e:nth-last-child(1)', "*/*[name() = 'e' and (position() = last() - 1)]"),
array('e:nth-last-child(2n+2)', "*/*[name() = 'e' and ((position() +2) mod -2 = 0 and position() < (last() -2))]"),
array('e:nth-last-child(1)', "*/*[name() = 'e' and (position() = last() - 0)]"),
array('e:nth-last-child(2n+2)', "*/*[name() = 'e' and (last() - position() - 1 >= 0 and (last() - position() - 1) mod 2 = 0)]"),
array('e:nth-of-type(1)', "*/e[position() = 1]"),
array('e:nth-last-of-type(1)', "*/e[position() = last() - 1]"),
array('e:nth-last-of-type(1)', "*/e[position() = last() - 1]"),
array('div e:nth-last-of-type(1) .aclass', "div/descendant-or-self::*/e[position() = last() - 1]/descendant-or-self::*/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' aclass ')]"),
array('e:nth-last-of-type(1)', "*/e[position() = last() - 0]"),
array('div e:nth-last-of-type(1) .aclass', "div/descendant-or-self::*/e[position() = last() - 0]/descendant-or-self::*/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' aclass ')]"),
array('e:first-child', "*/*[name() = 'e' and (position() = 1)]"),
array('e:last-child', "*/*[name() = 'e' and (position() = last())]"),
array('e:first-of-type', "*/e[position() = 1]"),
@ -121,7 +120,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
array('e:ConTains(foo)', "e[contains(string(.), 'foo')]"),
array('e.warning', "e[@class and contains(concat(' ', normalize-space(@class), ' '), ' warning ')]"),
array('e#myid', "e[@id = 'myid']"),
array('e:not(:nth-child(odd))', "e[not((position() -1) mod 2 = 0 and position() >= 1)]"),
array('e:not(:nth-child(odd))', "e[not(position() - 1 >= 0 and (position() - 1) mod 2 = 0)]"),
array('e:nOT(*)', "e[0]"),
array('e f', "e/descendant-or-self::*/f"),
array('e > f', "e/f"),
@ -188,17 +187,32 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
array('li:nth-child(+2n+1)', array('first-li', 'third-li', 'fifth-li', 'seventh-li')),
array('li:nth-child(odd)', array('first-li', 'third-li', 'fifth-li', 'seventh-li')),
array('li:nth-child(2n+4)', array('fourth-li', 'sixth-li')),
// FIXME: I'm not 100% sure this is right:
array('li:nth-child(3n+1)', array('first-li', 'fourth-li', 'seventh-li')),
array('li:nth-last-child(0)', array('seventh-li')),
array('li:nth-child(n)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')),
array('li:nth-child(n-1)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')),
array('li:nth-child(n+1)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')),
array('li:nth-child(n+3)', array('third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')),
array('li:nth-child(-n)', array()),
array('li:nth-child(-n-1)', array()),
array('li:nth-child(-n+1)', array('first-li')),
array('li:nth-child(-n+3)', array('first-li', 'second-li', 'third-li')),
array('li:nth-last-child(0)', array()),
array('li:nth-last-child(2n)', array('second-li', 'fourth-li', 'sixth-li')),
array('li:nth-last-child(even)', array('second-li', 'fourth-li', 'sixth-li')),
array('li:nth-last-child(2n+2)', array('second-li', 'fourth-li')),
array('li:nth-last-child(2n+2)', array('second-li', 'fourth-li', 'sixth-li')),
array('li:nth-last-child(n)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')),
array('li:nth-last-child(n-1)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')),
array('li:nth-last-child(n-3)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')),
array('li:nth-last-child(n+1)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')),
array('li:nth-last-child(n+3)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li')),
array('li:nth-last-child(-n)', array()),
array('li:nth-last-child(-n-1)', array()),
array('li:nth-last-child(-n+1)', array('seventh-li')),
array('li:nth-last-child(-n+3)', array('fifth-li', 'sixth-li', 'seventh-li')),
array('ol:first-of-type', array('first-ol')),
array('ol:nth-child(1)', array()),
array('ol:nth-child(1)', array('first-ol')),
array('ol:nth-of-type(2)', array('second-ol')),
// FIXME: like above (1) or (2)?
array('ol:nth-last-of-type(1)', array('first-ol')),
array('ol:nth-last-of-type(1)', array('second-ol')),
array('span:only-child', array('foobar-span')),
array('li div:only-child', array('li-div')),
array('div *:only-child', array('li-div', 'foobar-span')),

View File

@ -67,25 +67,38 @@ class FunctionExtension extends AbstractExtension
}
if (0 === $a) {
return $xpath->addCondition('position() = '.($last ? 'last() - '.$b : $b));
return $xpath->addCondition('position() = '.($last ? 'last() - '.($b - 1) : $b));
}
if ($a < 0) {
if ($b < 1) {
return $xpath->addCondition('false()');
}
$sign = '<=';
} else {
$sign = '>=';
}
$expr = 'position()';
if ($last) {
// todo: verify if this is right
$a = - $a;
$b = - $b;
$expr = 'last() - '.$expr;
$b--;
}
$conditions = 1 === $a
? array()
: array(sprintf('(position() %s) mod %s = 0', $b > 0 ? (string) (- $b) : '+'.(- $b), $a));
if (0 !== $b) {
$expr .= ' - '.$b;
}
$conditions = array(sprintf('%s %s 0', $expr, $sign));
if ($b >= 0) {
$conditions[] = 'position() >= '.$b;
} elseif ($last) {
$conditions[] = sprintf('position() < (last() %s)', $b);
if (1 !== $a && -1 !== $a) {
$conditions[] = sprintf('(%s) mod %d = 0', $expr, $a);
}
return $xpath->addCondition(implode(' and ', $conditions));
// todo: handle an+b, odd, even
// an+b means every-a, plus b, e.g., 2n+1 means odd
// 0n+b means b
@ -93,8 +106,6 @@ class FunctionExtension extends AbstractExtension
// an means every a elements, i.e., 2n means even
// -n means -1n
// -1n+6 means elements 6 and previous
return empty($conditions) ? $xpath : $xpath->addCondition(implode(' and ', $conditions));
}
/**