diff --git a/src/Symfony/Component/DomCrawler/Crawler.php b/src/Symfony/Component/DomCrawler/Crawler.php index 5dd4dba3cd..50f00c1ff7 100644 --- a/src/Symfony/Component/DomCrawler/Crawler.php +++ b/src/Symfony/Component/DomCrawler/Crawler.php @@ -245,7 +245,9 @@ class Crawler extends \SplObjectStorage public function addNodeList(\DOMNodeList $nodes) { foreach ($nodes as $node) { - $this->addNode($node); + if ($node instanceof \DOMNode) { + $this->addNode($node); + } } } @@ -834,18 +836,22 @@ class Crawler extends \SplObjectStorage // BC for Symfony 2.4 and lower were elements were adding in a fake _root parent if (0 === strpos($expression, '/_root/')) { $expression = './'.substr($expression, 7); + } elseif (0 === strpos($expression, 'self::*/')) { + $expression = './'.substr($expression, 8); } // add prefix before absolute element selector if (empty($expression)) { $expression = $nonMatchingExpression; } elseif (0 === strpos($expression, '//')) { - $expression = 'descendant-or-self::' . substr($expression, 2); + $expression = 'descendant-or-self::'.substr($expression, 2); } elseif (0 === strpos($expression, './/')) { - $expression = 'descendant-or-self::' . substr($expression, 3); + $expression = 'descendant-or-self::'.substr($expression, 3); } elseif (0 === strpos($expression, './')) { - $expression = 'self::' . substr($expression, 2); - } elseif ('/' === $expression[0]) { + $expression = 'self::'.substr($expression, 2); + } elseif (0 === strpos($expression, 'child::')) { + $expression = 'self::'.substr($expression, 7); + } elseif ('/' === $expression[0] || 0 === strpos($expression, 'self::')) { // the only direct child in Symfony 2.4 and lower is _root, which is already handled previously // so let's drop the expression entirely $expression = $nonMatchingExpression; @@ -853,9 +859,12 @@ class Crawler extends \SplObjectStorage // '.' is the fake root element in Symfony 2.4 and lower, which is excluded from results $expression = $nonMatchingExpression; } elseif (0 === strpos($expression, 'descendant::')) { - $expression = 'descendant-or-self::' . substr($expression, strlen('descendant::')); - } elseif (!preg_match('/^(ancestor|ancestor-or-self|attribute|child|descendant-or-self|following|following-sibling|parent|preceding|preceding-sibling|self)::/', $expression)) { - $expression = 'self::' .$expression; + $expression = 'descendant-or-self::'.substr($expression, strlen('descendant::')); + } elseif (preg_match('/^(ancestor|ancestor-or-self|attribute|following|following-sibling|namespace|parent|preceding|preceding-sibling)::/', $expression)) { + // the fake root has no parent, preceding or following nodes and also no attributes (even no namespace attributes) + $expression = $nonMatchingExpression; + } elseif (0 !== strpos($expression, 'descendant-or-self::')) { + $expression = 'self::'.$expression; } $expressions[] = $parenthesis.$expression; } diff --git a/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php b/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php index 4c9a4bcecf..4d4c5ac2a1 100644 --- a/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php +++ b/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php @@ -388,8 +388,8 @@ EOF $this->assertCount(1, $crawler->filterXPath('//body')); $this->assertCount(1, $crawler->filterXPath('descendant-or-self::body')); $this->assertCount(1, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('./div'), 'A child selection finds only the current div'); - $this->assertCount(2, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('descendant::div'), 'A descendant selector matches the current div and its child'); - $this->assertCount(2, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('//div'), 'A descendant selector matches the current div and its child'); + $this->assertCount(3, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('descendant::div'), 'A descendant selector matches the current div and its child'); + $this->assertCount(3, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('//div'), 'A descendant selector matches the current div and its child'); $this->assertCount(5, $crawler->filterXPath('(//a | //div)//img')); $this->assertCount(7, $crawler->filterXPath('((//a | //div)//img | //ul)')); $this->assertCount(7, $crawler->filterXPath('( ( //a | //div )//img | //ul )')); @@ -411,72 +411,104 @@ EOF $this->assertCount(3, $crawler->filterXPath('//body')->filterXPath('//button')->parents(), '->filterXpath() preserves parents when chained'); } + public function testFilterXPathWithFakeRoot() + { + $crawler = $this->createTestCrawler(); + $this->assertCount(0, $crawler->filterXPath('.'), '->filterXPath() returns an empty result if the XPath references the fake root node'); + $this->assertCount(0, $crawler->filterXPath('/_root'), '->filterXPath() returns an empty result if the XPath references the fake root node'); + $this->assertCount(0, $crawler->filterXPath('self::*'), '->filterXPath() returns an empty result if the XPath references the fake root node'); + $this->assertCount(0, $crawler->filterXPath('self::_root'), '->filterXPath() returns an empty result if the XPath references the fake root node'); + } + public function testFilterXPathWithAncestorAxis() { $crawler = $this->createTestCrawler()->filterXPath('//form'); - $this->assertCount(2, $crawler->filterXPath('ancestor::*')); + $this->assertCount(0, $crawler->filterXPath('ancestor::*'), 'The fake root node has no ancestor nodes'); } public function testFilterXPathWithAncestorOrSelfAxis() { $crawler = $this->createTestCrawler()->filterXPath('//form'); - $this->assertCount(3, $crawler->filterXPath('ancestor-or-self::*')); + $this->assertCount(0, $crawler->filterXPath('ancestor-or-self::*'), 'The fake root node has no ancestor nodes'); } public function testFilterXPathWithAttributeAxis() { $crawler = $this->createTestCrawler()->filterXPath('//form'); - $this->assertCount(2, $crawler->filterXPath('attribute::*')); + $this->assertCount(0, $crawler->filterXPath('attribute::*'), 'The fake root node has no attribute nodes'); + } + + public function testFilterXPathWithAttributeAxisAfterElementAxis() + { + $this->assertCount(3, $this->createTestCrawler()->filterXPath('//form/button/attribute::*'), '->filterXPath() handles attribute axes properly when they are preceded by an element filtering axis'); } public function testFilterXPathWithChildAxis() { - $crawler = $this->createTestCrawler()->filterXPath('//body'); + $crawler = $this->createTestCrawler()->filterXPath('//div[@id="parent"]'); - $this->assertCount(2, $crawler->filterXPath('child::input')); + $this->assertCount(1, $crawler->filterXPath('child::div'), 'A child selection finds only the current div'); } public function testFilterXPathWithFollowingAxis() { $crawler = $this->createTestCrawler()->filterXPath('//a'); - $this->assertCount(3, $crawler->filterXPath('following::div')); + $this->assertCount(0, $crawler->filterXPath('following::div'), 'The fake root node has no following nodes'); } public function testFilterXPathWithFollowingSiblingAxis() { $crawler = $this->createTestCrawler()->filterXPath('//a'); - $this->assertCount(2, $crawler->filterXPath('following-sibling::div')); + $this->assertCount(0, $crawler->filterXPath('following-sibling::div'), 'The fake root node has no following nodes'); + } + + public function testFilterXPathWithNamespaceAxis() + { + $crawler = $this->createTestCrawler()->filterXPath('//button'); + + $this->assertCount(0, $crawler->filterXPath('namespace::*'), 'The fake root node has no namespace nodes'); + } + + public function testFilterXPathWithNamespaceAxisAfterElementAxis() + { + $crawler = $this->createTestCrawler()->filterXPath('//div[@id="parent"]/namespace::*'); + + $this->assertCount(0, $crawler->filterXPath('namespace::*'), 'Namespace axes cannot be requested'); } public function testFilterXPathWithParentAxis() { $crawler = $this->createTestCrawler()->filterXPath('//button'); - $this->assertEquals('foo', $crawler->filterXPath('parent::*')->attr('action')); + $this->assertCount(0, $crawler->filterXPath('parent::*'), 'The fake root node has no parent nodes'); } public function testFilterXPathWithPrecedingAxis() { $crawler = $this->createTestCrawler()->filterXPath('//form'); - $this->assertCount(13, $crawler->filterXPath('preceding::*')); + $this->assertCount(0, $crawler->filterXPath('preceding::*'), 'The fake root node has no preceding nodes'); } public function testFilterXPathWithPrecedingSiblingAxis() { $crawler = $this->createTestCrawler()->filterXPath('//form'); - $this->assertCount(9, $crawler->filterXPath('preceding-sibling::*')); + $this->assertCount(0, $crawler->filterXPath('preceding-sibling::*'), 'The fake root node has no preceding nodes'); } public function testFilterXPathWithSelfAxes() { - $this->assertCount(1, $this->createTestCrawler()->filterXPath('self::*')); + $crawler = $this->createTestCrawler()->filterXPath('//a'); + + $this->assertCount(0, $crawler->filterXPath('self::a'), 'The fake root node has no "real" element name'); + $this->assertCount(0, $crawler->filterXPath('self::a/img'), 'The fake root node has no "real" element name'); + $this->assertCount(9, $crawler->filterXPath('self::*/a')); } /** @@ -844,6 +876,7 @@ HTML;
+