Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 00aedfc

Browse files
committed
bug #11624 [DomCrawler] fix the axes handling in a bc way (xabbuh)
This PR was merged into the 2.3 branch. Discussion ---------- [DomCrawler] fix the axes handling in a bc way | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #11503 | License | MIT | Doc PR | The previous fix in #11548 for handling XPath axes was not backward compatible. In previous Symfony versions the Crawler handled nodes by holding a "fake root node". This must be taken into account when evaluating (relativizing) XPath expressions. Commits ------- d26040f [DomCrawler] fix the axes handling in a bc way
2 parents afab3ee + d26040f commit 00aedfc

File tree

2 files changed

+63
-21
lines changed

2 files changed

+63
-21
lines changed

src/Symfony/Component/DomCrawler/Crawler.php

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,9 @@ public function addDocument(\DOMDocument $dom)
245245
public function addNodeList(\DOMNodeList $nodes)
246246
{
247247
foreach ($nodes as $node) {
248-
$this->addNode($node);
248+
if ($node instanceof \DOMNode) {
249+
$this->addNode($node);
250+
}
249251
}
250252
}
251253

@@ -834,28 +836,35 @@ private function relativize($xpath)
834836
// BC for Symfony 2.4 and lower were elements were adding in a fake _root parent
835837
if (0 === strpos($expression, '/_root/')) {
836838
$expression = './'.substr($expression, 7);
839+
} elseif (0 === strpos($expression, 'self::*/')) {
840+
$expression = './'.substr($expression, 8);
837841
}
838842

839843
// add prefix before absolute element selector
840844
if (empty($expression)) {
841845
$expression = $nonMatchingExpression;
842846
} elseif (0 === strpos($expression, '//')) {
843-
$expression = 'descendant-or-self::' . substr($expression, 2);
847+
$expression = 'descendant-or-self::'.substr($expression, 2);
844848
} elseif (0 === strpos($expression, './/')) {
845-
$expression = 'descendant-or-self::' . substr($expression, 3);
849+
$expression = 'descendant-or-self::'.substr($expression, 3);
846850
} elseif (0 === strpos($expression, './')) {
847-
$expression = 'self::' . substr($expression, 2);
848-
} elseif ('/' === $expression[0]) {
851+
$expression = 'self::'.substr($expression, 2);
852+
} elseif (0 === strpos($expression, 'child::')) {
853+
$expression = 'self::'.substr($expression, 7);
854+
} elseif ('/' === $expression[0] || 0 === strpos($expression, 'self::')) {
849855
// the only direct child in Symfony 2.4 and lower is _root, which is already handled previously
850856
// so let's drop the expression entirely
851857
$expression = $nonMatchingExpression;
852858
} elseif ('.' === $expression[0]) {
853859
// '.' is the fake root element in Symfony 2.4 and lower, which is excluded from results
854860
$expression = $nonMatchingExpression;
855861
} elseif (0 === strpos($expression, 'descendant::')) {
856-
$expression = 'descendant-or-self::' . substr($expression, strlen('descendant::'));
857-
} elseif (!preg_match('/^(ancestor|ancestor-or-self|attribute|child|descendant-or-self|following|following-sibling|parent|preceding|preceding-sibling|self)::/', $expression)) {
858-
$expression = 'self::' .$expression;
862+
$expression = 'descendant-or-self::'.substr($expression, strlen('descendant::'));
863+
} elseif (preg_match('/^(ancestor|ancestor-or-self|attribute|following|following-sibling|namespace|parent|preceding|preceding-sibling)::/', $expression)) {
864+
// the fake root has no parent, preceding or following nodes and also no attributes (even no namespace attributes)
865+
$expression = $nonMatchingExpression;
866+
} elseif (0 !== strpos($expression, 'descendant-or-self::')) {
867+
$expression = 'self::'.$expression;
859868
}
860869
$expressions[] = $parenthesis.$expression;
861870
}

src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -388,8 +388,8 @@ public function testFilterXpathComplexQueries()
388388
$this->assertCount(1, $crawler->filterXPath('//body'));
389389
$this->assertCount(1, $crawler->filterXPath('descendant-or-self::body'));
390390
$this->assertCount(1, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('./div'), 'A child selection finds only the current div');
391-
$this->assertCount(2, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('descendant::div'), 'A descendant selector matches the current div and its child');
392-
$this->assertCount(2, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('//div'), 'A descendant selector matches the current div and its child');
391+
$this->assertCount(3, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('descendant::div'), 'A descendant selector matches the current div and its child');
392+
$this->assertCount(3, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('//div'), 'A descendant selector matches the current div and its child');
393393
$this->assertCount(5, $crawler->filterXPath('(//a | //div)//img'));
394394
$this->assertCount(7, $crawler->filterXPath('((//a | //div)//img | //ul)'));
395395
$this->assertCount(7, $crawler->filterXPath('( ( //a | //div )//img | //ul )'));
@@ -411,72 +411,104 @@ public function testFilterXPath()
411411
$this->assertCount(3, $crawler->filterXPath('//body')->filterXPath('//button')->parents(), '->filterXpath() preserves parents when chained');
412412
}
413413

