diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index 3b2efce1c955e..e3100abc698bb 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -623,6 +623,35 @@ public function get_breadcrumbs() { return $breadcrumbs; } + /** + * Returns the nesting depth of the current location in the document. + * + * Example: + * + * $processor = WP_HTML_Processor::create_fragment( '

' ); + * // The processor starts in the BODY context, meaning it has depth from the start: HTML > BODY. + * 2 === $processor->get_current_depth(); + * + * // Opening the DIV element increases the depth. + * $processor->next_token(); + * 3 === $processor->get_current_depth(); + * + * // Opening the P element increases the depth. + * $processor->next_token(); + * 4 === $processor->get_current_depth(); + * + * // The P element is closed during `next_token()` so the depth is decreased to reflect that. + * $processor->next_token(); + * 3 === $processor->get_current_depth(); + * + * @since 6.6.0 + * + * @return int Nesting-depth of current location in the document. + */ + public function get_current_depth() { + return $this->state->stack_of_open_elements->count(); + } + /** * Parses next element in the 'in body' insertion mode. * diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessor.php b/tests/phpunit/tests/html-api/wpHtmlProcessor.php index 76022a9ce60d3..13c2d7b58eac6 100644 --- a/tests/phpunit/tests/html-api/wpHtmlProcessor.php +++ b/tests/phpunit/tests/html-api/wpHtmlProcessor.php @@ -334,4 +334,90 @@ public static function data_unsupported_special_in_body_tags() { 'XMP' => array( 'XMP' ), ); } + + /** + * Ensures that the HTML Processor properly reports the depth of a given element. + * + * @ticket 61255 + * + * @dataProvider data_html_with_target_element_and_depth_in_body + * + * @param string $html_with_target_element HTML containing element with `target` class. + * @param int $depth_at_element Depth into document at target node. + */ + public function test_reports_proper_element_depth_in_body( $html_with_target_element, $depth_at_element ) { + $processor = WP_HTML_Processor::create_fragment( $html_with_target_element ); + + $this->assertTrue( + $processor->next_tag( array( 'class_name' => 'target' ) ), + 'Failed to find target element: check test data provider.' + ); + + $this->assertSame( + $depth_at_element, + $processor->get_current_depth(), + 'HTML Processor reported the wrong depth at the matched element.' + ); + } + + /** + * Data provider. + * + * @return array[]. + */ + public static function data_html_with_target_element_and_depth_in_body() { + return array( + 'Single element' => array( '
', 3 ), + 'Basic layout and formatting stack' => array( '

', 7 ), + 'Adjacent elements' => array( '

', 4 ), + ); + } + + /** + * Ensures that the HTML Processor properly reports the depth of a given non-element. + * + * @ticket 61255 + * + * @dataProvider data_html_with_target_element_and_depth_of_next_node_in_body + * + * @param string $html_with_target_element HTML containing element with `target` class. + * @param int $depth_after_element Depth into document immediately after target node. + */ + public function test_reports_proper_non_element_depth_in_body( $html_with_target_element, $depth_after_element ) { + $processor = WP_HTML_Processor::create_fragment( $html_with_target_element ); + + $this->assertTrue( + $processor->next_tag( array( 'class_name' => 'target' ) ), + 'Failed to find target element: check test data provider.' + ); + + $this->assertTrue( + $processor->next_token(), + 'Failed to find next node after target element: check tests data provider.' + ); + + $this->assertSame( + $depth_after_element, + $processor->get_current_depth(), + 'HTML Processor reported the wrong depth after the matched element.' + ); + } + + /** + * Data provider. + * + * @return array[]. + */ + public static function data_html_with_target_element_and_depth_of_next_node_in_body() { + return array( + 'Element then text' => array( '
One Deeper', 4 ), + 'Basic layout and formatting stack' => array( '

Formatted', 8 ), + 'Basic layout with text' => array( '

ab

cee', 8 ), + 'Adjacent elements' => array( '

Here
', 5 ), + 'Adjacent text' => array( '

BeforeAfter

', 4 ), + 'HTML comment' => array( '', 3 ), + 'HTML comment in DIV' => array( '
', 4 ), + 'Funky comment' => array( '

What

', 5 ), + ); + } }