From 1469953df4a980d73e0fe4acc394765126740884 Mon Sep 17 00:00:00 2001 From: Dmitrii Chekaliuk Date: Fri, 17 May 2013 03:31:34 +0300 Subject: [PATCH] [CssSelector] Fix :nth-last-child() translation --- .../CssSelector/Node/FunctionNode.php | 51 ++++++++++--------- .../CssSelector/Tests/CssSelectorTest.php | 2 +- .../Tests/Node/FunctionNodeTest.php | 16 +++--- 3 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/Symfony/Component/CssSelector/Node/FunctionNode.php b/src/Symfony/Component/CssSelector/Node/FunctionNode.php index d645c20aa1..2419f1de97 100644 --- a/src/Symfony/Component/CssSelector/Node/FunctionNode.php +++ b/src/Symfony/Component/CssSelector/Node/FunctionNode.php @@ -100,43 +100,46 @@ class FunctionNode implements NodeInterface $xpath->addStarPrefix(); if ($a == 0) { if ($last) { - $b = sprintf('last() - %s', $b); + $b = sprintf('last() - %s', $b - 1); } $xpath->addCondition(sprintf('position() = %s', $b)); return $xpath; } + if ($a < 0) { + if ($b < 1) { + $xpath->addCondition('false()'); + + return $xpath; + } + + $sign = '<='; + } else { + $sign = '>='; + } + + $expr = 'position()'; + if ($last) { - // FIXME: I'm not sure if this is right - $a = -$a; - $b = -$b; + $expr = 'last() - '.$expr; + $b--; } - if ($b > 0) { - $bNeg = -$b; - } else { - $bNeg = sprintf('+%s', -$b); + if (0 !== $b) { + $expr .= ' - '.$b; + } + + $conditions = array(sprintf('%s %s 0', $expr, $sign)); + + if (1 !== $a && -1 !== $a) { + $conditions[] = sprintf('(%s) mod %d = 0', $expr, $a); } - if ($a != 1) { - $expr = array(sprintf('(position() %s) mod %s = 0', $bNeg, $a)); - } else { - $expr = array(); - } - - if ($b >= 0) { - $expr[] = sprintf('position() >= %s', $b); - } elseif ($b < 0 && $last) { - $expr[] = sprintf('position() < (last() %s)', $b); - } - $expr = implode($expr, ' and '); - - if ($expr) { - $xpath->addCondition($expr); - } + $xpath->addCondition(implode(' and ', $conditions)); return $xpath; + /* FIXME: handle an+b, odd, even an+b means every-a, plus b, e.g., 2n+1 means odd 0n+b means b diff --git a/src/Symfony/Component/CssSelector/Tests/CssSelectorTest.php b/src/Symfony/Component/CssSelector/Tests/CssSelectorTest.php index 0d9ca851aa..12f93fc035 100644 --- a/src/Symfony/Component/CssSelector/Tests/CssSelectorTest.php +++ b/src/Symfony/Component/CssSelector/Tests/CssSelectorTest.php @@ -54,7 +54,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[contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"), diff --git a/src/Symfony/Component/CssSelector/Tests/Node/FunctionNodeTest.php b/src/Symfony/Component/CssSelector/Tests/Node/FunctionNodeTest.php index 9654402ae0..23442f1dfa 100644 --- a/src/Symfony/Component/CssSelector/Tests/Node/FunctionNodeTest.php +++ b/src/Symfony/Component/CssSelector/Tests/Node/FunctionNodeTest.php @@ -36,12 +36,12 @@ class FunctionNodeTest extends \PHPUnit_Framework_TestCase // h1:nth-child(odd) $element2 = new ElementNode('*', new Token('Symbol', 'odd', -1)); $function = new FunctionNode($element, ':', 'nth-child', $element2); - $this->assertEquals("*/*[name() = 'h1' and ((position() -1) mod 2 = 0 and position() >= 1)]", (string) $function->toXpath(), '->toXpath() returns the xpath representation of the node'); + $this->assertEquals("*/*[name() = 'h1' and (position() - 1 >= 0 and (position() - 1) mod 2 = 0)]", (string) $function->toXpath(), '->toXpath() returns the xpath representation of the node'); // h1:nth-child(even) $element2 = new ElementNode('*', new Token('Symbol', 'even', -1)); $function = new FunctionNode($element, ':', 'nth-child', $element2); - $this->assertEquals("*/*[name() = 'h1' and ((position() +0) mod 2 = 0 and position() >= 0)]", (string) $function->toXpath(), '->toXpath() returns the xpath representation of the node'); + $this->assertEquals("*/*[name() = 'h1' and (position() >= 0 and (position()) mod 2 = 0)]", (string) $function->toXpath(), '->toXpath() returns the xpath representation of the node'); // h1:nth-child(n) $element2 = new ElementNode('*', new Token('Symbol', 'n', -1)); @@ -51,12 +51,12 @@ class FunctionNodeTest extends \PHPUnit_Framework_TestCase // h1:nth-child(3n+1) $element2 = new ElementNode('*', new Token('Symbol', '3n+1', -1)); $function = new FunctionNode($element, ':', 'nth-child', $element2); - $this->assertEquals("*/*[name() = 'h1' and ((position() -1) mod 3 = 0 and position() >= 1)]", (string) $function->toXpath(), '->toXpath() returns the xpath representation of the node'); + $this->assertEquals("*/*[name() = 'h1' and (position() - 1 >= 0 and (position() - 1) mod 3 = 0)]", (string) $function->toXpath(), '->toXpath() returns the xpath representation of the node'); // h1:nth-child(n+1) $element2 = new ElementNode('*', new Token('Symbol', 'n+1', -1)); $function = new FunctionNode($element, ':', 'nth-child', $element2); - $this->assertEquals("*/*[name() = 'h1' and (position() >= 1)]", (string) $function->toXpath(), '->toXpath() returns the xpath representation of the node'); + $this->assertEquals("*/*[name() = 'h1' and (position() - 1 >= 0)]", (string) $function->toXpath(), '->toXpath() returns the xpath representation of the node'); // h1:nth-child(1) $element2 = new ElementNode('*', new Token('Symbol', '2', -1)); @@ -66,16 +66,16 @@ class FunctionNodeTest extends \PHPUnit_Framework_TestCase // h1:nth-child(2n) $element2 = new ElementNode('*', new Token('Symbol', '2n', -1)); $function = new FunctionNode($element, ':', 'nth-child', $element2); - $this->assertEquals("*/*[name() = 'h1' and ((position() +0) mod 2 = 0 and position() >= 0)]", (string) $function->toXpath(), '->toXpath() returns the xpath representation of the node'); + $this->assertEquals("*/*[name() = 'h1' and (position() >= 0 and (position()) mod 2 = 0)]", (string) $function->toXpath(), '->toXpath() returns the xpath representation of the node'); // h1:nth-child(-n) $element2 = new ElementNode('*', new Token('Symbol', '-n', -1)); $function = new FunctionNode($element, ':', 'nth-child', $element2); - $this->assertEquals("*/*[name() = 'h1' and ((position() +0) mod -1 = 0 and position() >= 0)]", (string) $function->toXpath(), '->toXpath() returns the xpath representation of the node'); + $this->assertEquals("*/*[name() = 'h1' and (false())]", (string) $function->toXpath(), '->toXpath() returns the xpath representation of the node'); // h1:nth-last-child(2) $function = new FunctionNode($element, ':', 'nth-last-child', 2); - $this->assertEquals("*/*[name() = 'h1' and (position() = last() - 2)]", (string) $function->toXpath(), '->toXpath() returns the xpath representation of the node'); + $this->assertEquals("*/*[name() = 'h1' and (position() = last() - 1)]", (string) $function->toXpath(), '->toXpath() returns the xpath representation of the node'); // h1:nth-of-type(2) $function = new FunctionNode($element, ':', 'nth-of-type', 2); @@ -83,7 +83,7 @@ class FunctionNodeTest extends \PHPUnit_Framework_TestCase // h1:nth-last-of-type(2) $function = new FunctionNode($element, ':', 'nth-last-of-type', 2); - $this->assertEquals("*/h1[position() = last() - 2]", (string) $function->toXpath(), '->toXpath() returns the xpath representation of the node'); + $this->assertEquals("*/h1[position() = last() - 1]", (string) $function->toXpath(), '->toXpath() returns the xpath representation of the node'); /* // h1:not(p)