414+
public function testFilterXPathWithFakeRoot()
415+
{
416+
$crawler = $this->createTestCrawler();
417+
$this->assertCount(0, $crawler->filterXPath('.'), '->filterXPath() returns an empty result if the XPath references the fake root node');
418+
$this->assertCount(0, $crawler->filterXPath('/_root'), '->filterXPath() returns an empty result if the XPath references the fake root node');
419+
$this->assertCount(0, $crawler->filterXPath('self::*'), '->filterXPath() returns an empty result if the XPath references the fake root node');
420+
$this->assertCount(0, $crawler->filterXPath('self::_root'), '->filterXPath() returns an empty result if the XPath references the fake root node');
421+
}
422+
414423
public function testFilterXPathWithAncestorAxis()
415424
{
416425
$crawler = $this->createTestCrawler()->filterXPath('//form');
417426

418-
$this->assertCount(2, $crawler->filterXPath('ancestor::*'));
427+
$this->assertCount(0, $crawler->filterXPath('ancestor::*'), 'The fake root node has no ancestor nodes');
419428
}
420429

421430
public function testFilterXPathWithAncestorOrSelfAxis()
422431
{
423432
$crawler = $this->createTestCrawler()->filterXPath('//form');
424433

425-
$this->assertCount(3, $crawler->filterXPath('ancestor-or-self::*'));
434+
$this->assertCount(0, $crawler->filterXPath('ancestor-or-self::*'), 'The fake root node has no ancestor nodes');
426435
}
427436

428437
public function testFilterXPathWithAttributeAxis()
429438
{
430439
$crawler = $this->createTestCrawler()->filterXPath('//form');
431440

432-
$this->assertCount(2, $crawler->filterXPath('attribute::*'));
441+
$this->assertCount(0, $crawler->filterXPath('attribute::*'), 'The fake root node has no attribute nodes');
442+
}
443+
444+
public function testFilterXPathWithAttributeAxisAfterElementAxis()
445+
{
446+
$this->assertCount(3, $this->createTestCrawler()->filterXPath('//form/button/attribute::*'), '->filterXPath() handles attribute axes properly when they are preceded by an element filtering axis');
433447
}
434448

435449
public function testFilterXPathWithChildAxis()
436450
{
437-
$crawler = $this->createTestCrawler()->filterXPath('//body');
451+
$crawler = $this->createTestCrawler()->filterXPath('//div[@id="parent"]');
438452

439-
$this->assertCount(2, $crawler->filterXPath('child::input'));
453+
$this->assertCount(1, $crawler->filterXPath('child::div'), 'A child selection finds only the current div');
440454
}
441455

442456
public function testFilterXPathWithFollowingAxis()
443457
{
444458
$crawler = $this->createTestCrawler()->filterXPath('//a');
445459

446-
$this->assertCount(3, $crawler->filterXPath('following::div'));
460+
$this->assertCount(0, $crawler->filterXPath('following::div'), 'The fake root node has no following nodes');
447461
}
448462

449463
public function testFilterXPathWithFollowingSiblingAxis()
450464
{
451465
$crawler = $this->createTestCrawler()->filterXPath('//a');
452466

453-
$this->assertCount(2, $crawler->filterXPath('following-sibling::div'));
467+
$this->assertCount(0, $crawler->filterXPath('following-sibling::div'), 'The fake root node has no following nodes');
468+
}
469+
470+
public function testFilterXPathWithNamespaceAxis()
471+
{
472+
$crawler = $this->createTestCrawler()->filterXPath('//button');
473+
474+
$this->assertCount(0, $crawler->filterXPath('namespace::*'), 'The fake root node has no namespace nodes');
475+
}
476+
477+
public function testFilterXPathWithNamespaceAxisAfterElementAxis()
478+
{
479+
$crawler = $this->createTestCrawler()->filterXPath('//div[@id="parent"]/namespace::*');
480+
481+
$this->assertCount(0, $crawler->filterXPath('namespace::*'), 'Namespace axes cannot be requested');
454482
}
455483

456484
public function testFilterXPathWithParentAxis()
457485
{
458486
$crawler = $this->createTestCrawler()->filterXPath('//button');
459487

460-
$this->assertEquals('foo', $crawler->filterXPath('parent::*')->attr('action'));
488+
$this->assertCount(0, $crawler->filterXPath('parent::*'), 'The fake root node has no parent nodes');
461489
}
462490

463491
public function testFilterXPathWithPrecedingAxis()
464492
{
465493
$crawler = $this->createTestCrawler()->filterXPath('//form');
466494

467-
$this->assertCount(13, $crawler->filterXPath('preceding::*'));
495+
$this->assertCount(0, $crawler->filterXPath('preceding::*'), 'The fake root node has no preceding nodes');
468496
}
469497

470498
public function testFilterXPathWithPrecedingSiblingAxis()
471499
{
472500
$crawler = $this->createTestCrawler()->filterXPath('//form');
473501

474-
$this->assertCount(9, $crawler->filterXPath('preceding-sibling::*'));
502+
$this->assertCount(0, $crawler->filterXPath('preceding-sibling::*'), 'The fake root node has no preceding nodes');
475503
}
476504

477505
public function testFilterXPathWithSelfAxes()
478506
{
479-
$this->assertCount(1, $this->createTestCrawler()->filterXPath('self::*'));
507+
$crawler = $this->createTestCrawler()->filterXPath('//a');
508+
509+
$this->assertCount(0, $crawler->filterXPath('self::a'), 'The fake root node has no "real" element name');
510+
$this->assertCount(0, $crawler->filterXPath('self::a/img'), 'The fake root node has no "real" element name');
511+
$this->assertCount(9, $crawler->filterXPath('self::*/a'));
480512
}
481513

482514
/**
@@ -844,6 +876,7 @@ public function createTestCrawler($uri = null)
844876
</ul>
845877
<div id="parent">
846878
<div id="child"></div>
879+
<div id="child2" xmlns:foo="http://example.com"></div>
847880
</div>
848881
<div id="sibling"><img /></div>
849882
</body>

0 commit comments

Comments
 (0)