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

Skip to content

Commit c11c588

Browse files
committed
bug #10207 [DomCrawler] Fixed filterXPath() chaining (robbertkl)
This PR was merged into the 2.3 branch. Discussion ---------- [DomCrawler] Fixed filterXPath() chaining | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | BC breaks? | debatable (see below) | Deprecations? | no | Tests pass? | yes | Fixed tickets | #10206 | License | MIT | Doc PR | As @stof mentions in #10206, each node in the Crawler can belong to a different \DOMDocument. Therefore, I've made each node do its own XPath, relative to itself, and add all the results to a new Crawler. This way, all resulting nodes are still part of their original \DOMDocument and thus can reach all of their parent nodes. No current tests break on this change. I've added a new test for this case, by checking if the number of parents is correct after obtaining a node through chaining of `filterXPath()`. Now for BC: I can think of a number of cases where this change would give a different result. However, it's debatable/unclear if: - the old behavior was a bug in the first place (which would validate this change), or - the old behavior was intended (which would make this a BC breaking change) As an example, consider the following HTML: ```html <div name="a"><div name="b"><div name="c"></div></div></div> ``` What would happen if we run this: ```php echo $crawler->filterXPath('//div')->filterXPath('div')->filterXPath('div')->attr('name'); ``` Aside from breaking reachability of the parent nodes by chaining, with the original code it would echo 'a'. With this patch it would echo 'c', which, to me, makes more sense. Commits ------- 43a7716 [DomCrawler] Fixed filterXPath() chaining
2 parents e453c45 + 43a7716 commit c11c588

File tree

4 files changed

+20
-34
lines changed

4 files changed

+20
-34
lines changed

src/Symfony/Component/DomCrawler/Crawler.php

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ public function parents()
441441
$nodes = array();
442442

443443
while ($node = $node->parentNode) {
444-
if (1 === $node->nodeType && '_root' !== $node->nodeName) {
444+
if (1 === $node->nodeType) {
445445
$nodes[] = $node;
446446
}
447447
}
@@ -584,15 +584,13 @@ public function extract($attributes)
584584
*/
585585
public function filterXPath($xpath)
586586
{
587-
$document = new \DOMDocument('1.0', 'UTF-8');
588-
$root = $document->appendChild($document->createElement('_root'));
587+
$crawler = new static(null, $this->uri);
589588
foreach ($this as $node) {
590-
$root->appendChild($document->importNode($node, true));
589+
$domxpath = new \DOMXPath($node->ownerDocument);
590+
$crawler->add($domxpath->query($xpath, $node));
591591
}
592592

593-
$domxpath = new \DOMXPath($document);
594-
595-
return new static($domxpath->query($xpath), $this->uri);
593+
return $crawler;
596594
}
597595

598596
/**

src/Symfony/Component/DomCrawler/Field/FormField.php

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,7 @@ public function __construct(\DOMNode $node)
5252
{
5353
$this->node = $node;
5454
$this->name = $node->getAttribute('name');
55-
56-
$this->document = new \DOMDocument('1.0', 'UTF-8');
57-
$this->node = $this->document->importNode($this->node, true);
58-
59-
$root = $this->document->appendChild($this->document->createElement('_root'));
60-
$root->appendChild($this->node);
61-
$this->xpath = new \DOMXPath($this->document);
55+
$this->xpath = new \DOMXPath($node->ownerDocument);
6256

6357
$this->initialize();
6458
}

src/Symfony/Component/DomCrawler/Form.php

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -378,9 +378,7 @@ private function initialize()
378378
{
379379
$this->fields = new FormFieldRegistry();
380380

381-
$document = new \DOMDocument('1.0', 'UTF-8');
382-
$xpath = new \DOMXPath($document);
383-
$root = $document->appendChild($document->createElement('_root'));
381+
$xpath = new \DOMXPath($this->node->ownerDocument);
384382

385383
// add submitted button if it has a valid name
386384
if ('form' !== $this->button->nodeName && $this->button->hasAttribute('name') && $this->button->getAttribute('name')) {
@@ -390,39 +388,33 @@ private function initialize()
390388

391389
// temporarily change the name of the input node for the x coordinate
392390
$this->button->setAttribute('name', $name.'.x');
393-
$this->set(new Field\InputFormField($document->importNode($this->button, true)));
391+
$this->set(new Field\InputFormField($this->button));
394392

395393
// temporarily change the name of the input node for the y coordinate
396394
$this->button->setAttribute('name', $name.'.y');
397-
$this->set(new Field\InputFormField($document->importNode($this->button, true)));
395+
$this->set(new Field\InputFormField($this->button));
398396

399397
// restore the original name of the input node
400398
$this->button->setAttribute('name', $name);
401-
}
402-
else {
403-
$this->set(new Field\InputFormField($document->importNode($this->button, true)));
399+
} else {
400+
$this->set(new Field\InputFormField($this->button));
404401
}
405402
}
406403

407404
// find form elements corresponding to the current form
408405
if ($this->node->hasAttribute('id')) {
409-
// traverse through the whole document
410-
$node = $document->importNode($this->node->ownerDocument->documentElement, true);
411-
$root->appendChild($node);
412-
413406
// corresponding elements are either descendants or have a matching HTML5 form attribute
414407
$formId = Crawler::xpathLiteral($this->node->getAttribute('id'));
415-
$fieldNodes = $xpath->query(sprintf('descendant::input[@form=%s] | descendant::button[@form=%s] | descendant::textarea[@form=%s] | descendant::select[@form=%s] | //form[@id=%s]//input[not(@form)] | //form[@id=%s]//button[not(@form)] | //form[@id=%s]//textarea[not(@form)] | //form[@id=%s]//select[not(@form)]', $formId, $formId, $formId, $formId, $formId, $formId, $formId, $formId), $root);
408+
409+
// do the xpath query without $this->node as the context node (i.e. traverse through the whole document)
410+
$fieldNodes = $xpath->query(sprintf('descendant::input[@form=%s] | descendant::button[@form=%s] | descendant::textarea[@form=%s] | descendant::select[@form=%s] | //form[@id=%s]//input[not(@form)] | //form[@id=%s]//button[not(@form)] | //form[@id=%s]//textarea[not(@form)] | //form[@id=%s]//select[not(@form)]', $formId, $formId, $formId, $formId, $formId, $formId, $formId, $formId));
416411
foreach ($fieldNodes as $node) {
417412
$this->addField($node);
418413
}
419414
} else {
420-
// parent form has no id, add descendant elements only
421-
$node = $document->importNode($this->node, true);
422-
$root->appendChild($node);
423-
424-
// descendant elements with form attribute are not part of this form
425-
$fieldNodes = $xpath->query('descendant::input[not(@form)] | descendant::button[not(@form)] | descendant::textarea[not(@form)] | descendant::select[not(@form)]', $root);
415+
// do the xpath query with $this->node as the context node, to only find descendant elements
416+
// however, descendant elements with form attribute are not part of this form
417+
$fieldNodes = $xpath->query('descendant::input[not(@form)] | descendant::button[not(@form)] | descendant::textarea[not(@form)] | descendant::select[not(@form)]', $this->node);
426418
foreach ($fieldNodes as $node) {
427419
$this->addField($node);
428420
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,8 +378,10 @@ public function testFilterXPath()
378378
$this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->filterXPath() returns a new instance of a crawler');
379379

380380
$crawler = $this->createTestCrawler()->filterXPath('//ul');
381-
382381
$this->assertCount(6, $crawler->filterXPath('//li'), '->filterXPath() filters the node list with the XPath expression');
382+
383+
$crawler = $this->createTestCrawler();
384+
$this->assertCount(3, $crawler->filterXPath('//body')->filterXPath('//button')->parents(), '->filterXpath() preserves parents when chained');
383385
}
384386

385387
/**

0 commit comments

Comments
 (0)