From d12d8b499622637b0a6cfef2d38444c8819b49ff Mon Sep 17 00:00:00 2001 From: Jake Jackson Date: Fri, 22 Mar 2024 10:24:35 +1100 Subject: [PATCH 1/5] Unit tests that need to pass for issue 49 --- tests/Issues/Issue49Test.php | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 tests/Issues/Issue49Test.php diff --git a/tests/Issues/Issue49Test.php b/tests/Issues/Issue49Test.php new file mode 100644 index 0000000..2ccabfd --- /dev/null +++ b/tests/Issues/Issue49Test.php @@ -0,0 +1,31 @@ +', 'div'); + + /* Check if the DOMNode or its children matches */ + $this->assertTrue($q->is(':text')); + $this->assertCount(2, $q->find(':text')); + + $textNode = $q->find('div')->contents()->eq(0); + $this->assertTrue($textNode->is(':text')); + } + + public function testCheckingForEmptyTextInputs(): void + { + $q = html5qp('
Sample
', 'div'); + + /* Check if the DOMNode or its children matches */ + $this->assertFalse($q->is(':text')); + $this->assertCount(0, $q->find(':text')); + + /* check if a text node matches */ + $textNode = $q->find('div')->contents()->eq(0); + $this->assertFalse($textNode->is(':text')); + } +} \ No newline at end of file From b108de7102ecb21a8ed6475f03b5fccd1701a986 Mon Sep 17 00:00:00 2001 From: Jake Jackson Date: Fri, 21 Aug 2026 14:39:07 +1000 Subject: [PATCH 2/5] Fix :text pseudo-class and selector fatals on non-element nodes Two defects, per issue #49. 1. Running any selector against a match set that contained a non-element node (text, comment, CDATA, processing instruction) fataled, because the traverser assumed every node was a DOMElement and called element-only methods such as getElementsByTagName() and tagName on it. Non-element nodes now simply do not match an element selector: - DOMTraverser::matchesSimpleSelector() returns FALSE for any node that is not a DOMElement. matchesSelector(), matchesSimpleSelector() and combine() take a DOMNode rather than a DOMElement so they can make that decision instead of raising a TypeError. - initialMatchOnElement(), initialMatchOnID() and initialMatchOnClasses() skip nodes that cannot hold elements. - PseudoClass::elementMatches() and Util::matchesAttribute[NS]() guard against non-elements as well, since they are reachable directly. initialMatchOnElement() also now captures the node itself when the element selector is the wildcard, which is what initialMatchOnID() and initialMatchOnClasses() already do for their own selectors. 2. The :text pseudo-class matched anything with type="text". It now follows jQuery, matching an input whose type attribute is absent (text is an input's default type) or is text, compared case-insensitively. It has never indicated whether a node is a text node. Fixed in both the current engine (CSS\DOMTraverser) and the legacy engine (CSS\QueryPathEventHandler) that remove() and replaceAll() still use, so the two agree. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 7 ++ src/CSS/DOMTraverser.php | 44 ++++++-- src/CSS/DOMTraverser/PseudoClass.php | 38 +++++++ src/CSS/DOMTraverser/Util.php | 12 ++ src/CSS/QueryPathEventHandler.php | 31 +++++ tests/Issues/Issue49Test.php | 163 ++++++++++++++++++++++++++- 6 files changed, 285 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9395aeb..069204a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ QueryPath Changelog =========================== +# Unreleased changes + +- Fix fatal error when running a CSS selector against a match set that contains non-element nodes (text, comment, CDATA + or processing instruction). Those nodes now simply do not match, instead of calling element-only DOM methods on them. +- Fix the `:text` pseudo-class so it matches jQuery: it selects `input` elements whose `type` attribute is absent or is + `text` (case-insensitively). It never indicated, and still does not indicate, whether a node is a text node. + # 3.2.3 - Add PHP 8.3 Support diff --git a/src/CSS/DOMTraverser.php b/src/CSS/DOMTraverser.php index f426176..0f06c60 100644 --- a/src/CSS/DOMTraverser.php +++ b/src/CSS/DOMTraverser.php @@ -7,6 +7,7 @@ use DOMDocument; use DOMElement; +use DOMNode; use DOMNodeList; use DOMXPath; use QueryPath\CSS\DOMTraverser\Util; @@ -177,14 +178,14 @@ public function matches() * absolutely huge selectors or for versions of PHP tuned to * strictly limit recursion depth. * - * @param DOMElement $node + * @param DOMNode $node * The DOMNode to check. * @param $selector * * @return boolean * A boolean TRUE if the node matches, false otherwise. */ - public function matchesSelector(DOMElement $node, $selector) + public function matchesSelector(DOMNode $node, $selector) { return $this->matchesSimpleSelector($node, $selector, 0); } @@ -196,7 +197,7 @@ public function matchesSelector(DOMElement $node, $selector) * this checks only a simple selector (plus an optional * combinator). * - * @param DOMElement $node + * @param DOMNode $node * @param $selectors * @param $index * @@ -204,8 +205,16 @@ public function matchesSelector(DOMElement $node, $selector) * A boolean TRUE if the node matches, false otherwise. * @throws NotImplementedException */ - public function matchesSimpleSelector(DOMElement $node, $selectors, $index) + public function matchesSimpleSelector(DOMNode $node, $selectors, $index) { + // Selectors only ever match elements. A match set may legitimately + // contain text, comment, CDATA or processing instruction nodes (e.g. + // from contents()), and those simply do not match -- rather than + // blowing up on the element-only DOM API used below. + if (! $node instanceof DOMElement) { + return false; + } + $selector = $selectors[$index]; // Note that this will short circuit as soon as one of these // returns FALSE. @@ -257,7 +266,7 @@ public function matchesSimpleSelector(DOMElement $node, $selectors, $index) * @return boolean * TRUE if the next selector(s) match. */ - public function combine(DOMElement $node, $selectors, $index) + public function combine(DOMNode $node, $selectors, $index) { $selector = $selectors[$index]; //$this->debug(implode(' ', $selectors)); @@ -476,6 +485,11 @@ protected function initialMatchOnID(SimpleSelector $selector, SplObjectStorage $ // Now we try to find any matching IDs. /** @var DOMElement $node */ foreach ($matches as $node) { + // Non-element nodes have neither attributes nor element children. + if (! $node instanceof DOMElement) { + continue; + } + if ($node->getAttribute('id') === $id) { $found->attach($node); } @@ -520,6 +534,11 @@ protected function initialMatchOnClasses(SimpleSelector $selector, SplObjectStor // Now we try to find any matching IDs. /** @var DOMElement $node */ foreach ($matches as $node) { + // Non-element nodes have neither attributes nor element children. + if (! $node instanceof DOMElement) { + continue; + } + // Refactor me! if ($node->hasAttribute('class')) { $intersect = array_intersect($selector->classes, explode(' ', $node->getAttribute('class'))); @@ -585,11 +604,18 @@ protected function initialMatchOnElement(SimpleSelector $selector, SplObjectStor $element = '*'; } $found = $this->newMatches(); - /** @var DOMDocument $node */ + /** @var DOMDocument|DOMElement $node */ foreach ($matches as $node) { - // Capture the case where the initial element is the root element. - if ($node->tagName === $element - || ($element === '*' && $node->parentNode instanceof DOMDocument)) { + // Only elements and documents can contain elements. Text, comment, + // CDATA and processing instruction nodes never match, and do not + // support the element-only API used below. + if (! $node instanceof DOMElement && ! $node instanceof DOMDocument) { + continue; + } + + // Capture the case where the node itself matches the element. + if ($node instanceof DOMElement + && ($element === '*' || $node->tagName === $element)) { $found->attach($node); } $nl = $node->getElementsByTagName($element); diff --git a/src/CSS/DOMTraverser/PseudoClass.php b/src/CSS/DOMTraverser/PseudoClass.php index de3bf2c..da11e7c 100644 --- a/src/CSS/DOMTraverser/PseudoClass.php +++ b/src/CSS/DOMTraverser/PseudoClass.php @@ -12,6 +12,7 @@ namespace QueryPath\CSS\DOMTraverser; +use DOMElement; use QueryPath\CSS\DOMTraverser; use QueryPath\CSS\NotImplementedException; use QueryPath\CSS\EventHandler; @@ -46,6 +47,13 @@ class PseudoClass */ public function elementMatches($pseudoclass, $node, $scope, $value = null) { + // Pseudo-classes are only ever satisfied by elements. Text, comment, + // CDATA and processing instruction nodes have no tag name, attributes + // or element children, so they can never match. + if (! $node instanceof DOMElement) { + return false; + } + $name = strtolower($pseudoclass); // Need to handle known pseudoclasses. switch ($name) { @@ -160,6 +168,8 @@ public function elementMatches($pseudoclass, $node, $scope, $value = null) case 'checked': return Util::matchesAttribute($node, $name); case 'text': + return $this->isTextInput($node); + case 'radio': case 'checkbox': case 'file': @@ -226,6 +236,34 @@ protected function lang($node, $value) return false; } + /** + * Provides jQuery pseudoclass ':text'. + * + * This mirrors jQuery, where `:text` selects `input` elements of type text + * -- that is, an `input` whose `type` attribute is either absent (`text` is + * the default type of an `input`) or is `text`, matched case-insensitively. + * + * It does NOT indicate whether the node is a text node. + * + * @param DOMElement $node + * + * @return bool + * @see https://api.jquery.com/text-selector/ + */ + protected function isTextInput($node): bool + { + if (strtolower($node->localName) !== 'input') { + return false; + } + + // An input with no type attribute defaults to a text input. + if (! $node->hasAttribute('type')) { + return true; + } + + return strtolower($node->getAttribute('type')) === 'text'; + } + /** * Provides jQuery pseudoclass ':header'. * diff --git a/src/CSS/DOMTraverser/Util.php b/src/CSS/DOMTraverser/Util.php index c6751bd..18539c9 100644 --- a/src/CSS/DOMTraverser/Util.php +++ b/src/CSS/DOMTraverser/Util.php @@ -7,6 +7,7 @@ namespace QueryPath\CSS\DOMTraverser; +use DOMElement; use QueryPath\CSS\EventHandler; /** @@ -26,6 +27,12 @@ class Util */ public static function matchesAttribute($node, $name, $value = null, $operation = EventHandler::IS_EXACTLY): bool { + // Only elements have attributes. Text, comment, CDATA and processing + // instruction nodes can never match an attribute selector. + if (! $node instanceof DOMElement) { + return false; + } + if (! $node->hasAttribute($name)) { return false; } @@ -47,6 +54,11 @@ public static function matchesAttributeNS( $value = null, $operation = EventHandler::IS_EXACTLY ) { + // Only elements have attributes. + if (! $node instanceof DOMElement) { + return false; + } + if (! $node->hasAttributeNS($nsuri, $name)) { return false; } diff --git a/src/CSS/QueryPathEventHandler.php b/src/CSS/QueryPathEventHandler.php index 43e06e3..037dd41 100644 --- a/src/CSS/QueryPathEventHandler.php +++ b/src/CSS/QueryPathEventHandler.php @@ -352,6 +352,35 @@ public function attribute($name, $value = null, $operation = EventHandler::IS_EX $this->findAnyElement = false; } + /** + * Helper function for the jQuery ':text' pseudo-class. + * + * As in jQuery, ':text' selects `input` elements of type text -- that is, an + * `input` whose `type` attribute is either absent (`text` is the default + * type of an `input`) or is `text`, matched case-insensitively. It does NOT + * indicate whether the node is a text node. + * + * @see https://api.jquery.com/text-selector/ + */ + protected function textInput() + { + $found = new SplObjectStorage(); + $matches = $this->candidateList(); + foreach ($matches as $item) { + if (strtolower($item->localName) !== 'input') { + continue; + } + + // An input with no type attribute defaults to a text input. + if (! $item->hasAttribute('type') || strtolower($item->getAttribute('type')) === 'text') { + $found->attach($item); + } + } + + $this->matches = $found; + $this->findAnyElement = false; + } + /** * Helper function to find all elements with exact matches. * @@ -556,6 +585,8 @@ public function pseudoClass($name, $value = null) $this->attribute($name); break; case 'text': + $this->textInput(); + break; case 'radio': case 'checkbox': case 'file': diff --git a/tests/Issues/Issue49Test.php b/tests/Issues/Issue49Test.php index 2ccabfd..9cf32b7 100644 --- a/tests/Issues/Issue49Test.php +++ b/tests/Issues/Issue49Test.php @@ -2,8 +2,42 @@ namespace QueryPathTests; +use DOMCdataSection; +use DOMComment; +use DOMProcessingInstruction; +use DOMText; + class Issue49Test extends TestCase { + protected const INPUT_HTML = '
' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '
'; + + /** + * Get the ID of every element in the match set. + * + * @param \QueryPath\DOMQuery $query + * + * @return array + */ + protected function ids($query): array + { + $ids = []; + foreach ($query as $item) { + $ids[] = $item->attr('id'); + } + sort($ids); + + return $ids; + } + public function testCheckingForMatchingTextInputs(): void { $q = html5qp('
', 'div'); @@ -28,4 +62,131 @@ public function testCheckingForEmptyTextInputs(): void $textNode = $q->find('div')->contents()->eq(0); $this->assertFalse($textNode->is(':text')); } -} \ No newline at end of file + + /** + * As in jQuery, ':text' matches an `input` whose type is absent or 'text' + * (case-insensitively), and nothing else. + * + * @see https://api.jquery.com/text-selector/ + */ + public function testTextSelectorOnlyMatchesTextInputs(): void + { + $q = html5qp(self::INPUT_HTML, 'div'); + + $this->assertSame(['a', 'b', 'c'], $this->ids($q->find(':text'))); + } + + public function testTextSelectorMatchesTheInputItself(): void + { + $this->assertTrue(html5qp('
', 'input')->is(':text')); + $this->assertTrue(html5qp('
', 'input')->is(':text')); + $this->assertTrue(html5qp('
', 'input')->is(':text')); + + $this->assertFalse(html5qp('
', 'input')->is(':text')); + $this->assertFalse(html5qp('
', 'input')->is(':text')); + $this->assertFalse(html5qp('
', 'textarea')->is(':text')); + $this->assertFalse(html5qp('
', 'button')->is(':text')); + } + + /** + * remove() runs the selector through the legacy CSS engine, which must agree + * with find(). + */ + public function testTextSelectorInTheLegacyEngine(): void + { + $q = html5qp(self::INPUT_HTML, 'div'); + + $this->assertSame(['a', 'b', 'c'], $this->ids($q->remove(':text'))); + $this->assertCount(0, $q->find(':text')); + } + + /** + * Any selector run against a match set holding a text node must return a + * sane result rather than fataling on the element-only DOM API. + */ + public function testSelectorsAgainstATextNodeDoNotThrow(): void + { + $textNode = html5qp('
SampleChild
', 'div') + ->contents() + ->eq(0); + + $this->assertInstanceOf(DOMText::class, $textNode->get(0)); + + $this->assertFalse($textNode->is('*')); + $this->assertFalse($textNode->is('span')); + $this->assertFalse($textNode->is('.wrap')); + $this->assertFalse($textNode->is('#wrap')); + $this->assertFalse($textNode->is('[class]')); + $this->assertFalse($textNode->is('[class="wrap"]')); + $this->assertFalse($textNode->is(':first-child')); + $this->assertFalse($textNode->is('div span')); + + $this->assertCount(0, $textNode->find('*')); + $this->assertCount(0, $textNode->find('span')); + $this->assertCount(0, $textNode->find('.wrap')); + $this->assertCount(0, $textNode->find('#wrap')); + $this->assertCount(0, $textNode->find('[class]')); + $this->assertCount(0, $textNode->filter('*')); + } + + public function testSelectorsAgainstACommentNodeDoNotThrow(): void + { + $comment = html5qp('
Child
', 'div') + ->contents() + ->eq(0); + + $this->assertInstanceOf(DOMComment::class, $comment->get(0)); + + $this->assertFalse($comment->is('*')); + $this->assertFalse($comment->is('span')); + $this->assertFalse($comment->is('.wrap')); + $this->assertFalse($comment->is('#wrap')); + $this->assertFalse($comment->is('[class]')); + $this->assertFalse($comment->is(':text')); + + $this->assertCount(0, $comment->find('*')); + $this->assertCount(0, $comment->find('span')); + $this->assertCount(0, $comment->find('[class]')); + } + + public function testSelectorsAgainstCdataAndProcessingInstructionNodesDoNotThrow(): void + { + $contents = qp( + 'Text', + 'root' + )->contents(); + + $cdata = $contents->eq(0); + $pi = $contents->eq(1); + + $this->assertInstanceOf(DOMCdataSection::class, $cdata->get(0)); + $this->assertInstanceOf(DOMProcessingInstruction::class, $pi->get(0)); + + foreach ([$cdata, $pi] as $node) { + $this->assertFalse($node->is('*')); + $this->assertFalse($node->is('child')); + $this->assertFalse($node->is('.c')); + $this->assertFalse($node->is('#i')); + $this->assertFalse($node->is('[class]')); + + $this->assertCount(0, $node->find('*')); + $this->assertCount(0, $node->find('child')); + } + } + + /** + * A match set mixing elements with non-element nodes must still match the + * elements it holds. + */ + public function testMixedNodeMatchSetStillMatchesItsElements(): void + { + $contents = html5qp('
SampleChild
', 'div')->contents(); + + $this->assertCount(2, $contents); + $this->assertSame(['s'], $this->ids($contents->find('span'))); + $this->assertSame(['s'], $this->ids($contents->find('.x'))); + $this->assertSame(['s'], $this->ids($contents->find('#s'))); + $this->assertSame(['s'], $this->ids($contents->filter('span'))); + $this->assertTrue($contents->is('.x')); + } +} From caa92c825e7e63de9af6656e21614ba19b722b78 Mon Sep 17 00:00:00 2001 From: Jake Jackson Date: Sat, 22 Aug 2026 04:21:22 +1000 Subject: [PATCH 3/5] Test :text against the collection rather than its descendants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The assertion held the
and expected is(':text') to be true, which only worked because is() ran a descendant search. #72 makes is() test the elements in the match set, as jQuery does, so that assertion would flip to false. Rewritten so it does not depend on which semantics are in force: the containment question is asked with has(), which is what it always meant, and is() is asked of the inputs themselves. It passes both with and without #72. Also renamed $textNode to $firstInput in this test. contents()->eq(0) here is the first element, not a text node — the name is accurate in the sibling test below, where the fixture really does hold text. Co-Authored-By: Claude Opus 5 (1M context) --- .phpunit.result.cache | 1 + tests/Issues/Issue49Test.php | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 .phpunit.result.cache diff --git a/.phpunit.result.cache b/.phpunit.result.cache new file mode 100644 index 0000000..49614cc --- /dev/null +++ b/.phpunit.result.cache @@ -0,0 +1 @@ +{"version":1,"defects":[],"times":{"QueryPathTests\\Issue49Test::testCheckingForMatchingTextInputs":0.006,"QueryPathTests\\Issue49Test::testCheckingForEmptyTextInputs":0,"QueryPathTests\\Issue49Test::testTextSelectorOnlyMatchesTextInputs":0.001,"QueryPathTests\\Issue49Test::testTextSelectorMatchesTheInputItself":0.001,"QueryPathTests\\Issue49Test::testTextSelectorInTheLegacyEngine":0.001,"QueryPathTests\\Issue49Test::testSelectorsAgainstATextNodeDoNotThrow":0.001,"QueryPathTests\\Issue49Test::testSelectorsAgainstACommentNodeDoNotThrow":0,"QueryPathTests\\Issue49Test::testSelectorsAgainstCdataAndProcessingInstructionNodesDoNotThrow":0,"QueryPathTests\\Issue49Test::testMixedNodeMatchSetStillMatchesItsElements":0}} \ No newline at end of file diff --git a/tests/Issues/Issue49Test.php b/tests/Issues/Issue49Test.php index 9cf32b7..ceabcdc 100644 --- a/tests/Issues/Issue49Test.php +++ b/tests/Issues/Issue49Test.php @@ -42,12 +42,21 @@ public function testCheckingForMatchingTextInputs(): void { $q = html5qp('
', 'div'); - /* Check if the DOMNode or its children matches */ - $this->assertTrue($q->is(':text')); + /* + * The collection holds the
. It is not itself a text input, but it contains two, + * so the containment question is asked with has() and the matches with find(). + */ + $this->assertCount(1, $q->has(':text')); $this->assertCount(2, $q->find(':text')); - $textNode = $q->find('div')->contents()->eq(0); - $this->assertTrue($textNode->is(':text')); + /* The inputs themselves match: an explicit type="text", and an with no type */ + $this->assertTrue($q->find('input')->is(':text')); + $this->assertTrue($q->find('[name="text1"]')->is(':text')); + $this->assertTrue($q->find('[name="text2"]')->is(':text')); + + /* contents() here holds the two elements, not text nodes */ + $firstInput = $q->find('div')->contents()->eq(0); + $this->assertTrue($firstInput->is(':text')); } public function testCheckingForEmptyTextInputs(): void From 771ab5b38d6667986746eb4c34050881fc858f9a Mon Sep 17 00:00:00 2001 From: Jake Jackson Date: Sat, 22 Aug 2026 04:37:30 +1000 Subject: [PATCH 4/5] Ask find() for descendants and filter() for the set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two assertions reached find() for a node that was already in their own match set, which only worked because find() self-matched. #73 makes find() search descendants only, as jQuery does. Rewritten to ask each question of the method that answers it: find() of a real descendant, filter()/is() of the elements in the set. The mixed-node fixture gains a nested so find() still has something to reach, which keeps the point of the test — that a set holding a text node does not cause a fatal — intact on both sides of the selector. Passes with and without #72/#73. Co-Authored-By: Claude Opus 5 (1M context) --- .phpunit.result.cache | 2 +- tests/Issues/Issue49Test.php | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 49614cc..851153e 100644 --- a/.phpunit.result.cache +++ b/.phpunit.result.cache @@ -1 +1 @@ -{"version":1,"defects":[],"times":{"QueryPathTests\\Issue49Test::testCheckingForMatchingTextInputs":0.006,"QueryPathTests\\Issue49Test::testCheckingForEmptyTextInputs":0,"QueryPathTests\\Issue49Test::testTextSelectorOnlyMatchesTextInputs":0.001,"QueryPathTests\\Issue49Test::testTextSelectorMatchesTheInputItself":0.001,"QueryPathTests\\Issue49Test::testTextSelectorInTheLegacyEngine":0.001,"QueryPathTests\\Issue49Test::testSelectorsAgainstATextNodeDoNotThrow":0.001,"QueryPathTests\\Issue49Test::testSelectorsAgainstACommentNodeDoNotThrow":0,"QueryPathTests\\Issue49Test::testSelectorsAgainstCdataAndProcessingInstructionNodesDoNotThrow":0,"QueryPathTests\\Issue49Test::testMixedNodeMatchSetStillMatchesItsElements":0}} \ No newline at end of file +{"version":1,"defects":{"QueryPathTests\\DOMQueryTest::testFilterLambda":1,"QueryPathTests\\DOMQueryTest::testEachLambda":1},"times":{"QueryPathTests\\Issue49Test::testCheckingForMatchingTextInputs":0.004,"QueryPathTests\\Issue49Test::testCheckingForEmptyTextInputs":0,"QueryPathTests\\Issue49Test::testTextSelectorOnlyMatchesTextInputs":0,"QueryPathTests\\Issue49Test::testTextSelectorMatchesTheInputItself":0.001,"QueryPathTests\\Issue49Test::testTextSelectorInTheLegacyEngine":0.001,"QueryPathTests\\Issue49Test::testSelectorsAgainstATextNodeDoNotThrow":0,"QueryPathTests\\Issue49Test::testSelectorsAgainstACommentNodeDoNotThrow":0,"QueryPathTests\\Issue49Test::testSelectorsAgainstCdataAndProcessingInstructionNodesDoNotThrow":0,"QueryPathTests\\Issue49Test::testMixedNodeMatchSetStillMatchesItsElements":0,"QueryPathTests\\CSS\\DOMTraverserTest::testConstructor":0,"QueryPathTests\\CSS\\DOMTraverserTest::testFind":0,"QueryPathTests\\CSS\\DOMTraverserTest::testMatches":0,"QueryPathTests\\CSS\\DOMTraverserTest::testMatchElement":0.001,"QueryPathTests\\CSS\\DOMTraverserTest::testMatchAttributes":0.002,"QueryPathTests\\CSS\\DOMTraverserTest::testMatchId":0,"QueryPathTests\\CSS\\DOMTraverserTest::testMatchClasses":0,"QueryPathTests\\CSS\\DOMTraverserTest::testMatchPseudoClasses":0,"QueryPathTests\\CSS\\DOMTraverserTest::testMatchPseudoElements":0,"QueryPathTests\\CSS\\DOMTraverserTest::testCombineAdjacent":0,"QueryPathTests\\CSS\\DOMTraverserTest::testCombineSibling":0,"QueryPathTests\\CSS\\DOMTraverserTest::testCombineDirectDescendant":0,"QueryPathTests\\CSS\\DOMTraverserTest::testCombineAnyDescendant":0,"QueryPathTests\\CSS\\DOMTraverserTest::testMultipleSelectors":0,"QueryPathTests\\CSS\\ParserTest::testElementID":0.003,"QueryPathTests\\CSS\\ParserTest::testElement":0,"QueryPathTests\\CSS\\ParserTest::testElementNS":0,"QueryPathTests\\CSS\\ParserTest::testAnyElement":0,"QueryPathTests\\CSS\\ParserTest::testAnyElementInNS":0,"QueryPathTests\\CSS\\ParserTest::testElementClass":0,"QueryPathTests\\CSS\\ParserTest::testPseudoClass":0,"QueryPathTests\\CSS\\ParserTest::testPseudoElement":0,"QueryPathTests\\CSS\\ParserTest::testDirectDescendant":0,"QueryPathTests\\CSS\\ParserTest::testAnyDescendant":0,"QueryPathTests\\CSS\\ParserTest::testAdjacent":0,"QueryPathTests\\CSS\\ParserTest::testSibling":0,"QueryPathTests\\CSS\\ParserTest::testAnotherSelector":0,"QueryPathTests\\CSS\\ParserTest::testIllegalAttribute":0,"QueryPathTests\\CSS\\ParserTest::testAttribute":0.001,"QueryPathTests\\CSS\\ParserTest::testAttributeNS":0,"QueryPathTests\\CSS\\ParserTest::testIllegalCombinators1":0,"QueryPathTests\\CSS\\ParserTest::testIllegalCombinators2":0,"QueryPathTests\\CSS\\ParserTest::testIllegalID":0,"QueryPathTests\\CSS\\ParserTest::testElementNSClassAndAttribute":0,"QueryPathTests\\CSS\\ParserTest::testAllCombo":0,"QueryPathTests\\CSS\\PseudoClassTest::testUnknownPseudoClass":0,"QueryPathTests\\CSS\\PseudoClassTest::testLang":0,"QueryPathTests\\CSS\\PseudoClassTest::testLangNS":0,"QueryPathTests\\CSS\\PseudoClassTest::testFormType":0,"QueryPathTests\\CSS\\PseudoClassTest::testHasAttribute":0,"QueryPathTests\\CSS\\PseudoClassTest::testHeader":0,"QueryPathTests\\CSS\\PseudoClassTest::testContains":0,"QueryPathTests\\CSS\\PseudoClassTest::testContainsExactly":0,"QueryPathTests\\CSS\\PseudoClassTest::testHas":0,"QueryPathTests\\CSS\\PseudoClassTest::testParent":0,"QueryPathTests\\CSS\\PseudoClassTest::testFirst":0,"QueryPathTests\\CSS\\PseudoClassTest::testLast":0,"QueryPathTests\\CSS\\PseudoClassTest::testNot":0,"QueryPathTests\\CSS\\PseudoClassTest::testEmpty":0,"QueryPathTests\\CSS\\PseudoClassTest::testOnlyChild":0,"QueryPathTests\\CSS\\PseudoClassTest::testLastOfType":0,"QueryPathTests\\CSS\\PseudoClassTest::testFirstOftype":0,"QueryPathTests\\CSS\\PseudoClassTest::testOnlyOfType":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthLastChild":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #0":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #1":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #2":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #3":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #4":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #5":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #6":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #7":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #8":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #9":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #10":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #11":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #12":0,"QueryPathTests\\CSS\\PseudoClassTest::testEven":0,"QueryPathTests\\CSS\\PseudoClassTest::testOdd":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthOfTypeChild":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthLastOfTypeChild":0,"QueryPathTests\\CSS\\PseudoClassTest::testLink":0,"QueryPathTests\\CSS\\PseudoClassTest::testRoot":0,"QueryPathTests\\CSS\\PseudoClassTest::testLt":0,"QueryPathTests\\CSS\\PseudoClassTest::testGt":0,"QueryPathTests\\CSS\\PseudoClassTest::testEq":0,"QueryPathTests\\CSS\\PseudoClassTest::testAnyLink":0,"QueryPathTests\\CSS\\PseudoClassTest::testLocalLink":0,"QueryPathTests\\CSS\\PseudoClassTest::testScope":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testGetMatches":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testEmptySelector":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testElementNS":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testFailedElementNS":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testElement":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testElementId":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testAnyElementInNS":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testAnyElement":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testElementClass":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testDirectDescendant":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testAttribute":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassLang":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassEnabledDisabledChecked":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassLink":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassXReset":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassRoot":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #0":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #1":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #2":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #3":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #4":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #5":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #6":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #7":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #8":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #9":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #10":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #11":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #12":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #13":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #14":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #15":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #16":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #17":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChildNested":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassOnlyChild":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassOnlyOfType":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassFirstChild":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassLastChild":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthLastChild":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassFirstOfType":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthFirstOfType":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassLastOfType":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoNthClassLastOfType":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassEmpty":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassFirst":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassLast":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassGT":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassLT":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNTH":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthOfType":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassFormElements":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassHeader":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassContains":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassContainsExactly":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassHas":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNot":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoElement":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testAdjacent":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testAnotherSelector":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testSibling":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testAnyDescendant":0,"QueryPathTests\\CSS\\SelectorTest::testElement":0,"QueryPathTests\\CSS\\SelectorTest::testElementNS":0,"QueryPathTests\\CSS\\SelectorTest::testId":0,"QueryPathTests\\CSS\\SelectorTest::testClasses":0,"QueryPathTests\\CSS\\SelectorTest::testAttributes":0,"QueryPathTests\\CSS\\SelectorTest::testAttributesNS":0,"QueryPathTests\\CSS\\SelectorTest::testPseudoClasses":0,"QueryPathTests\\CSS\\SelectorTest::testPseudoElements":0,"QueryPathTests\\CSS\\SelectorTest::testCombinators":0,"QueryPathTests\\CSS\\SelectorTest::testIterator":0,"QueryPathTests\\CSS\\TokenTest::testName":0,"QueryPathTests\\CSS\\UtilTest::testRemoveQuotes":0,"QueryPathTests\\CSS\\UtilTest::testParseAnB":0,"QueryPathTests\\DOMQueryTest::testDOMQueryConstructors":0,"QueryPathTests\\DOMQueryTest::testDOMQueryHtmlConstructors":0,"QueryPathTests\\DOMQueryTest::testHtml5":0.001,"QueryPathTests\\DOMQueryTest::testInnerHtml5":0,"QueryPathTests\\DOMQueryTest::testOptionXMLEncoding":0,"QueryPathTests\\DOMQueryTest::testQPAbstractFactory":0,"QueryPathTests\\DOMQueryTest::testQPAbstractFactoryIterating":0,"QueryPathTests\\DOMQueryTest::testFailedCall":0,"QueryPathTests\\DOMQueryTest::testFailedObjectConstruction":0,"QueryPathTests\\DOMQueryTest::testFailedHTTPLoad":0.001,"QueryPathTests\\DOMQueryTest::testFailedHTTPLoadWithContext":0,"QueryPathTests\\DOMQueryTest::testFailedParseHTMLElement":0,"QueryPathTests\\DOMQueryTest::testFailedParseXMLElement":0,"QueryPathTests\\DOMQueryTest::testIgnoreParserWarnings":0,"QueryPathTests\\DOMQueryTest::testFailedParseNonMarkup":0,"QueryPathTests\\DOMQueryTest::testFailedParseEntity":0,"QueryPathTests\\DOMQueryTest::testReplaceEntitiesOption":0,"QueryPathTests\\DOMQueryTest::testFind":0,"QueryPathTests\\DOMQueryTest::testFindInPlace":0,"QueryPathTests\\DOMQueryTest::testTop":0,"QueryPathTests\\DOMQueryTest::testAttr":0,"QueryPathTests\\DOMQueryTest::testHasAttr":0,"QueryPathTests\\DOMQueryTest::testVal":0,"QueryPathTests\\DOMQueryTest::testCss":0,"QueryPathTests\\DOMQueryTest::testRemoveAttr":0,"QueryPathTests\\DOMQueryTest::testEq":0,"QueryPathTests\\DOMQueryTest::testIs":0,"QueryPathTests\\DOMQueryTest::testIndex":0,"QueryPathTests\\DOMQueryTest::testFilter":0,"QueryPathTests\\DOMQueryTest::testFilterPreg":0,"QueryPathTests\\DOMQueryTest::testFilterLambda":0,"QueryPathTests\\DOMQueryTest::testFilterCallback":0,"QueryPathTests\\DOMQueryTest::testFailedFilterCallback":0,"QueryPathTests\\DOMQueryTest::testFailedMapCallback":0,"QueryPathTests\\DOMQueryTest::testNot":0,"QueryPathTests\\DOMQueryTest::testSlice":0,"QueryPathTests\\DOMQueryTest::testMap":0,"QueryPathTests\\DOMQueryTest::testEach":0,"QueryPathTests\\DOMQueryTest::testEachOnInvalidCallback":0,"QueryPathTests\\DOMQueryTest::testEachLambda":0,"QueryPathTests\\DOMQueryTest::testDeepest":0,"QueryPathTests\\DOMQueryTest::testTag":0,"QueryPathTests\\DOMQueryTest::testAppend":0.001,"QueryPathTests\\DOMQueryTest::testAppendBadMarkup":0,"QueryPathTests\\DOMQueryTest::testAppendBadObject":0,"QueryPathTests\\DOMQueryTest::testAppendTo":0,"QueryPathTests\\DOMQueryTest::testPrepend":0,"QueryPathTests\\DOMQueryTest::testPrependTo":0,"QueryPathTests\\DOMQueryTest::testBefore":0,"QueryPathTests\\DOMQueryTest::testAfter":0,"QueryPathTests\\DOMQueryTest::testInsertBefore":0,"QueryPathTests\\DOMQueryTest::testInsertAfter":0,"QueryPathTests\\DOMQueryTest::testReplaceWith":0,"QueryPathTests\\DOMQueryTest::testReplaceAll":0,"QueryPathTests\\DOMQueryTest::testUnwrap":0,"QueryPathTests\\DOMQueryTest::testFailedUnwrap":0,"QueryPathTests\\DOMQueryTest::testWrap":0.001,"QueryPathTests\\DOMQueryTest::testWrapAll":0.001,"QueryPathTests\\DOMQueryTest::testWrapInner":0,"QueryPathTests\\DOMQueryTest::testRemove":0,"QueryPathTests\\DOMQueryTest::testHasClass":0,"QueryPathTests\\DOMQueryTest::testAddClass":0,"QueryPathTests\\DOMQueryTest::testRemoveClass":0,"QueryPathTests\\DOMQueryTest::testAdd":0,"QueryPathTests\\DOMQueryTest::testEnd":0,"QueryPathTests\\DOMQueryTest::testAndSelf":0,"QueryPathTests\\DOMQueryTest::testChildren":0,"QueryPathTests\\DOMQueryTest::testRemoveChildren":0,"QueryPathTests\\DOMQueryTest::testContents":0,"QueryPathTests\\DOMQueryTest::testNS":0,"QueryPathTests\\DOMQueryTest::testSiblings":0,"QueryPathTests\\DOMQueryTest::testHTML":0.001,"QueryPathTests\\DOMQueryTest::testInnerHTML":0,"QueryPathTests\\DOMQueryTest::testInnerXML":0,"QueryPathTests\\DOMQueryTest::testInnerXHTML":0,"QueryPathTests\\DOMQueryTest::testXML":0,"QueryPathTests\\DOMQueryTest::testXHTML":0,"QueryPathTests\\DOMQueryTest::testWriteXML":0.001,"QueryPathTests\\DOMQueryTest::testWriteXHTML":0,"QueryPathTests\\DOMQueryTest::testFailWriteXML":0,"QueryPathTests\\DOMQueryTest::testFailWriteXHTML":0,"QueryPathTests\\DOMQueryTest::testFailWriteHTML":0,"QueryPathTests\\DOMQueryTest::testWriteHTML":0,"QueryPathTests\\DOMQueryTest::testText":0,"QueryPathTests\\DOMQueryTest::testTextAfter":0,"QueryPathTests\\DOMQueryTest::testTextBefore":0,"QueryPathTests\\DOMQueryTest::testTextImplode":0,"QueryPathTests\\DOMQueryTest::testChildrenText":0,"QueryPathTests\\DOMQueryTest::testNext":0,"QueryPathTests\\DOMQueryTest::testPrev":0,"QueryPathTests\\DOMQueryTest::testNextAll":0,"QueryPathTests\\DOMQueryTest::testPrevAll":0,"QueryPathTests\\DOMQueryTest::testParent":0,"QueryPathTests\\DOMQueryTest::testClosest":0,"QueryPathTests\\DOMQueryTest::testParents":0,"QueryPathTests\\DOMQueryTest::testCloneAll":0,"QueryPathTests\\DOMQueryTest::testBranch":0,"QueryPathTests\\DOMQueryTest::testXpath":0,"QueryPathTests\\DOMQueryTest::test__clone":0,"QueryPathTests\\DOMQueryTest::testStub":0,"QueryPathTests\\DOMQueryTest::testIterator":0,"QueryPathTests\\DOMQueryTest::testModeratelySizedDocument":0.002,"QueryPathTests\\DOMQueryTest::testSize":0,"QueryPathTests\\DOMQueryTest::testCount":0,"QueryPathTests\\DOMQueryTest::testLength":0,"QueryPathTests\\DOMQueryTest::testDocument":0,"QueryPathTests\\DOMQueryTest::testDetach":0,"QueryPathTests\\DOMQueryTest::testAttach":0,"QueryPathTests\\DOMQueryTest::testEmptyElement":0,"QueryPathTests\\DOMQueryTest::testHas":0,"QueryPathTests\\DOMQueryTest::testNextUntil":0,"QueryPathTests\\DOMQueryTest::testPrevUntil":0,"QueryPathTests\\DOMQueryTest::testEven":0,"QueryPathTests\\DOMQueryTest::testOdd":0,"QueryPathTests\\DOMQueryTest::testFirst":0,"QueryPathTests\\DOMQueryTest::testFirstChild":0,"QueryPathTests\\DOMQueryTest::testLast":0,"QueryPathTests\\DOMQueryTest::testLastChild":0,"QueryPathTests\\DOMQueryTest::testParentsUntil":0,"QueryPathTests\\DOMQueryTest::testSort":0,"QueryPathTests\\DOMQueryTest::testRegressionFindOptimizations":0,"QueryPathTests\\DOMQueryTest::testDataURL":0,"QueryPathTests\\DOMQueryTest::testEncodeDataURL":0,"QueryPathTests\\EntitiesTest::testReplaceEntity":0,"QueryPathTests\\EntitiesTest::testReplaceAllEntities":0,"QueryPathTests\\EntitiesTest::testReplaceHexEntities":0,"QueryPathTests\\EntitiesTest::testQPEntityReplacement":0,"QueryPathTests\\ExamplesTest::testExampleRunsCleanly with data set \"basic-docx-parser\"":0.075,"QueryPathTests\\ExamplesTest::testExampleRunsCleanly with data set \"basic-manipulation-filter-and-retrieval\"":0.046,"QueryPathTests\\ExamplesTest::testExampleRunsCleanly with data set \"basic-odt-parser\"":0.05,"QueryPathTests\\ExamplesTest::testExampleRunsCleanly with data set \"create-html-document\"":0.05,"QueryPathTests\\ExamplesTest::testExampleRunsCleanly with data set \"create-svg-document\"":0.05,"QueryPathTests\\ExamplesTest::testExampleRunsCleanly with data set \"create-xml-document\"":0.048,"QueryPathTests\\ExamplesTest::testExampleRunsCleanly with data set \"generating-rss-feed\"":0.05,"QueryPathTests\\ExamplesTest::testExampleRunsCleanly with data set \"hello-world\"":0.05,"QueryPathTests\\ExamplesTest::testExampleRunsCleanly with data set \"iterating-over-matches\"":0.05,"QueryPathTests\\ExamplesTest::testExampleRunsCleanly with data set \"parsing-php-source\"":0.05,"QueryPathTests\\ExamplesTest::testNetworkExampleListIsAccurate":0,"QueryPathTests\\ExamplesTest::testEveryExampleDirectoryIsRunnable":0,"QueryPathTests\\Extension\\FormatTest::it_formats_tag_text_node":0,"QueryPathTests\\Extension\\FormatTest::it_formats_attribute":0,"QueryPathTests\\Extension\\QPXMLTest::testCDATA":0,"QueryPathTests\\Extension\\QPXMLTest::testComment":0,"QueryPathTests\\Extension\\QPXMLTest::testProcessingInstruction":0,"QueryPathTests\\Extension\\QPXSLTest::testXSLT":0,"QueryPathTests\\ExtensionTest::testExtensions":0,"QueryPathTests\\ExtensionTest::testHasExtension":0,"QueryPathTests\\ExtensionTest::testStubToe":0,"QueryPathTests\\ExtensionTest::testStuble":0,"QueryPathTests\\ExtensionTest::testNoRegistry":0,"QueryPathTests\\ExtensionTest::testExtend":0,"QueryPathTests\\ExtensionTest::testAutoloadExtensions":0,"QueryPathTests\\ExtensionTest::testCallFailure":0,"QueryPathTests\\OptionsTest::testOptions":0,"QueryPathTests\\OptionsTest::testQPOverrideOrder":0,"QueryPathTests\\OptionsTest::testQPHas":0,"QueryPathTests\\OptionsTest::testQPMerge":0,"QueryPathTests\\QueryPathIteratorTest::testCurrent":0,"QueryPathTests\\QueryPathTest::testWith":0,"QueryPathTests\\QueryPathTest::testWithHTML":0,"QueryPathTests\\QueryPathTest::testWithHTML5":0,"QueryPathTests\\QueryPathTest::testWithXML":0,"QueryPathTests\\QueryPathTest::testEnable":0,"QueryPathTests\\XMLIshTest::testXMLishMock":0,"QueryPathTests\\XMLIshTest::testXMLishWithBrokenHTML":0}} \ No newline at end of file diff --git a/tests/Issues/Issue49Test.php b/tests/Issues/Issue49Test.php index ceabcdc..fcc5750 100644 --- a/tests/Issues/Issue49Test.php +++ b/tests/Issues/Issue49Test.php @@ -55,7 +55,7 @@ public function testCheckingForMatchingTextInputs(): void $this->assertTrue($q->find('[name="text2"]')->is(':text')); /* contents() here holds the two elements, not text nodes */ - $firstInput = $q->find('div')->contents()->eq(0); + $firstInput = $q->contents()->eq(0); $this->assertTrue($firstInput->is(':text')); } @@ -189,13 +189,20 @@ public function testSelectorsAgainstCdataAndProcessingInstructionNodesDoNotThrow */ public function testMixedNodeMatchSetStillMatchesItsElements(): void { - $contents = html5qp('
SampleChild
', 'div')->contents(); + $contents = html5qp('
SampleChild
', 'div') + ->contents(); + /* The set holds a text node and an element; neither may cause a fatal. */ $this->assertCount(2, $contents); - $this->assertSame(['s'], $this->ids($contents->find('span'))); - $this->assertSame(['s'], $this->ids($contents->find('.x'))); - $this->assertSame(['s'], $this->ids($contents->find('#s'))); + + /* find() reaches the descendants of the elements in the set */ + $this->assertSame(['e'], $this->ids($contents->find('em'))); + $this->assertSame(['e'], $this->ids($contents->find('#e'))); + + /* filter() and is() ask about the elements in the set itself */ $this->assertSame(['s'], $this->ids($contents->filter('span'))); + $this->assertSame(['s'], $this->ids($contents->filter('.x'))); + $this->assertSame(['s'], $this->ids($contents->filter('#s'))); $this->assertTrue($contents->is('.x')); } } From e23694e1e2003649e3c098df06a574cf57bc343e Mon Sep 17 00:00:00 2001 From: Jake Jackson Date: Sat, 22 Aug 2026 04:56:16 +1000 Subject: [PATCH 5/5] Share the :text rule between the engines, and tidy the diff MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rule — an input whose type is absent or case-insensitively "text" — was spelled out once per engine. The two are meant to agree, which is why this PR has a test asserting they do; sharing the definition is what actually keeps them agreeing. Util is the established home for this: 4.1.0 moved parseAnB() there for the same reason. Also in this commit, none of it behavioural: - Drop .phpunit.result.cache, which was committed despite being in .gitignore. - Restore the six blank lines the diff had stripped from released CHANGELOG sections. All five open PRs edit that file, so unrelated whitespace churn in it buys four conflicts for nothing. - Record the find('*') self-match change in the CHANGELOG. It was needed to make is(':text') work on an element under the current is(), but it is a behaviour change that was going in unmentioned, and #73 supersedes it. Co-Authored-By: Claude Opus 5 (1M context) --- .phpunit.result.cache | 1 - CHANGELOG.md | 9 +++++++++ src/CSS/DOMTraverser/PseudoClass.php | 11 +---------- src/CSS/DOMTraverser/Util.php | 23 +++++++++++++++++++++++ src/CSS/QueryPathEventHandler.php | 7 +------ 5 files changed, 34 insertions(+), 17 deletions(-) delete mode 100644 .phpunit.result.cache diff --git a/.phpunit.result.cache b/.phpunit.result.cache deleted file mode 100644 index 851153e..0000000 --- a/.phpunit.result.cache +++ /dev/null @@ -1 +0,0 @@ -{"version":1,"defects":{"QueryPathTests\\DOMQueryTest::testFilterLambda":1,"QueryPathTests\\DOMQueryTest::testEachLambda":1},"times":{"QueryPathTests\\Issue49Test::testCheckingForMatchingTextInputs":0.004,"QueryPathTests\\Issue49Test::testCheckingForEmptyTextInputs":0,"QueryPathTests\\Issue49Test::testTextSelectorOnlyMatchesTextInputs":0,"QueryPathTests\\Issue49Test::testTextSelectorMatchesTheInputItself":0.001,"QueryPathTests\\Issue49Test::testTextSelectorInTheLegacyEngine":0.001,"QueryPathTests\\Issue49Test::testSelectorsAgainstATextNodeDoNotThrow":0,"QueryPathTests\\Issue49Test::testSelectorsAgainstACommentNodeDoNotThrow":0,"QueryPathTests\\Issue49Test::testSelectorsAgainstCdataAndProcessingInstructionNodesDoNotThrow":0,"QueryPathTests\\Issue49Test::testMixedNodeMatchSetStillMatchesItsElements":0,"QueryPathTests\\CSS\\DOMTraverserTest::testConstructor":0,"QueryPathTests\\CSS\\DOMTraverserTest::testFind":0,"QueryPathTests\\CSS\\DOMTraverserTest::testMatches":0,"QueryPathTests\\CSS\\DOMTraverserTest::testMatchElement":0.001,"QueryPathTests\\CSS\\DOMTraverserTest::testMatchAttributes":0.002,"QueryPathTests\\CSS\\DOMTraverserTest::testMatchId":0,"QueryPathTests\\CSS\\DOMTraverserTest::testMatchClasses":0,"QueryPathTests\\CSS\\DOMTraverserTest::testMatchPseudoClasses":0,"QueryPathTests\\CSS\\DOMTraverserTest::testMatchPseudoElements":0,"QueryPathTests\\CSS\\DOMTraverserTest::testCombineAdjacent":0,"QueryPathTests\\CSS\\DOMTraverserTest::testCombineSibling":0,"QueryPathTests\\CSS\\DOMTraverserTest::testCombineDirectDescendant":0,"QueryPathTests\\CSS\\DOMTraverserTest::testCombineAnyDescendant":0,"QueryPathTests\\CSS\\DOMTraverserTest::testMultipleSelectors":0,"QueryPathTests\\CSS\\ParserTest::testElementID":0.003,"QueryPathTests\\CSS\\ParserTest::testElement":0,"QueryPathTests\\CSS\\ParserTest::testElementNS":0,"QueryPathTests\\CSS\\ParserTest::testAnyElement":0,"QueryPathTests\\CSS\\ParserTest::testAnyElementInNS":0,"QueryPathTests\\CSS\\ParserTest::testElementClass":0,"QueryPathTests\\CSS\\ParserTest::testPseudoClass":0,"QueryPathTests\\CSS\\ParserTest::testPseudoElement":0,"QueryPathTests\\CSS\\ParserTest::testDirectDescendant":0,"QueryPathTests\\CSS\\ParserTest::testAnyDescendant":0,"QueryPathTests\\CSS\\ParserTest::testAdjacent":0,"QueryPathTests\\CSS\\ParserTest::testSibling":0,"QueryPathTests\\CSS\\ParserTest::testAnotherSelector":0,"QueryPathTests\\CSS\\ParserTest::testIllegalAttribute":0,"QueryPathTests\\CSS\\ParserTest::testAttribute":0.001,"QueryPathTests\\CSS\\ParserTest::testAttributeNS":0,"QueryPathTests\\CSS\\ParserTest::testIllegalCombinators1":0,"QueryPathTests\\CSS\\ParserTest::testIllegalCombinators2":0,"QueryPathTests\\CSS\\ParserTest::testIllegalID":0,"QueryPathTests\\CSS\\ParserTest::testElementNSClassAndAttribute":0,"QueryPathTests\\CSS\\ParserTest::testAllCombo":0,"QueryPathTests\\CSS\\PseudoClassTest::testUnknownPseudoClass":0,"QueryPathTests\\CSS\\PseudoClassTest::testLang":0,"QueryPathTests\\CSS\\PseudoClassTest::testLangNS":0,"QueryPathTests\\CSS\\PseudoClassTest::testFormType":0,"QueryPathTests\\CSS\\PseudoClassTest::testHasAttribute":0,"QueryPathTests\\CSS\\PseudoClassTest::testHeader":0,"QueryPathTests\\CSS\\PseudoClassTest::testContains":0,"QueryPathTests\\CSS\\PseudoClassTest::testContainsExactly":0,"QueryPathTests\\CSS\\PseudoClassTest::testHas":0,"QueryPathTests\\CSS\\PseudoClassTest::testParent":0,"QueryPathTests\\CSS\\PseudoClassTest::testFirst":0,"QueryPathTests\\CSS\\PseudoClassTest::testLast":0,"QueryPathTests\\CSS\\PseudoClassTest::testNot":0,"QueryPathTests\\CSS\\PseudoClassTest::testEmpty":0,"QueryPathTests\\CSS\\PseudoClassTest::testOnlyChild":0,"QueryPathTests\\CSS\\PseudoClassTest::testLastOfType":0,"QueryPathTests\\CSS\\PseudoClassTest::testFirstOftype":0,"QueryPathTests\\CSS\\PseudoClassTest::testOnlyOfType":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthLastChild":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #0":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #1":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #2":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #3":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #4":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #5":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #6":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #7":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #8":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #9":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #10":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #11":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthChild with data set #12":0,"QueryPathTests\\CSS\\PseudoClassTest::testEven":0,"QueryPathTests\\CSS\\PseudoClassTest::testOdd":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthOfTypeChild":0,"QueryPathTests\\CSS\\PseudoClassTest::testNthLastOfTypeChild":0,"QueryPathTests\\CSS\\PseudoClassTest::testLink":0,"QueryPathTests\\CSS\\PseudoClassTest::testRoot":0,"QueryPathTests\\CSS\\PseudoClassTest::testLt":0,"QueryPathTests\\CSS\\PseudoClassTest::testGt":0,"QueryPathTests\\CSS\\PseudoClassTest::testEq":0,"QueryPathTests\\CSS\\PseudoClassTest::testAnyLink":0,"QueryPathTests\\CSS\\PseudoClassTest::testLocalLink":0,"QueryPathTests\\CSS\\PseudoClassTest::testScope":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testGetMatches":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testEmptySelector":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testElementNS":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testFailedElementNS":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testElement":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testElementId":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testAnyElementInNS":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testAnyElement":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testElementClass":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testDirectDescendant":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testAttribute":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassLang":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassEnabledDisabledChecked":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassLink":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassXReset":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassRoot":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #0":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #1":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #2":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #3":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #4":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #5":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #6":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #7":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #8":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #9":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #10":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #11":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #12":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #13":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #14":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #15":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #16":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChild with data set #17":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthChildNested":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassOnlyChild":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassOnlyOfType":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassFirstChild":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassLastChild":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthLastChild":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassFirstOfType":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthFirstOfType":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassLastOfType":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoNthClassLastOfType":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassEmpty":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassFirst":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassLast":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassGT":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassLT":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNTH":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNthOfType":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassFormElements":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassHeader":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassContains":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassContainsExactly":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassHas":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoClassNot":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testPseudoElement":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testAdjacent":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testAnotherSelector":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testSibling":0,"QueryPathTests\\CSS\\QueryPathEventHandlerTest::testAnyDescendant":0,"QueryPathTests\\CSS\\SelectorTest::testElement":0,"QueryPathTests\\CSS\\SelectorTest::testElementNS":0,"QueryPathTests\\CSS\\SelectorTest::testId":0,"QueryPathTests\\CSS\\SelectorTest::testClasses":0,"QueryPathTests\\CSS\\SelectorTest::testAttributes":0,"QueryPathTests\\CSS\\SelectorTest::testAttributesNS":0,"QueryPathTests\\CSS\\SelectorTest::testPseudoClasses":0,"QueryPathTests\\CSS\\SelectorTest::testPseudoElements":0,"QueryPathTests\\CSS\\SelectorTest::testCombinators":0,"QueryPathTests\\CSS\\SelectorTest::testIterator":0,"QueryPathTests\\CSS\\TokenTest::testName":0,"QueryPathTests\\CSS\\UtilTest::testRemoveQuotes":0,"QueryPathTests\\CSS\\UtilTest::testParseAnB":0,"QueryPathTests\\DOMQueryTest::testDOMQueryConstructors":0,"QueryPathTests\\DOMQueryTest::testDOMQueryHtmlConstructors":0,"QueryPathTests\\DOMQueryTest::testHtml5":0.001,"QueryPathTests\\DOMQueryTest::testInnerHtml5":0,"QueryPathTests\\DOMQueryTest::testOptionXMLEncoding":0,"QueryPathTests\\DOMQueryTest::testQPAbstractFactory":0,"QueryPathTests\\DOMQueryTest::testQPAbstractFactoryIterating":0,"QueryPathTests\\DOMQueryTest::testFailedCall":0,"QueryPathTests\\DOMQueryTest::testFailedObjectConstruction":0,"QueryPathTests\\DOMQueryTest::testFailedHTTPLoad":0.001,"QueryPathTests\\DOMQueryTest::testFailedHTTPLoadWithContext":0,"QueryPathTests\\DOMQueryTest::testFailedParseHTMLElement":0,"QueryPathTests\\DOMQueryTest::testFailedParseXMLElement":0,"QueryPathTests\\DOMQueryTest::testIgnoreParserWarnings":0,"QueryPathTests\\DOMQueryTest::testFailedParseNonMarkup":0,"QueryPathTests\\DOMQueryTest::testFailedParseEntity":0,"QueryPathTests\\DOMQueryTest::testReplaceEntitiesOption":0,"QueryPathTests\\DOMQueryTest::testFind":0,"QueryPathTests\\DOMQueryTest::testFindInPlace":0,"QueryPathTests\\DOMQueryTest::testTop":0,"QueryPathTests\\DOMQueryTest::testAttr":0,"QueryPathTests\\DOMQueryTest::testHasAttr":0,"QueryPathTests\\DOMQueryTest::testVal":0,"QueryPathTests\\DOMQueryTest::testCss":0,"QueryPathTests\\DOMQueryTest::testRemoveAttr":0,"QueryPathTests\\DOMQueryTest::testEq":0,"QueryPathTests\\DOMQueryTest::testIs":0,"QueryPathTests\\DOMQueryTest::testIndex":0,"QueryPathTests\\DOMQueryTest::testFilter":0,"QueryPathTests\\DOMQueryTest::testFilterPreg":0,"QueryPathTests\\DOMQueryTest::testFilterLambda":0,"QueryPathTests\\DOMQueryTest::testFilterCallback":0,"QueryPathTests\\DOMQueryTest::testFailedFilterCallback":0,"QueryPathTests\\DOMQueryTest::testFailedMapCallback":0,"QueryPathTests\\DOMQueryTest::testNot":0,"QueryPathTests\\DOMQueryTest::testSlice":0,"QueryPathTests\\DOMQueryTest::testMap":0,"QueryPathTests\\DOMQueryTest::testEach":0,"QueryPathTests\\DOMQueryTest::testEachOnInvalidCallback":0,"QueryPathTests\\DOMQueryTest::testEachLambda":0,"QueryPathTests\\DOMQueryTest::testDeepest":0,"QueryPathTests\\DOMQueryTest::testTag":0,"QueryPathTests\\DOMQueryTest::testAppend":0.001,"QueryPathTests\\DOMQueryTest::testAppendBadMarkup":0,"QueryPathTests\\DOMQueryTest::testAppendBadObject":0,"QueryPathTests\\DOMQueryTest::testAppendTo":0,"QueryPathTests\\DOMQueryTest::testPrepend":0,"QueryPathTests\\DOMQueryTest::testPrependTo":0,"QueryPathTests\\DOMQueryTest::testBefore":0,"QueryPathTests\\DOMQueryTest::testAfter":0,"QueryPathTests\\DOMQueryTest::testInsertBefore":0,"QueryPathTests\\DOMQueryTest::testInsertAfter":0,"QueryPathTests\\DOMQueryTest::testReplaceWith":0,"QueryPathTests\\DOMQueryTest::testReplaceAll":0,"QueryPathTests\\DOMQueryTest::testUnwrap":0,"QueryPathTests\\DOMQueryTest::testFailedUnwrap":0,"QueryPathTests\\DOMQueryTest::testWrap":0.001,"QueryPathTests\\DOMQueryTest::testWrapAll":0.001,"QueryPathTests\\DOMQueryTest::testWrapInner":0,"QueryPathTests\\DOMQueryTest::testRemove":0,"QueryPathTests\\DOMQueryTest::testHasClass":0,"QueryPathTests\\DOMQueryTest::testAddClass":0,"QueryPathTests\\DOMQueryTest::testRemoveClass":0,"QueryPathTests\\DOMQueryTest::testAdd":0,"QueryPathTests\\DOMQueryTest::testEnd":0,"QueryPathTests\\DOMQueryTest::testAndSelf":0,"QueryPathTests\\DOMQueryTest::testChildren":0,"QueryPathTests\\DOMQueryTest::testRemoveChildren":0,"QueryPathTests\\DOMQueryTest::testContents":0,"QueryPathTests\\DOMQueryTest::testNS":0,"QueryPathTests\\DOMQueryTest::testSiblings":0,"QueryPathTests\\DOMQueryTest::testHTML":0.001,"QueryPathTests\\DOMQueryTest::testInnerHTML":0,"QueryPathTests\\DOMQueryTest::testInnerXML":0,"QueryPathTests\\DOMQueryTest::testInnerXHTML":0,"QueryPathTests\\DOMQueryTest::testXML":0,"QueryPathTests\\DOMQueryTest::testXHTML":0,"QueryPathTests\\DOMQueryTest::testWriteXML":0.001,"QueryPathTests\\DOMQueryTest::testWriteXHTML":0,"QueryPathTests\\DOMQueryTest::testFailWriteXML":0,"QueryPathTests\\DOMQueryTest::testFailWriteXHTML":0,"QueryPathTests\\DOMQueryTest::testFailWriteHTML":0,"QueryPathTests\\DOMQueryTest::testWriteHTML":0,"QueryPathTests\\DOMQueryTest::testText":0,"QueryPathTests\\DOMQueryTest::testTextAfter":0,"QueryPathTests\\DOMQueryTest::testTextBefore":0,"QueryPathTests\\DOMQueryTest::testTextImplode":0,"QueryPathTests\\DOMQueryTest::testChildrenText":0,"QueryPathTests\\DOMQueryTest::testNext":0,"QueryPathTests\\DOMQueryTest::testPrev":0,"QueryPathTests\\DOMQueryTest::testNextAll":0,"QueryPathTests\\DOMQueryTest::testPrevAll":0,"QueryPathTests\\DOMQueryTest::testParent":0,"QueryPathTests\\DOMQueryTest::testClosest":0,"QueryPathTests\\DOMQueryTest::testParents":0,"QueryPathTests\\DOMQueryTest::testCloneAll":0,"QueryPathTests\\DOMQueryTest::testBranch":0,"QueryPathTests\\DOMQueryTest::testXpath":0,"QueryPathTests\\DOMQueryTest::test__clone":0,"QueryPathTests\\DOMQueryTest::testStub":0,"QueryPathTests\\DOMQueryTest::testIterator":0,"QueryPathTests\\DOMQueryTest::testModeratelySizedDocument":0.002,"QueryPathTests\\DOMQueryTest::testSize":0,"QueryPathTests\\DOMQueryTest::testCount":0,"QueryPathTests\\DOMQueryTest::testLength":0,"QueryPathTests\\DOMQueryTest::testDocument":0,"QueryPathTests\\DOMQueryTest::testDetach":0,"QueryPathTests\\DOMQueryTest::testAttach":0,"QueryPathTests\\DOMQueryTest::testEmptyElement":0,"QueryPathTests\\DOMQueryTest::testHas":0,"QueryPathTests\\DOMQueryTest::testNextUntil":0,"QueryPathTests\\DOMQueryTest::testPrevUntil":0,"QueryPathTests\\DOMQueryTest::testEven":0,"QueryPathTests\\DOMQueryTest::testOdd":0,"QueryPathTests\\DOMQueryTest::testFirst":0,"QueryPathTests\\DOMQueryTest::testFirstChild":0,"QueryPathTests\\DOMQueryTest::testLast":0,"QueryPathTests\\DOMQueryTest::testLastChild":0,"QueryPathTests\\DOMQueryTest::testParentsUntil":0,"QueryPathTests\\DOMQueryTest::testSort":0,"QueryPathTests\\DOMQueryTest::testRegressionFindOptimizations":0,"QueryPathTests\\DOMQueryTest::testDataURL":0,"QueryPathTests\\DOMQueryTest::testEncodeDataURL":0,"QueryPathTests\\EntitiesTest::testReplaceEntity":0,"QueryPathTests\\EntitiesTest::testReplaceAllEntities":0,"QueryPathTests\\EntitiesTest::testReplaceHexEntities":0,"QueryPathTests\\EntitiesTest::testQPEntityReplacement":0,"QueryPathTests\\ExamplesTest::testExampleRunsCleanly with data set \"basic-docx-parser\"":0.075,"QueryPathTests\\ExamplesTest::testExampleRunsCleanly with data set \"basic-manipulation-filter-and-retrieval\"":0.046,"QueryPathTests\\ExamplesTest::testExampleRunsCleanly with data set \"basic-odt-parser\"":0.05,"QueryPathTests\\ExamplesTest::testExampleRunsCleanly with data set \"create-html-document\"":0.05,"QueryPathTests\\ExamplesTest::testExampleRunsCleanly with data set \"create-svg-document\"":0.05,"QueryPathTests\\ExamplesTest::testExampleRunsCleanly with data set \"create-xml-document\"":0.048,"QueryPathTests\\ExamplesTest::testExampleRunsCleanly with data set \"generating-rss-feed\"":0.05,"QueryPathTests\\ExamplesTest::testExampleRunsCleanly with data set \"hello-world\"":0.05,"QueryPathTests\\ExamplesTest::testExampleRunsCleanly with data set \"iterating-over-matches\"":0.05,"QueryPathTests\\ExamplesTest::testExampleRunsCleanly with data set \"parsing-php-source\"":0.05,"QueryPathTests\\ExamplesTest::testNetworkExampleListIsAccurate":0,"QueryPathTests\\ExamplesTest::testEveryExampleDirectoryIsRunnable":0,"QueryPathTests\\Extension\\FormatTest::it_formats_tag_text_node":0,"QueryPathTests\\Extension\\FormatTest::it_formats_attribute":0,"QueryPathTests\\Extension\\QPXMLTest::testCDATA":0,"QueryPathTests\\Extension\\QPXMLTest::testComment":0,"QueryPathTests\\Extension\\QPXMLTest::testProcessingInstruction":0,"QueryPathTests\\Extension\\QPXSLTest::testXSLT":0,"QueryPathTests\\ExtensionTest::testExtensions":0,"QueryPathTests\\ExtensionTest::testHasExtension":0,"QueryPathTests\\ExtensionTest::testStubToe":0,"QueryPathTests\\ExtensionTest::testStuble":0,"QueryPathTests\\ExtensionTest::testNoRegistry":0,"QueryPathTests\\ExtensionTest::testExtend":0,"QueryPathTests\\ExtensionTest::testAutoloadExtensions":0,"QueryPathTests\\ExtensionTest::testCallFailure":0,"QueryPathTests\\OptionsTest::testOptions":0,"QueryPathTests\\OptionsTest::testQPOverrideOrder":0,"QueryPathTests\\OptionsTest::testQPHas":0,"QueryPathTests\\OptionsTest::testQPMerge":0,"QueryPathTests\\QueryPathIteratorTest::testCurrent":0,"QueryPathTests\\QueryPathTest::testWith":0,"QueryPathTests\\QueryPathTest::testWithHTML":0,"QueryPathTests\\QueryPathTest::testWithHTML5":0,"QueryPathTests\\QueryPathTest::testWithXML":0,"QueryPathTests\\QueryPathTest::testEnable":0,"QueryPathTests\\XMLIshTest::testXMLishMock":0,"QueryPathTests\\XMLIshTest::testXMLishWithBrokenHTML":0}} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 18aaf08..6c97f97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ QueryPath Changelog or processing instruction). Those nodes now simply do not match, instead of calling element-only DOM methods on them. - Fix the `:text` pseudo-class so it matches jQuery: it selects `input` elements whose `type` attribute is absent or is `text` (case-insensitively). It never indicated, and still does not indicate, whether a node is a text node. +- `find('*')` now matches the nodes in the match set as well as their descendants, so that a selector can be tested + against an element already in hand. Note that #73 replaces this with jQuery's descendant-only `find()`; this entry + is provisional and should be dropped if that lands first. - Reorganise, modernise, and repair the `examples/` directory. Each example now lives in its own subdirectory with an `index.php`, and the full set is indexed in `examples/quickstart-guide.md` - Convert the remaining legacy examples: `simple_example.php`, `techniques.php`, `svg.php`, `rss.php`, `odt.php`, `parse_php.php`, and `sparql.php` - Fix examples that no longer ran: send a `User-Agent` where remote hosts now require one, resolve paths relative to the example rather than the working directory, and stop relying on the removed `qp.php` autoloader and the PHP 8 incompatible `eachLambda()` @@ -16,17 +19,23 @@ QueryPath Changelog - Add `QueryPathTests\ExamplesTest`, which runs every offline example on each supported PHP version and fails if one stops working. The examples that call third-party services are run by the new `Examples` workflow, weekly and whenever an example changes - Rewrite the cURL example against the PubMed E-utilities API. MusicBrainz throttles by IP address, which made the example unusable from any shared address - Add `composer run test:examples` (and `test:examples:network`) to run the examples locally + # 4.1.0 + - 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 + - Only define global functions qp(), htmlqp(), and html5qp() if they haven't been defined already. - Fix for :nth-child(n+B) to select B-th and all following elements - Fix for :nth-child(-n+B) to select first B elements - Update PHPUnit Test Suite to use @dataProvider in testPseudoClassNthChild() to reduce code repetition - Fix error when getting parents() for HTML elements + # 4.0.0 + - Reverse logic in DomQuery::html5() so that DomQuery::html5() returns the content of the current match, and DomQuery::html5('') replaces the content of the current matches. This matches the existing logic used in DomQuery::html(). - Return DOMQuery object if QueryMutators::wrapAll() has no matches (instead of null). This aligns the method with the Docblock return type. diff --git a/src/CSS/DOMTraverser/PseudoClass.php b/src/CSS/DOMTraverser/PseudoClass.php index ee7e480..2812c9b 100644 --- a/src/CSS/DOMTraverser/PseudoClass.php +++ b/src/CSS/DOMTraverser/PseudoClass.php @@ -252,16 +252,7 @@ protected function lang($node, $value) */ protected function isTextInput($node): bool { - if (strtolower($node->localName) !== 'input') { - return false; - } - - // An input with no type attribute defaults to a text input. - if (! $node->hasAttribute('type')) { - return true; - } - - return strtolower($node->getAttribute('type')) === 'text'; + return Util::isTextInput($node); } /** diff --git a/src/CSS/DOMTraverser/Util.php b/src/CSS/DOMTraverser/Util.php index 455bf83..ba4cf00 100644 --- a/src/CSS/DOMTraverser/Util.php +++ b/src/CSS/DOMTraverser/Util.php @@ -177,4 +177,27 @@ public static function parseAnB($rule): array return [$aVal, $bVal]; } + + /** + * Does this node match jQuery's :text pseudo-class? + * + * jQuery's :text selects input elements whose type attribute is absent, or is "text" + * regardless of case. It says nothing about whether a node is a text node. + * + * Both selector engines ask this question, so they share one answer — they are meant to + * agree, and two copies of the rule would be free to drift apart. + * + * @param mixed $node + * + * @return bool + */ + public static function isTextInput($node): bool + { + if (! $node instanceof DOMElement || strtolower($node->localName) !== 'input') { + return false; + } + + // An input with no type attribute defaults to a text input. + return ! $node->hasAttribute('type') || strtolower($node->getAttribute('type')) === 'text'; + } } diff --git a/src/CSS/QueryPathEventHandler.php b/src/CSS/QueryPathEventHandler.php index b92f93f..b8d13d7 100644 --- a/src/CSS/QueryPathEventHandler.php +++ b/src/CSS/QueryPathEventHandler.php @@ -368,12 +368,7 @@ protected function textInput() $found = new SplObjectStorage(); $matches = $this->candidateList(); foreach ($matches as $item) { - if (strtolower($item->localName) !== 'input') { - continue; - } - - // An input with no type attribute defaults to a text input. - if (! $item->hasAttribute('type') || strtolower($item->getAttribute('type')) === 'text') { + if (Util::isTextInput($item)) { $found->offsetSet($item); } }