From 2d9027d9a29053fae8a97dc617fb7a43ba9cddea Mon Sep 17 00:00:00 2001 From: Dmitrii Chekaliuk Date: Fri, 17 May 2013 02:48:20 +0300 Subject: [PATCH] [CssSelector] Fix :nth-last-child() translation --- .../Component/CssSelector/CssSelectorTest.php | 2 +- .../Tests/XPath/TranslatorTest.php | 38 +++++++++++++------ .../XPath/Extension/FunctionExtension.php | 37 +++++++++++------- 3 files changed, 51 insertions(+), 26 deletions(-) diff --git a/src/Symfony/Component/CssSelector/CssSelectorTest.php b/src/Symfony/Component/CssSelector/CssSelectorTest.php index e2ca42ff4c..a65a3f6730 100644 --- a/src/Symfony/Component/CssSelector/CssSelectorTest.php +++ b/src/Symfony/Component/CssSelector/CssSelectorTest.php @@ -45,7 +45,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 ')]"), diff --git a/src/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php b/src/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php index af915e30f9..7a90c3b69b 100644 --- a/src/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php +++ b/src/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php @@ -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')), diff --git a/src/Symfony/Component/CssSelector/XPath/Extension/FunctionExtension.php b/src/Symfony/Component/CssSelector/XPath/Extension/FunctionExtension.php index 2e120ba075..8c8f6f1ee8 100644 --- a/src/Symfony/Component/CssSelector/XPath/Extension/FunctionExtension.php +++ b/src/Symfony/Component/CssSelector/XPath/Extension/FunctionExtension.php @@ -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)); } /**