From deff28d1692fb5d48b5ccccbbf303963dc8df159 Mon Sep 17 00:00:00 2001 From: Dmitriy Zo Date: Wed, 6 Nov 2024 15:10:31 +0000 Subject: [PATCH 1/2] Fix for :nth-child(n+B) --- CHANGELOG.md | 5 + src/CSS/QueryPathEventHandler.php | 6 +- .../CSS/QueryPathEventHandlerTest.php | 121 +++++------------- 3 files changed, 43 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7f201ec..8d130241 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ QueryPath Changelog =========================== +# 4.0.2 + +- Fix for :nth-child(n+B). It now selects B-th and all the following elements +- Refactored relevant PHPUnit Test Suite to use @dataProvider to remove code repetitions + # 4.0.1 - Only define global functions qp(), htmlqp(), and html5qp() if they haven't been defined already. diff --git a/src/CSS/QueryPathEventHandler.php b/src/CSS/QueryPathEventHandler.php index 43e06e3a..a6947b6b 100644 --- a/src/CSS/QueryPathEventHandler.php +++ b/src/CSS/QueryPathEventHandler.php @@ -736,7 +736,11 @@ protected function parseAnB($rule) // Each of these is legal: 1, -1, and -. '-' is shorthand for -1. $aVal = trim($rule[0]); - $aVal = ($aVal == '-') ? -1 : (int) $aVal; + if ($aVal === '') { + $aVal = 1; + } else { + $aVal = ($aVal == '-') ? -1 : (int) $aVal; + } $bVal = ! empty($rule[1]) ? (int) trim($rule[1]) : 0; diff --git a/tests/QueryPath/CSS/QueryPathEventHandlerTest.php b/tests/QueryPath/CSS/QueryPathEventHandlerTest.php index fb025230..b0fb56e5 100644 --- a/tests/QueryPath/CSS/QueryPathEventHandlerTest.php +++ b/tests/QueryPath/CSS/QueryPathEventHandlerTest.php @@ -615,7 +615,31 @@ public function testChildAtIndex() { $this->assertEquals('one', $this->nthMatch($matches, 1)->getAttribute('id')); }*/ - public function testPseudoClassNthChild() + public function nthChildProvider(): array + { + return [ + [':root :even', 3, 'four' ], // full list + ['i:even', 2, 'four' ], // restricted to specific element + ['i:odd', 3, 'three' ], // restricted to specific element, odd this time + ['i:nth-child(odd)', 3, 'three' ], // odd + ['i:nth-child(2n+1)', 3, 'three' ], // odd, equiv to 2n + 1 + ['i:nth-child(2n-1)', 3, 'three' ], // odd, equiv to 2n + 1 + ['i:nth-child(2n)', 2, 'four' ], // even + //['i:nth-child(-2n)', 2, 'four' ], // Not totally sure what should be returned here + ['i:nth-child(4n)', 1, 'four', 0], // every fourth row + ['i:nth-child(4n+1)', 2, 'five' ], // first of every four rows + ['i:nth-child(1)', 1, 'one', 0 ], // first row + ['i:nth-child(0n-0)', 0, null ], // empty list + ['i:nth-child(n+3)', 3, 'four' ], // third+ lines + //['i:nth-child(0n+3)', 1, 'three' ], // third element in a group of siblings + //['i:nth-child(-n+3)', 3, 'three' ], // first three lines + ]; + } + + /** + * @dataProvider nthChildProvider + */ + public function testPseudoClassNthChild($selector, $matchesCount, $matchId, $matchIndex = 1) { $xml = ' @@ -631,95 +655,16 @@ public function testPseudoClassNthChild() // Test full list $handler = new QueryPathEventHandler($doc); - $handler->find(':root :even'); - $matches = $handler->getMatches(); - $this->assertEquals(3, $matches->count()); - $this->assertEquals('four', $this->nthMatch($matches, 1)->getAttribute('id')); - - // Test restricted to specific element - $handler = new QueryPathEventHandler($doc); - $handler->find('i:even'); - $matches = $handler->getMatches(); - $this->assertEquals(2, $matches->count()); - $this->assertEquals('four', $this->nthMatch($matches, 1)->getAttribute('id')); - - // Test restricted to specific element, odd this time - $handler = new QueryPathEventHandler($doc); - $handler->find('i:odd'); - $matches = $handler->getMatches(); - $this->assertEquals(3, $matches->count()); - $this->assertEquals('three', $this->nthMatch($matches, 1)->getAttribute('id')); - - // Test nth-child(odd) - $handler = new QueryPathEventHandler($doc); - $handler->find('i:nth-child(odd)'); - $matches = $handler->getMatches(); - $this->assertEquals(3, $matches->count()); - $this->assertEquals('three', $this->nthMatch($matches, 1)->getAttribute('id')); - - // Test nth-child(2n+1) - $handler = new QueryPathEventHandler($doc); - $handler->find('i:nth-child(2n+1)'); - $matches = $handler->getMatches(); - $this->assertEquals(3, $matches->count()); - $this->assertEquals('three', $this->nthMatch($matches, 1)->getAttribute('id')); - - // Test nth-child(2n) (even) - $handler = new QueryPathEventHandler($doc); - $handler->find('i:nth-child(2n)'); + $handler->find($selector); $matches = $handler->getMatches(); - $this->assertEquals(2, $matches->count()); - $this->assertEquals('four', $this->nthMatch($matches, 1)->getAttribute('id')); - - // Not totally sure what should be returned here - // Test nth-child(-2n) - // $handler = new QueryPathEventHandler($doc); - // $handler->find('i:nth-child(-2n)'); - // $matches = $handler->getMatches(); - // $this->assertEquals(2, $matches->count()); - // $this->assertEquals('four', $this->nthMatch($matches, 1)->getAttribute('id')); - - // Test nth-child(2n-1) (odd, equiv to 2n + 1) - $handler = new QueryPathEventHandler($doc); - $handler->find('i:nth-child(2n-1)'); - $matches = $handler->getMatches(); - $this->assertEquals(3, $matches->count()); - $this->assertEquals('three', $this->nthMatch($matches, 1)->getAttribute('id')); - - // Test nth-child(4n) (every fourth row) - $handler = new QueryPathEventHandler($doc); - $handler->find('i:nth-child(4n)'); - $matches = $handler->getMatches(); - $this->assertEquals(1, $matches->count()); - $this->assertEquals('four', $this->nthMatch($matches, 0)->getAttribute('id')); - - // Test nth-child(4n+1) (first of every four rows) - $handler = new QueryPathEventHandler($doc); - $handler->find('i:nth-child(4n+1)'); - $matches = $handler->getMatches(); - // Should match rows one and five - $this->assertEquals(2, $matches->count()); - $this->assertEquals('five', $this->nthMatch($matches, 1)->getAttribute('id')); - - // Test nth-child(1) (First row) - $handler = new QueryPathEventHandler($doc); - $handler->find('i:nth-child(1)'); - $matches = $handler->getMatches(); - $this->assertEquals(1, $matches->count()); - $this->assertEquals('one', $this->firstMatch($matches)->getAttribute('id')); - - // Test nth-child(0n-0) (Empty list) - $handler = new QueryPathEventHandler($doc); - $handler->find('i:nth-child(0n-0)'); - $matches = $handler->getMatches(); - $this->assertEquals(0, $matches->count()); - - // Test nth-child(-n+3) (First three lines) - // $handler = new QueryPathEventHandler($doc); - // $handler->find('i:nth-child(-n+3)'); - // $matches = $handler->getMatches(); - // $this->assertEquals(3, $matches->count()); + $this->assertEquals($matchesCount, $matches->count()); + if ($matchesCount) { + $this->assertEquals($matchId, $this->nthMatch($matches, $matchIndex)->getAttribute('id')); + } + } + public function testPseudoClassNthChildNested() + { $xml = ' From 843ebe2052c43d92e5ab13e961576775b79974f8 Mon Sep 17 00:00:00 2001 From: Dmitriy Zo Date: Wed, 6 Nov 2024 17:04:38 +0000 Subject: [PATCH 2/2] Fix for error while getting parent(s) on HTML node --- .gitignore | 4 +++- CHANGELOG.md | 5 +++++ src/Helpers/QueryFilters.php | 4 ++-- tests/QueryPath/DOMQueryTest.php | 2 ++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index e1d95c1a..32df0b84 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ docs/* doc/* test/fakepear vendor/ -composer.lock \ No newline at end of file +composer.lock +.phpunit.result.cache +.idea/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d130241..eda61962 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ QueryPath Changelog =========================== +# 4.0.3 + +- Fix for error while getting parent() on HTML root node +- Fix for error while getting parents() on HTML node + # 4.0.2 - Fix for :nth-child(n+B). It now selects B-th and all the following elements diff --git a/src/Helpers/QueryFilters.php b/src/Helpers/QueryFilters.php index c242a14f..841f44d6 100644 --- a/src/Helpers/QueryFilters.php +++ b/src/Helpers/QueryFilters.php @@ -798,7 +798,7 @@ public function parent($selector = null): Query { $found = new SplObjectStorage(); foreach ($this->matches as $m) { - while ($m->parentNode->nodeType !== XML_DOCUMENT_NODE) { + while ($m->parentNode && $m->parentNode->nodeType !== XML_DOCUMENT_NODE) { $m = $m->parentNode; // Is there any case where parent node is not an element? if ($m->nodeType === XML_ELEMENT_NODE) { @@ -838,7 +838,7 @@ public function parents($selector = null): Query { $found = new SplObjectStorage(); foreach ($this->matches as $m) { - while ($m->parentNode->nodeType !== XML_DOCUMENT_NODE) { + while ($m->parentNode && $m->parentNode->nodeType !== XML_DOCUMENT_NODE) { $m = $m->parentNode; // Is there any case where parent node is not an element? if ($m->nodeType === XML_ELEMENT_NODE) { diff --git a/tests/QueryPath/DOMQueryTest.php b/tests/QueryPath/DOMQueryTest.php index 39c49fda..4e4027f5 100644 --- a/tests/QueryPath/DOMQueryTest.php +++ b/tests/QueryPath/DOMQueryTest.php @@ -1676,6 +1676,8 @@ public function testParent() $this->assertEquals('root', qp($file, 'unary')->parent()->tag()); $this->assertEquals('root', qp($file, 'li')->parent('root')->tag()); $this->assertEquals(2, qp($file, 'li')->parent()->count()); + $this->assertEquals(0, qp(DATA_HTML_FILE, 'html')->parent()->count()); + $this->assertEquals(2, qp(DATA_HTML_FILE, 'table')->parents()->count()); } public function testClosest()