From 99423a325c6c38ec37f9f847b0b9c281a2d5a732 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 3 May 2024 13:48:56 +0200 Subject: [PATCH] added DomQuery::matches() --- src/Framework/DomQuery.php | 14 ++++++- tests/Framework/DomQuery.css2Xpath.phpt | 56 ++++++++++++------------- tests/Framework/DomQuery.fromXml.phpt | 41 ++++++++++++++++-- 3 files changed, 78 insertions(+), 33 deletions(-) diff --git a/src/Framework/DomQuery.php b/src/Framework/DomQuery.php index bc00eba8..621baefa 100644 --- a/src/Framework/DomQuery.php +++ b/src/Framework/DomQuery.php @@ -66,7 +66,8 @@ public static function fromXml(string $xml): self */ public function find(string $selector): array { - return $this->xpath(self::css2xpath($selector)); + $base = str_starts_with($selector, '>') ? 'self' : 'descendant'; + return $this->xpath($base . '::' . self::css2xpath($selector)); } @@ -79,12 +80,21 @@ public function has(string $selector): bool } + /** + * Determines if the current element matches the specified CSS selector. + */ + public function matches(string $selector): bool + { + return (bool) $this->xpath('self::' . self::css2xpath($selector)); + } + + /** * Converts a CSS selector into an XPath expression. */ public static function css2xpath(string $css): string { - $xpath = './/*'; + $xpath = '*'; preg_match_all(<<<'XX' / ([#.:]?)([a-z][a-z0-9_-]*)| # id, class, pseudoclass (1,2) diff --git a/tests/Framework/DomQuery.css2Xpath.phpt b/tests/Framework/DomQuery.css2Xpath.phpt index ee4c80c1..cc1fee3c 100644 --- a/tests/Framework/DomQuery.css2Xpath.phpt +++ b/tests/Framework/DomQuery.css2Xpath.phpt @@ -8,73 +8,73 @@ use Tester\DomQuery; require __DIR__ . '/../bootstrap.php'; test('type selectors', function () { - Assert::same('.//*', DomQuery::css2xpath('*')); - Assert::same('.//foo', DomQuery::css2xpath('foo')); + Assert::same('*', DomQuery::css2xpath('*')); + Assert::same('foo', DomQuery::css2xpath('foo')); }); test('#ID', function () { - Assert::same(".//*[@id='foo']", DomQuery::css2xpath('#foo')); - Assert::same(".//*[@id='id']", DomQuery::css2xpath('*#id')); + Assert::same("*[@id='foo']", DomQuery::css2xpath('#foo')); + Assert::same("*[@id='id']", DomQuery::css2xpath('*#id')); }); test('class', function () { - Assert::same(".//*[contains(concat(' ', normalize-space(@class), ' '), ' foo ')]", DomQuery::css2xpath('.foo')); - Assert::same(".//*[contains(concat(' ', normalize-space(@class), ' '), ' foo ')]", DomQuery::css2xpath('*.foo')); - Assert::same(".//*[contains(concat(' ', normalize-space(@class), ' '), ' foo ')][contains(concat(' ', normalize-space(@class), ' '), ' bar ')]", DomQuery::css2xpath('.foo.bar')); + Assert::same("*[contains(concat(' ', normalize-space(@class), ' '), ' foo ')]", DomQuery::css2xpath('.foo')); + Assert::same("*[contains(concat(' ', normalize-space(@class), ' '), ' foo ')]", DomQuery::css2xpath('*.foo')); + Assert::same("*[contains(concat(' ', normalize-space(@class), ' '), ' foo ')][contains(concat(' ', normalize-space(@class), ' '), ' bar ')]", DomQuery::css2xpath('.foo.bar')); }); test('attribute selectors', function () { - Assert::same('.//div[@foo]', DomQuery::css2xpath('div[foo]')); - Assert::same(".//div[@foo='bar']", DomQuery::css2xpath('div[foo=bar]')); - Assert::same(".//*[@foo='bar']", DomQuery::css2xpath('[foo="bar"]')); - Assert::same(".//div[@foo='bar']", DomQuery::css2xpath('div[foo="bar"]')); - Assert::same(".//div[@foo='bar']", DomQuery::css2xpath("div[foo='bar']")); - Assert::same(".//div[@foo='bar']", DomQuery::css2xpath('div[Foo="bar"]')); - Assert::same(".//div[contains(concat(' ', normalize-space(@foo), ' '), ' bar ')]", DomQuery::css2xpath('div[foo~="bar"]')); - Assert::same(".//div[contains(@foo, 'bar')]", DomQuery::css2xpath('div[foo*="bar"]')); - Assert::same(".//div[starts-with(@foo, 'bar')]", DomQuery::css2xpath('div[foo^="bar"]')); - Assert::same(".//div[substring(@foo, string-length(@foo)-0)='bar']", DomQuery::css2xpath('div[foo$="bar"]')); - Assert::same(".//div[@foo='bar[]']", DomQuery::css2xpath("div[foo='bar[]']")); - Assert::same(".//div[@foo='bar[]']", DomQuery::css2xpath('div[foo="bar[]"]')); + Assert::same('div[@foo]', DomQuery::css2xpath('div[foo]')); + Assert::same("div[@foo='bar']", DomQuery::css2xpath('div[foo=bar]')); + Assert::same("*[@foo='bar']", DomQuery::css2xpath('[foo="bar"]')); + Assert::same("div[@foo='bar']", DomQuery::css2xpath('div[foo="bar"]')); + Assert::same("div[@foo='bar']", DomQuery::css2xpath("div[foo='bar']")); + Assert::same("div[@foo='bar']", DomQuery::css2xpath('div[Foo="bar"]')); + Assert::same("div[contains(concat(' ', normalize-space(@foo), ' '), ' bar ')]", DomQuery::css2xpath('div[foo~="bar"]')); + Assert::same("div[contains(@foo, 'bar')]", DomQuery::css2xpath('div[foo*="bar"]')); + Assert::same("div[starts-with(@foo, 'bar')]", DomQuery::css2xpath('div[foo^="bar"]')); + Assert::same("div[substring(@foo, string-length(@foo)-0)='bar']", DomQuery::css2xpath('div[foo$="bar"]')); + Assert::same("div[@foo='bar[]']", DomQuery::css2xpath("div[foo='bar[]']")); + Assert::same("div[@foo='bar[]']", DomQuery::css2xpath('div[foo="bar[]"]')); }); test('variants', function () { - Assert::same(".//*[@id='foo']|//*[@id='bar']", DomQuery::css2xpath('#foo, #bar')); - Assert::same(".//*[@id='foo']|//*[@id='bar']", DomQuery::css2xpath('#foo,#bar')); - Assert::same(".//*[@id='foo']|//*[@id='bar']", DomQuery::css2xpath('#foo ,#bar')); + Assert::same("*[@id='foo']|//*[@id='bar']", DomQuery::css2xpath('#foo, #bar')); + Assert::same("*[@id='foo']|//*[@id='bar']", DomQuery::css2xpath('#foo,#bar')); + Assert::same("*[@id='foo']|//*[@id='bar']", DomQuery::css2xpath('#foo ,#bar')); }); test('descendant combinator', function () { Assert::same( - ".//div[@id='foo']//*[contains(concat(' ', normalize-space(@class), ' '), ' bar ')]", + "div[@id='foo']//*[contains(concat(' ', normalize-space(@class), ' '), ' bar ')]", DomQuery::css2xpath('div#foo .bar'), ); Assert::same( - './/div//*//p', + 'div//*//p', DomQuery::css2xpath('div * p'), ); }); test('child combinator', function () { - Assert::same(".//div[@id='foo']/span", DomQuery::css2xpath('div#foo>span')); - Assert::same(".//div[@id='foo']/span", DomQuery::css2xpath('div#foo > span')); + Assert::same("div[@id='foo']/span", DomQuery::css2xpath('div#foo>span')); + Assert::same("div[@id='foo']/span", DomQuery::css2xpath('div#foo > span')); }); test('general sibling combinator', function () { - Assert::same('.//div/following-sibling::span', DomQuery::css2xpath('div ~ span')); + Assert::same('div/following-sibling::span', DomQuery::css2xpath('div ~ span')); }); test('complex', function () { Assert::same( - ".//div[@id='foo']//span[contains(concat(' ', normalize-space(@class), ' '), ' bar ')]" + "div[@id='foo']//span[contains(concat(' ', normalize-space(@class), ' '), ' bar ')]" . "|//*[@id='bar']//li[contains(concat(' ', normalize-space(@class), ' '), ' baz ')]//a", DomQuery::css2xpath('div#foo span.bar, #bar li.baz a'), ); diff --git a/tests/Framework/DomQuery.fromXml.phpt b/tests/Framework/DomQuery.fromXml.phpt index 5033c16d..d598e4d6 100644 --- a/tests/Framework/DomQuery.fromXml.phpt +++ b/tests/Framework/DomQuery.fromXml.phpt @@ -7,6 +7,41 @@ use Tester\DomQuery; require __DIR__ . '/../bootstrap.php'; -$q = DomQuery::fromXml('hello'); -Assert::true($q->has('body')); -Assert::false($q->has('p')); + +$xml = <<<'XML' + + Item 1 + Item 2 + + Item 3 + + + XML; + +$dom = DomQuery::fromXml($xml); +Assert::type(DomQuery::class, $dom); + +// root +Assert::true($dom->matches('root')); +Assert::false($dom->has('root')); + +// find +$results = $dom->find('.foo'); +Assert::count(2, $results); +Assert::type(DomQuery::class, $results[0]); +Assert::type(DomQuery::class, $results[1]); + +// children +$results = $dom->find('> item'); +Assert::count(2, $results); + +// has +Assert::true($dom->has('#test1')); +Assert::false($dom->has('#nonexistent')); +Assert::false($dom->find('container')[0]->has('#test1')); +Assert::true($dom->find('container')[0]->has('#test3')); + +// matches +$subItem = $dom->find('#test1')[0]; +Assert::true($subItem->matches('.foo')); +Assert::false($subItem->matches('.bar'));