Skip to content

Commit

Permalink
feature #28221 [DomCrawler] Add a way to filter direct children (Eine…
Browse files Browse the repository at this point in the history
…nlum)

This PR was squashed before being merged into the 4.2-dev branch (closes #28221).

Discussion
----------

[DomCrawler] Add a way to filter direct children

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #28171
| License       | MIT
| Doc PR        | -

The Dom-Crawler component only has a `filter()` method (to filter the node and all its children) and a `children()` method to return direct children.
**There is currently no way to easily filter (thanks to a selector) the direct children of a node, like jQuery allows so (with a selector passed to the `.children([selector])` method).**

**This PR adds a way to optionally filter direct children thanks to a CSS selector**. Here is an example of the usage:

```php
$html = <<<'HTML'
<html>
    <body>
        <div id="foo">
            <p class="lorem" id="p1"></p>
            <p class="lorem" id="p2"></p>
            <div id="nested">
                <p class="lorem" id="p3"></p>
            </div>
        </div>
    </body>
</html>
HTML;

$crawler = new Crawler($html);
$foo = $crawler->filter('#foo');

$foo->children() // will select `#p1`, `#p2` and `#nested`
$foo->children('p') // will select `#p1` and `p2`
$foo->children('.lorem') // will select `#p1` and `p2`
```
This PR adds only an optional parameter and adds no BC break.

Commits
-------

f634afd [DomCrawler] Add a way to filter direct children
  • Loading branch information
nicolas-grekas committed Aug 24, 2018
2 parents d8e2af3 + f634afd commit f03a54e
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 6 deletions.
32 changes: 26 additions & 6 deletions src/Symfony/Component/DomCrawler/Crawler.php
Expand Up @@ -501,16 +501,28 @@ public function parents()
/**
* Returns the children nodes of the current selection.
*
* @param string|null $selector An optional CSS selector to filter children
*
* @return self
*
* @throws \InvalidArgumentException When current node is empty
* @throws \RuntimeException If the CssSelector Component is not available and $selector is provided
*/
public function children()
public function children(/* string $selector = null */)
{
$selector = 0 < \func_num_args() ? func_get_arg(0) : null;

if (!$this->nodes) {
throw new \InvalidArgumentException('The current node list is empty.');
}

if (null !== $selector) {
$converter = $this->createCssSelectorConverter();
$xpath = $converter->toXPath($selector, 'child::');

return $this->filterRelativeXPath($xpath);
}

$node = $this->getNode(0)->firstChild;

return $this->createSubCrawler($node ? $this->sibling($node) : array());
Expand Down Expand Up @@ -691,11 +703,7 @@ public function filterXPath($xpath)
*/
public function filter($selector)
{
if (!class_exists(CssSelectorConverter::class)) {
throw new \RuntimeException('To filter with a CSS selector, install the CssSelector component ("composer require symfony/css-selector"). Or use filterXpath instead.');
}

$converter = new CssSelectorConverter($this->isHtml);
$converter = $this->createCssSelectorConverter();

// The CssSelector already prefixes the selector with descendant-or-self::
return $this->filterRelativeXPath($converter->toXPath($selector));
Expand Down Expand Up @@ -1148,4 +1156,16 @@ private function createSubCrawler($nodes)

return $crawler;
}

/**
* @throws \RuntimeException If the CssSelector Component is not available
*/
private function createCssSelectorConverter(): CssSelectorConverter
{
if (!class_exists(CssSelectorConverter::class)) {
throw new \RuntimeException('To filter with a CSS selector, install the CssSelector component ("composer require symfony/css-selector"). Or use filterXpath instead.');
}

return new CssSelectorConverter($this->isHtml);
}
}
31 changes: 31 additions & 0 deletions src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php
Expand Up @@ -1004,6 +1004,37 @@ public function testChildren()
}
}

public function testFilteredChildren()
{
$html = <<<'HTML'
<!DOCTYPE html>
<html lang="en">
<body>
<div id="foo">
<div class="lorem">
<p class="lorem"></p>
</div>
<div class="lorem">
<span class="lorem"></span>
</div>
<span class="ipsum"></span>
</div>
</body>
</html>
HTML;

$crawler = new Crawler($html);
$foo = $crawler->filter('#foo');

$this->assertEquals(3, $foo->children()->count());
$this->assertEquals(2, $foo->children('.lorem')->count());
$this->assertEquals(2, $foo->children('div')->count());
$this->assertEquals(2, $foo->children('div.lorem')->count());
$this->assertEquals(1, $foo->children('span')->count());
$this->assertEquals(1, $foo->children('span.ipsum')->count());
$this->assertEquals(1, $foo->children('.ipsum')->count());
}

public function testParents()
{
$crawler = $this->createTestCrawler()->filterXPath('//li[1]');
Expand Down

0 comments on commit f03a54e

Please sign in to comment.