Skip to content

Child combinator throws TypeError when its right-hand side matches the document element (X > * always fails) #74

Description

@jakejackson1

Guidelines

Description of the bug

Any selector using the child combinator (>) whose right-hand side can also match the document element raises a TypeError from the selector engine. In practice this means X > * always fails, whatever X is, because * matches the root as well as everything else.

Uncaught TypeError: QueryPath\CSS\DOMTraverser::matchesSimpleSelector():
Argument #1 ($node) must be of type DOMElement, DOMDocument given

This affects qp(), htmlqp(), html5qp() and find() alike, since they all go through CSS\DOMTraverser.

Root cause

DOMTraverser::combineDirectDescendant() hands the parent node straight to matchesSimpleSelector() without checking its type (src/CSS/DOMTraverser.php:358-367):

public function combineDirectDescendant($node, $selectors, $index)
{
	$parent = $node->parentNode;
	if (empty($parent)) {
		return false;
	}

	return $this->matchesSimpleSelector($parent, $selectors, $index);
}

matchesSimpleSelector() is typed DOMElement $node (src/CSS/DOMTraverser.php:207). When the right-hand selector matches the document element, $parent is the DOMDocument, and the type declaration rejects it.

combineAnyDescendant() immediately above already guards for this case:

if ($node->nodeType != XML_ELEMENT_NODE) {
	continue;
}

which is why the descendant combinator (X *) works and the child combinator does not.

Suggested fix

Return false when the parent is not an element:

$parent = $node->parentNode;
if (empty($parent) || $parent->nodeType !== XML_ELEMENT_NODE) {
	return false;
}

This is correct by definition — the document node can never match an element selector.

Workaround

children() does the same job without going through the combinator.

QueryPath version

4.2.0 (also reproduces on main at bed5d2c)

PHP Version and environment (server type, cli provider etc., enclosing libraries and their respective versions)

PHP 8.3.16 CLI (macOS, Homebrew). Not PHP-8 specific — the cause is present on every supported version unless noted.

Minimal reproducible PHP+HTML snippet to replicate bug

<?php
require __DIR__ . '/vendor/autoload.php';

$xml = '<?xml version="1.0"?><root><wrap><a/><b/></wrap></root>';

var_dump(qp($xml, 'wrap > a')->count());   // int(1) - fine
var_dump(qp($xml, 'wrap > *')->count());   // TypeError

html5qp('<html><body><ul><li>a</li></ul></body></html>', 'ul > *') fails identically.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions