[DomCrawler] Added Crawler::matches(), ::closest(), ::outerHtml()
This commit is contained in:
parent
1981f06c88
commit
9535f9e8d6
@ -5,6 +5,9 @@ CHANGELOG
|
||||
-----
|
||||
|
||||
* Added `Form::getName()` method.
|
||||
* Added `Crawler::matches()` method.
|
||||
* Added `Crawler::closest()` method.
|
||||
* Added `Crawler::outerHtml()` method.
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
@ -427,6 +427,45 @@ class Crawler implements \Countable, \IteratorAggregate
|
||||
return $this->createSubCrawler($this->sibling($this->getNode(0)->parentNode->firstChild));
|
||||
}
|
||||
|
||||
public function matches(string $selector): bool
|
||||
{
|
||||
if (!$this->nodes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$converter = $this->createCssSelectorConverter();
|
||||
$xpath = $converter->toXPath($selector, 'self::');
|
||||
|
||||
return 0 !== $this->filterRelativeXPath($xpath)->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return first parents (heading toward the document root) of the Element that matches the provided selector.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
|
||||
*
|
||||
* @throws \InvalidArgumentException When current node is empty
|
||||
*/
|
||||
public function closest(string $selector): ?self
|
||||
{
|
||||
if (!$this->nodes) {
|
||||
throw new \InvalidArgumentException('The current node list is empty.');
|
||||
}
|
||||
|
||||
$domNode = $this->getNode(0);
|
||||
|
||||
while (XML_ELEMENT_NODE === $domNode->nodeType) {
|
||||
$node = $this->createSubCrawler($domNode);
|
||||
if ($node->matches($selector)) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
$domNode = $node->getNode(0)->parentNode;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next siblings nodes of the current selection.
|
||||
*
|
||||
@ -609,6 +648,22 @@ class Crawler implements \Countable, \IteratorAggregate
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function outerHtml(): string
|
||||
{
|
||||
if (!\count($this)) {
|
||||
throw new \InvalidArgumentException('The current node list is empty.');
|
||||
}
|
||||
|
||||
$node = $this->getNode(0);
|
||||
$owner = $node->ownerDocument;
|
||||
|
||||
if (null !== $this->html5Parser && '<!DOCTYPE html>' === $owner->saveXML($owner->childNodes[0])) {
|
||||
$owner = $this->html5Parser;
|
||||
}
|
||||
|
||||
return $owner->saveHTML($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates an XPath expression.
|
||||
*
|
||||
|
@ -880,6 +880,105 @@ HTML;
|
||||
}
|
||||
}
|
||||
|
||||
public function provideMatchTests()
|
||||
{
|
||||
yield ['#foo', true, '#foo'];
|
||||
yield ['#foo', true, '.foo'];
|
||||
yield ['#foo', true, '.other'];
|
||||
yield ['#foo', false, '.bar'];
|
||||
|
||||
yield ['#bar', true, '#bar'];
|
||||
yield ['#bar', true, '.bar'];
|
||||
yield ['#bar', true, '.other'];
|
||||
yield ['#bar', false, '.foo'];
|
||||
}
|
||||
|
||||
/** @dataProvider provideMatchTests */
|
||||
public function testMatch(string $mainNodeSelector, bool $expected, string $selector)
|
||||
{
|
||||
$html = <<<'HTML'
|
||||
<html lang="en">
|
||||
<body>
|
||||
<div id="foo" class="foo other">
|
||||
<div>
|
||||
<div id="bar" class="bar other"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
HTML;
|
||||
|
||||
$crawler = $this->createCrawler($this->getDoctype().$html);
|
||||
$node = $crawler->filter($mainNodeSelector);
|
||||
$this->assertSame($expected, $node->matches($selector));
|
||||
}
|
||||
|
||||
public function testClosest()
|
||||
{
|
||||
$html = <<<'HTML'
|
||||
<html lang="en">
|
||||
<body>
|
||||
<div class="lorem2 ok">
|
||||
<div>
|
||||
<div class="lorem3 ko"></div>
|
||||
</div>
|
||||
<div class="lorem1 ok">
|
||||
<div id="foo" class="newFoo ok">
|
||||
<div class="lorem1 ko"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lorem2 ko">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
HTML;
|
||||
|
||||
$crawler = $this->createCrawler($this->getDoctype().$html);
|
||||
$foo = $crawler->filter('#foo');
|
||||
|
||||
$newFoo = $foo->closest('#foo');
|
||||
$this->assertInstanceOf(Crawler::class, $newFoo);
|
||||
$this->assertSame('newFoo ok', $newFoo->attr('class'));
|
||||
|
||||
$lorem1 = $foo->closest('.lorem1');
|
||||
$this->assertInstanceOf(Crawler::class, $lorem1);
|
||||
$this->assertSame('lorem1 ok', $lorem1->attr('class'));
|
||||
|
||||
$lorem2 = $foo->closest('.lorem2');
|
||||
$this->assertInstanceOf(Crawler::class, $lorem2);
|
||||
$this->assertSame('lorem2 ok', $lorem2->attr('class'));
|
||||
|
||||
$lorem3 = $foo->closest('.lorem3');
|
||||
$this->assertNull($lorem3);
|
||||
|
||||
$notFound = $foo->closest('.not-found');
|
||||
$this->assertNull($notFound);
|
||||
}
|
||||
|
||||
public function testOuterHtml()
|
||||
{
|
||||
$html = <<<'HTML'
|
||||
<html lang="en">
|
||||
<body>
|
||||
<div class="foo">
|
||||
<ul>
|
||||
<li>1</li>
|
||||
<li>2</li>
|
||||
<li>3</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
HTML;
|
||||
|
||||
$crawler = $this->createCrawler($this->getDoctype().$html);
|
||||
$bar = $crawler->filter('ul');
|
||||
$output = $bar->outerHtml();
|
||||
$output = str_replace([' ', "\n"], '', $output);
|
||||
$expected = '<ul><li>1</li><li>2</li><li>3</li></ul>';
|
||||
$this->assertSame($expected, $output);
|
||||
}
|
||||
|
||||
public function testNextAll()
|
||||
{
|
||||
$crawler = $this->createTestCrawler()->filterXPath('//li')->eq(1);
|
||||
|
Reference in New Issue
Block a user