Skip to content

Commit

Permalink
Merge branch 'super-dm3-fix-nth-child'
Browse files Browse the repository at this point in the history
  • Loading branch information
jakejackson1 committed Nov 19, 2024
2 parents 49f95fb + 2da963a commit b5fb9e9
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 169 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ QueryPath Changelog
# Unreleased changes

- Update composer.json to mark library as PHP 8.4 compatible
- Use `\QueryPath\CSS\DOMTraverser\Util::parseAnB()` in `\QueryPath\CSS\QueryPathEventHandler` class to parse the `:nth-child(an+b)` syntax
- Deprecate protected method `\QueryPath\CSS\QueryPathEventHandler::parseAnB()` in favor of public static method `\QueryPath\CSS\DOMTraverser\Util::parseAnB()`

# 4.0.1

Expand Down
1 change: 0 additions & 1 deletion src/CSS/DOMTraverser/PseudoClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,6 @@ protected function isNthChild($node, $value, $reverse = false, $byType = false):
$parent = $node->parentNode;
if (empty($parent)
|| ($groupSize === 0 && $elementInGroup === 0)
|| ($groupSize > 0 && $elementInGroup > $groupSize)
) {
return false;
}
Expand Down
2 changes: 2 additions & 0 deletions src/CSS/DOMTraverser/Util.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ public static function parseAnB($rule): array
$aVal = $matches[1] ?? 1;
if ($aVal === '-') {
$aVal = -1;
} elseif ($aVal === '') {
$aVal = 1;
} else {
$aVal = (int) $aVal;
}
Expand Down
43 changes: 8 additions & 35 deletions src/CSS/QueryPathEventHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
use DOMElement;
use DOMNode;
use DOMNodeList;
use QueryPath\CSS\DOMTraverser\Util;
use QueryPath\Exception;
use SplObjectStorage;
use stdClass;
Expand Down Expand Up @@ -486,19 +487,19 @@ public function pseudoClass($name, $value = null)

// Standard child-checking items.
case 'nth-child':
[$aVal, $bVal] = $this->parseAnB($value);
[$aVal, $bVal] = Util::parseAnB($value);
$this->nthChild($aVal, $bVal);
break;
case 'nth-last-child':
[$aVal, $bVal] = $this->parseAnB($value);
[$aVal, $bVal] = Util::parseAnB($value);
$this->nthLastChild($aVal, $bVal);
break;
case 'nth-of-type':
[$aVal, $bVal] = $this->parseAnB($value);
[$aVal, $bVal] = Util::parseAnB($value);
$this->nthOfTypeChild($aVal, $bVal, false);
break;
case 'nth-last-of-type':
[$aVal, $bVal] = $this->parseAnB($value);
[$aVal, $bVal] = Util::parseAnB($value);
$this->nthLastOfTypeChild($aVal, $bVal);
break;
case 'first-child':
Expand Down Expand Up @@ -712,41 +713,13 @@ private function getByPosition($operator, $pos)
* @param $rule
* Some rule in the an+b format.
*
* @return
* @return int[]
* Array (list($aVal, $bVal)) of the two values.
* @throws ParseException
* If the rule does not follow conventions.
* @deprecated 4.0.2
*/
protected function parseAnB($rule)
{
if ($rule == 'even') {
return [2, 0];
} elseif ($rule == 'odd') {
return [2, 1];
} elseif ($rule == 'n') {
return [1, 0];
} elseif (is_numeric($rule)) {
return [0, (int) $rule];
}

$rule = explode('n', $rule);
if (count($rule) == 0) {
throw new ParseException("nth-child value is invalid.");
}

// Each of these is legal: 1, -1, - and <empty>. '-' is shorthand for -1. <empty> is shorthand for 1
$aVal = trim($rule[0]);
if ($aVal === '') {
$aVal = 1;
} elseif ($aVal === '-') {
$aVal = -1;
} else {
$aVal = (int) $aVal;
}

$bVal = ! empty($rule[1]) ? (int) trim($rule[1]) : 0;

return [$aVal, $bVal];
return Util::parseAnB($rule);
}

