From c426b8de9355dc232babd87e4f93018e36f687a7 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 23 Nov 2024 15:47:30 +0100 Subject: [PATCH] added DomQuery::closest() --- src/Framework/DomQuery.php | 13 +++++++++++++ tests/Framework/DomQuery.fromHtml.84.phpt | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/Framework/DomQuery.php b/src/Framework/DomQuery.php index 4b824e91..8137e190 100644 --- a/src/Framework/DomQuery.php +++ b/src/Framework/DomQuery.php @@ -112,6 +112,19 @@ public function matches(string $selector): bool } + /** + * Returns closest ancestor matching CSS selector. + */ + public function closest(string $selector): ?self + { + if (PHP_VERSION_ID < 80400) { + throw new \LogicException('Requires PHP 8.4 or newer.'); + } + $el = Dom\import_simplexml($this)->closest($selector); + return $el ? simplexml_import_dom($el, self::class) : null; + } + + /** * Converts a CSS selector into an XPath expression. */ diff --git a/tests/Framework/DomQuery.fromHtml.84.phpt b/tests/Framework/DomQuery.fromHtml.84.phpt index 75fa246c..37dbda49 100644 --- a/tests/Framework/DomQuery.fromHtml.84.phpt +++ b/tests/Framework/DomQuery.fromHtml.84.phpt @@ -97,6 +97,24 @@ test('matches() checks if element matches selector', function () { Assert::false($para->matches('.test')); }); +test('closest() finds nearest matching ancestor', function () { + $dom = DomQuery::fromHtml(' +
+
+
+

Test

+
+
+
+ '); + + $p = $dom->find('p')[0]; + Assert::type(DomQuery::class, $p->closest('div')); + Assert::true($p->closest('div')->matches('.inner')); + Assert::true($p->closest('.outer')->matches('.outer')); + Assert::null($p->closest('span')); +}); + test('find() returns empty array for no matches', function () { $dom = DomQuery::fromHtml('
'); Assert::same([], $dom->find('nonexistent'));