Skip to content

Commit

Permalink
Move class constant into Tag Processor, match with it, and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
dmsnell committed Nov 16, 2023
1 parent 415ba5b commit e8c4704
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 22 deletions.
19 changes: 4 additions & 15 deletions src/wp-includes/html-api/class-wp-html-open-elements.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public function current_node() {
*
* @see https://html.spec.whatwg.org/#has-an-element-in-the-specific-scope
*
* @param string $tag_name Name of tag check, or the class constant HEADING_ELEMENTS to specify H1-H6.
* @param string $tag_name Name of tag check, or WP_HTML_Tag_Processor::H1_H6_ELEMENTS for any H1 - H6.
* @param string[] $termination_list List of elements that terminate the search.
* @return bool Whether the element was found in a specific scope.
*/
Expand All @@ -117,7 +117,7 @@ public function has_element_in_specific_scope( $tag_name, $termination_list ) {
}

if (
self::HEADING_ELEMENTS === $tag_name &&
WP_HTML_Tag_Processor::H1_H6_ELEMENTS === $tag_name &&
in_array( $node->node_name, array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), true )
) {
return true;
Expand Down Expand Up @@ -271,15 +271,15 @@ public function pop() {
* @see WP_HTML_Open_Elements::pop
*
* @param string $tag_name Name of tag that needs to be popped off of the stack of open elements,
* or the class constant HEADING_ELEMENTS to specify any of H1-H6.
* or WP_HTML_Tag_Processor::H1_H6_ELEMENTS to specify any of H1 - H6.
* @return bool Whether a tag of the given name was found and popped off of the stack of open elements.
*/
public function pop_until( $tag_name ) {
foreach ( $this->walk_up() as $item ) {
$this->pop();

if (
self::HEADING_ELEMENTS === $tag_name &&
WP_HTML_Tag_Processor::H1_H6_ELEMENTS === $tag_name &&
in_array( $item->node_name, array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), true )
) {
return true;
Expand Down Expand Up @@ -444,15 +444,4 @@ public function after_element_pop( $item ) {
break;
}
}

/**
* Represents the collection of H1-H6 elements.
*
* @since 6.5.0
*
* @see has_element_in_scope()
*
* @var string
*/
const HEADING_ELEMENTS = 'heading-elements';
}
8 changes: 2 additions & 6 deletions src/wp-includes/html-api/class-wp-html-processor.php
Original file line number Diff line number Diff line change
Expand Up @@ -694,11 +694,7 @@ private function step_in_body() {
case '-H4':
case '-H5':
case '-H6':
if (
! $this->state->stack_of_open_elements->has_element_in_scope(
WP_HTML_Open_Elements::HEADING_ELEMENTS
)
) {
if ( ! $this->state->stack_of_open_elements->has_element_in_scope( WP_HTML_Tag_Processor::H1_H6_ELEMENTS ) ) {
/*
* This is a parse error; ignore the token.
*
Expand All @@ -713,7 +709,7 @@ private function step_in_body() {
// @TODO: Record parse error: this error doesn't impact parsing.
}

$this->state->stack_of_open_elements->pop_until( WP_HTML_Open_Elements::HEADING_ELEMENTS );
$this->state->stack_of_open_elements->pop_until( WP_HTML_Tag_Processor::H1_H6_ELEMENTS );
return true;

/*
Expand Down
39 changes: 38 additions & 1 deletion src/wp-includes/html-api/class-wp-html-tag-processor.php
Original file line number Diff line number Diff line change
Expand Up @@ -2401,6 +2401,7 @@ private function parse_query( $query ) {
* Checks whether a given tag and its attributes match the search criteria.
*
* @since 6.2.0
* @since 6.5.0 Allows matching any of the H1 - H6 tag names with the WP_HTML_Tag_Processor::H1_H6_ELEMENTS constant.
*
* @return bool Whether the given tag and its attribute match the search criteria.
*/
Expand All @@ -2409,8 +2410,31 @@ private function matches() {
return false;
}

Check failure on line 2412 in src/wp-includes/html-api/class-wp-html-tag-processor.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Functions must not contain multiple empty lines in a row; found 2 empty lines

// Does the tag name match the requested tag name in a case-insensitive manner?
if ( null !== $this->sought_tag_name ) {
if ( self::H1_H6_ELEMENTS === $this->sought_tag_name ) {
/*
* H1 through H6 are special because they act like one tag
* name but are distinct. It's common enough to want to stop
* at any of them without knowing in advance which one to
* look for; the class constant aids this by representing
* the entire set of six elements: H1, H2, H3, H4, H5, H6.
*/

if ( 2 !== $this->tag_name_length ) {
return false;
}

$c = $this->html[ $this->tag_name_starts_at ];
if ( 'h' !== $c && 'H' !== $c ) {
return false;
}

$c = $this->html[ $this->tag_name_starts_at + 1 ];
if ( '1' !== $c && '2' !== $c && '3' !== $c && '4' !== $c && '5' !== $c && '6' !== $c ) {
return false;
}
} elseif ( null !== $this->sought_tag_name ) {
/*
* String (byte) length lookup is fast. If they aren't the
* same length then they can't be the same string values.
Expand Down Expand Up @@ -2447,4 +2471,17 @@ private function matches() {

return true;
}

// Class constants that would otherwise be distracting if found at the top of the document.

/**
* Represents the collection of H1-H6 elements.
*
* @since 6.5.0
*
* @see has_element_in_scope()
*
* @var string
*/
const H1_H6_ELEMENTS = 'Match on any of H1, H2, H3, H4, H5, H6';
}
71 changes: 71 additions & 0 deletions tests/phpunit/tests/html-api/wpHtmlTagProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,77 @@ public function test_next_tag_matches_decoded_class_names() {
$this->assertTrue( $p->next_tag( array( 'class_name' => '<egg>' ) ), 'Failed to find tag with HTML-encoded class name.' );
}

/**
* Ensures that the H1_H6_ELEMENTS constant leads to matches for H1 through H6 elements.
*
* @ticket {TICKET_NUMBER}
*
* @covers WP_HTML_Tag_Processor::next_tag
*
* @dataProvider data_h_tag_names
*
* @param string $h_tag_name One of H1 through H6, case-insensitive.
*/
public function test_next_tag_matches_h1_through_h6_with_the_class_constant( $h_tag_name ) {
$p = new WP_HTML_Tag_Processor( "<div><img><{$h_tag_name}></div>" );

$this->assertTrue( $p->next_tag( WP_HTML_Tag_Processor::H1_H6_ELEMENTS ), "Failed to find {$h_tag_name} tag opener." );
}

/**
* Data provider.
*
* @return array[]
*/
public function data_h_tag_names() {
return array(
'H1' => array( 'H1' ),
'H2' => array( 'H2' ),
'H3' => array( 'H3' ),
'H4' => array( 'H4' ),
'H5' => array( 'H5' ),
'H6' => array( 'H6' ),
);
}

/**
* Ensures that the H1_H6_ELEMENTS constant doesn't lead to matches on
* tag names that look similar to H1 - H6 but aren't those elements.
*
* @ticket {TICKET_NUMBER}
*
* @covers WP_HTML_Tag_Processor::next_tag
*
* @dataProvider data_invalid_h_tag_names
*
* @param string $invalid_h_tag_name Tag names that look like H1 through H6 but are not those tag names.
*/
public function test_next_tag_does_not_match_invalid_h_elements_with_the_class_constant( $invalid_h_tag_name ) {
$p = new WP_HTML_Tag_Processor( "<div><img><{$invalid_h_tag_name}></div>" );

$this->assertFalse( $p->next_tag( WP_HTML_Tag_Processor::H1_H6_ELEMENTS ), "Found {$p->get_tag()} when looking for {$invalid_h_tag_name} element and should have found nothing." );
}

/**
* Data provider.
*
* @return array[].
*/
public function data_invalid_h_tag_names() {
return array(
'H0' => array( 'H0' ),
'H7' => array( 'H7' ),
'H13' => array( 'H13' ),

/*
* Preserve the FULLWIDTH DIGIT SIX key because PHPUnit interprets '6' as
* a numeric array item and reports "data set 0" instead of "6".
*/
'' => array( '6' ),
'H4-CUSTOM' => array( 'H4-CUSTOM' ),
);
}

/**
* @ticket 56299
* @ticket 57852
Expand Down

0 comments on commit e8c4704

Please sign in to comment.