/**
Expand Down
168 changes: 35 additions & 133 deletions tests/QueryPath/CSS/PseudoClassTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,29 @@ public function testNthLastChild()
$this->assertEquals(3, $i);
}

public function testNthChild()
public function nthChildProvider(): array
{
return [
['2n+1', 10, ['a', 'c']], // Every odd row
['odd', 10, ['a', 'c']], // Every odd row
['2n', 10, ['b', 'd']], // Every even row
['even', 10, ['b', 'd']], // Even (2n)
['4n-1', 5, 'c' ], // 4n - 1 == 4n + 3
['6n-1', 3, null ], // 6n - 1
['26n-1', 0, null ], // 26n - 1
['0n+0', 0, null ], // 0n + 0 -- spec says this is always FALSE
['3', 1, 'c' ], // 3 (0n+3)
['-n+3', 3, null ], // -n+3: First three elements
['n+3', 18, null ], // third+ elements
['2n+4', 9, ['d', 'b']], // fourth+ even elements
['6n+30', 0, null ], // 6n + 30. These should always fail to match
];
}

/**
* @dataProvider nthChildProvider
*/
public function testNthChild($pattern, $matchesCount, $matchTag)
{
$xml = '<?xml version="1.0"?><root>';
$xml .= str_repeat('<a/><b/><c/><d/>', 5);
Expand All @@ -438,145 +460,25 @@ public function testNthChild()
[$ele, $root] = $this->doc($xml, 'root');
$nl = $root->childNodes;

// 2n + 1 -- Every odd row.
$i = 0;
$expects = ['a', 'c'];
$j = 0;
foreach ($nl as $n) {
$res = $ps->elementMatches('nth-child', $n, $root, '2n+1');
if ($res) {
++$i;
$name = $n->tagName;
$this->assertContains($name, $expects, sprintf('Expected b or d, got %s in slot %s', $name, ++$j));
}
}
$this->assertEquals(10, $i, '2n+1 is ten items.');

// Odd
$i = 0;
$expects = ['a', 'c'];
$j = 0;
foreach ($nl as $n) {
$res = $ps->elementMatches('nth-child', $n, $root, 'odd');
if ($res) {
++$i;
$name = $n->tagName;
$this->assertContains($name, $expects, sprintf('Expected b or d, got %s in slot %s', $name, ++$j));
}
}
$this->assertEquals(10, $i, '2n+1 is ten items.');

// 2n + 0 -- every even row
$i = 0;
$expects = ['b', 'd'];
foreach ($nl as $n) {
$res = $ps->elementMatches('nth-child', $n, $root, '2n');
if ($res) {
++$i;
$name = $n->tagName;
$this->assertContains($name, $expects, 'Expected a or c, got ' . $name);
}
}
$this->assertEquals(10, $i, '2n+0 is ten items.');

// Even (2n)
$i = 0;
$expects = ['b', 'd'];
foreach ($nl as $n) {
$res = $ps->elementMatches('nth-child', $n, $root, 'even');
if ($res) {
++$i;
$name = $n->tagName;
$this->assertContains($name, $expects, 'Expected a or c, got ' . $name);
}
}
$this->assertEquals(10, $i, ' even is ten items.');

// 4n - 1 == 4n + 3
$i = 0;
$j = 0;
foreach ($nl as $n) {
$res = $ps->elementMatches('nth-child', $n, $root, '4n-1');
$res = $ps->elementMatches('nth-child', $n, $root, $pattern);
if ($res) {
++$i;
$name = $n->tagName;
$this->assertEquals('c', $name, 'Expected c, got ' . $name);
}
}
$this->assertEquals(5, $i);

// 6n - 1
$i = 0;
foreach ($nl as $n) {
$res = $ps->elementMatches('nth-child', $n, $root, '6n-1');
if ($res) {
++$i;
}
}
$this->assertEquals(3, $i);

// 6n + 1
$i = 0;
foreach ($nl as $n) {
$res = $ps->elementMatches('nth-child', $n, $root, '6n+1');
if ($res) {
++$i;
}
}
$this->assertEquals(4, $i);

// 26n - 1
$i = 0;
foreach ($nl as $n) {
$res = $ps->elementMatches('nth-child', $n, $root, '26n-1');
if ($res) {
++$i;
}
}
$this->assertEquals(0, $i);

// 0n + 0 -- spec says this is always FALSE.
$i = 0;
foreach ($nl as $n) {
$res = $ps->elementMatches('nth-child', $n, $root, '0n+0');
if ($res) {
++$i;
}
}
$this->assertEquals(0, $i);

// 3 (0n+3)
$i = 0;
foreach ($nl as $n) {
$res = $ps->elementMatches('nth-child', $n, $root, '3');
if ($res) {
++$i;
$this->assertEquals('c', $n->tagName);
}
}
$this->assertEquals(1, $i);

// -n+3: First three elements.
$i = 0;
foreach ($nl as $n) {
$res = $ps->elementMatches('nth-child', $n, $root, '-n+3');
if ($res) {
++$i;
//$this->assertEquals('c', $n->tagName);
}
}
$this->assertEquals(3, $i);

// BROKEN RULES -- these should always fail to match.

// 6n + 7;
$i = 0;
foreach ($nl as $n) {
$res = $ps->elementMatches('nth-child', $n, $root, '6n+7');
if ($res) {
++$i;
if (is_string($matchTag)) {
$this->assertEquals($matchTag, $name, 'Invalid tagName match');
} elseif (is_array($matchTag)) {
$this->assertContains(
$name,
$matchTag,
'Expected only ['.implode(', ', $matchTag).'] tags, got '.$name.' in slot '.++$j
);
}
}
}
$this->assertEquals(0, $i);
$this->assertEquals($matchesCount, $i, 'Invalid matches count');
}

public function testEven()
Expand Down
1 change: 1 addition & 0 deletions tests/QueryPath/CSS/UtilTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public function testParseAnB()
$this->assertEquals([2, -1], Util::parseAnB('2n - 1'));
// -n + 3
$this->assertEquals([-1, 3], Util::parseAnB('-n+3'));
$this->assertEquals([1, 3], Util::parseAnB('n+3'));

// Test invalid values
$this->assertEquals([0, 0], Util::parseAnB('obviously + invalid'));
Expand Down

0 comments on commit b5fb9e9

Please sign in to comment.