From 370539beb3a08e6fca8380c8f78b86ba35c7f86b Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Wed, 27 Nov 2024 22:39:22 +0100 Subject: [PATCH] Working and tested --- .../html-api/class-wp-css-selectors.php | 83 +++++++++++-------- .../phpunit/tests/html-api/wpCssSelectors.php | 67 ++++++++++++++- 2 files changed, 113 insertions(+), 37 deletions(-) diff --git a/src/wp-includes/html-api/class-wp-css-selectors.php b/src/wp-includes/html-api/class-wp-css-selectors.php index 8ccec5de029cc..734c3e38d094b 100644 --- a/src/wp-includes/html-api/class-wp-css-selectors.php +++ b/src/wp-includes/html-api/class-wp-css-selectors.php @@ -117,21 +117,31 @@ private static function parse( string $input ) { $input = str_replace( array( "\r", "\f" ), "\n", $input ); $input = str_replace( "\0", "\u{FFFD}", $input ); - $length = strlen( $input ); - $selectors = array(); - $offset = 0; - while ( $offset < $length ) { - $selector = WP_CSS_ID_Selector::parse( $input, $offset ); - if ( null !== $selector ) { - $selectors[] = $selector; - } + $selector = WP_CSS_Complex_Selector::parse( $input, $offset ); + if ( null === $selector ) { + return null; } - if ( count( $selectors ) ) { - return new WP_CSS_Selector_List( $selectors ); + WP_CSS_Selector_Parser::parse_whitespace( $input, $offset ); + + $selectors = array( $selector ); + while ( $offset < strlen( $input ) ) { + // Each loop should stop on a `,` selector list delimiter. + if ( ',' !== $input[ $offset ] ) { + return null; + } + ++$offset; + WP_CSS_Selector_Parser::parse_whitespace( $input, $offset ); + $selector = WP_CSS_Complex_Selector::parse( $input, $offset ); + if ( null === $selector ) { + return null; + } + $selectors[] = $selector; + WP_CSS_Selector_Parser::parse_whitespace( $input, $offset ); } - return null; + + return new WP_CSS_Selector_List( $selectors ); } } @@ -145,7 +155,7 @@ public static function parse( string $input, int &$offset ); abstract class WP_CSS_Selector_Parser implements IWP_CSS_Selector_Parser { const UTF8_MAX_CODEPOINT_VALUE = 0x10FFFF; - protected static function parse_whitespace( string $input, int &$offset ): bool { + public static function parse_whitespace( string $input, int &$offset ): bool { $length = strspn( $input, " \t\r\n\f", $offset ); $advanced = $length > 0; $offset += $length; @@ -938,35 +948,38 @@ public static function parse( string $input, int &$offset ): ?self { $found_whitespace = self::parse_whitespace( $input, $updated_offset ); while ( $updated_offset < strlen( $input ) ) { - switch ( $input[ $updated_offset ] ) { - case self::COMBINATOR_CHILD: - case self::COMBINATOR_NEXT_SIBLING: - case self::COMBINATOR_SUBSEQUENT_SIBLING: + if ( + self::COMBINATOR_CHILD === $input[ $updated_offset ] || + self::COMBINATOR_NEXT_SIBLING === $input[ $updated_offset ] || + self::COMBINATOR_SUBSEQUENT_SIBLING === $input[ $updated_offset ] + ) { $combinator = $input[ $updated_offset ]; ++$updated_offset; self::parse_whitespace( $input, $updated_offset ); - break; - default: - /* - * Whitespace is a descendant combinator. - * Either whitespace was found and we're on a selector, - * or we've failed to find any combinator and parsing is complete. - */ - if ( ! $found_whitespace ) { - break 2; - } - $combinator = self::COMBINATOR_DESCENDANT; + // Failure to find a selector here is a parse error + $selector = WP_CSS_Selector::parse( $input, $updated_offset ); + // Failure to find a selector is a parse error. + if ( null === $selector ) { + return null; + } + $selectors[] = $combinator; + $selectors[] = $selector; + } elseif ( ! $found_whitespace ) { + break; + } else { + + /* + * Whitespace is ambiguous, it could be a descendant combinator or + * insignificant whitespace. + */ + $selector = WP_CSS_Selector::parse( $input, $updated_offset ); + if ( null === $selector ) { break; + } + $selectors[] = self::COMBINATOR_DESCENDANT; + $selectors[] = $selector; } - // Here we've found a combinator and need another selector. - $selector = WP_CSS_Selector::parse( $input, $updated_offset ); - // Failure to find a selector is a parse error. - if ( null === $selector ) { - return null; - } - $selectors[] = $combinator; - $selectors[] = $selector; $found_whitespace = self::parse_whitespace( $input, $updated_offset ); } $offset = $updated_offset; diff --git a/tests/phpunit/tests/html-api/wpCssSelectors.php b/tests/phpunit/tests/html-api/wpCssSelectors.php index 4189ec586011a..33ada4ccbe3f9 100644 --- a/tests/phpunit/tests/html-api/wpCssSelectors.php +++ b/tests/phpunit/tests/html-api/wpCssSelectors.php @@ -357,15 +357,24 @@ public function test_parse_selector() { $this->assertSame( ' > .child', substr( $input, $offset ) ); } + /** + * @ticket TBD + */ + public function test_parse_empty_selector() { + $input = ''; + $offset = 0; + $result = WP_CSS_Selector::parse( $input, $offset ); + $this->assertNull( $result ); + } + /** * @ticket TBD */ public function test_parse_complex_selector() { - $input = 'el.foo#bar[baz=quux] > .child, rest'; + $input = 'el.foo#bar[baz=quux] > .child , rest'; $offset = 0; $sel = WP_CSS_Complex_Selector::parse( $input, $offset ); - var_dump( $sel ); $this->assertSame( 3, count( $sel->selectors ) ); $this->assertNotNull( $sel->selectors[0]->type_selector ); $this->assertSame( 3, count( $sel->selectors[0]->subclass_selectors ) ); @@ -376,4 +385,58 @@ public function test_parse_complex_selector() { $this->assertSame( ', rest', substr( $input, $offset ) ); } + + /** + * @ticket TBD + */ + public function test_parse_invalid_complex_selector() { + $input = 'el.foo#bar[baz=quux] > , rest'; + $offset = 0; + $result = WP_CSS_Complex_Selector::parse( $input, $offset ); + $this->assertNull( $result ); + } + + public function test_parse_empty_complex_selector() { + $input = ''; + $offset = 0; + $result = WP_CSS_Complex_Selector::parse( $input, $offset ); + $this->assertNull( $result ); + } + + + /** + * @ticket TBD + */ + public function test_parse_selector_list() { + $input = 'el.foo#bar[baz=quux] .descendent , rest'; + $result = WP_CSS_Selector_List::from_selectors( $input ); + $this->assertNotNull( $result ); + } + + /** + * @ticket TBD + */ + public function test_parse_invalid_selector_list() { + $input = 'el,,'; + $result = WP_CSS_Selector_List::from_selectors( $input ); + $this->assertNull( $result ); + } + + /** + * @ticket TBD + */ + public function test_parse_invalid_selector_list2() { + $input = 'el!'; + $result = WP_CSS_Selector_List::from_selectors( $input ); + $this->assertNull( $result ); + } + + /** + * @ticket TBD + */ + public function test_parse_empty_selector_list() { + $input = " \t \t\n\r\f"; + $result = WP_CSS_Selector_List::from_selectors( $input ); + $this->assertNull( $result ); + } }