forked from WordPress/wordpress-develop
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Split up main CSS selector class and support more restricted selectors in the tag processor.
- Loading branch information
Showing
15 changed files
with
520 additions
and
162 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
165 changes: 165 additions & 0 deletions
165
src/wp-includes/html-api/class-wp-css-complex-selector-list.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
<?php | ||
/** | ||
* HTML API: WP_CSS_Complex_Selector_List class | ||
* | ||
* @package WordPress | ||
* @subpackage HTML-API | ||
* @since TBD | ||
*/ | ||
|
||
/** | ||
* Core class used by the {@see WP_HTML_Processor} to parse and match CSS selectors. | ||
* | ||
* This class is designed for internal use by the HTML processor. | ||
* | ||
* For usage, see {@see WP_HTML_Processor::select()} or {@see WP_HTML_Processor::select_all()}. | ||
* | ||
* This class is instantiated via the {@see WP_CSS_Complex_Selector_List::from_selectors()} method. | ||
* It takes a CSS selector string and returns an instance of itself or `null` if the selector | ||
* is invalid or unsupported. | ||
* | ||
* A subset of the CSS selector grammar is supported. The grammar is defined in the CSS Syntax | ||
* specification, which is available at {@link https://www.w3.org/TR/selectors/#grammar}. | ||
* | ||
* This class is rougly analogous to the <selector-list> in the grammar. See {@see WP_CSS_Compound_Selector_List} for more details on the grammar. | ||
* | ||
* This class supports the same selector syntax as {@see WP_CSS_Compound_Selector_List} as well as: | ||
* - The following combinators: | ||
* - Next sibling (`el + el`) | ||
* - Subsequent sibling (`el ~ el`) | ||
* | ||
* @since TBD | ||
* | ||
* @access private | ||
*/ | ||
class WP_CSS_Complex_Selector_List extends WP_CSS_Compound_Selector_List implements WP_CSS_HTML_Processor_Matcher { | ||
/** | ||
* Takes a CSS selector string and returns an instance of itself or `null` if the selector | ||
* string is invalid or unsupported. | ||
* | ||
* @since TBD | ||
* | ||
* @param string $input CSS selectors. | ||
* @return static|null | ||
*/ | ||
public static function from_selectors( string $input ) { | ||
// > A selector string is a list of one or more complex selectors ([SELECTORS4], section 3.1) that may be surrounded by whitespace… | ||
$input = trim( $input, " \t\r\n\r" ); | ||
|
||
if ( '' === $input ) { | ||
return null; | ||
} | ||
|
||
/* | ||
* > The input stream consists of the filtered code points pushed into it as the input byte stream is decoded. | ||
* > | ||
* > To filter code points from a stream of (unfiltered) code points input: | ||
* > Replace any U+000D CARRIAGE RETURN (CR) code points, U+000C FORM FEED (FF) code points, or pairs of U+000D CARRIAGE RETURN (CR) followed by U+000A LINE FEED (LF) in input by a single U+000A LINE FEED (LF) code point. | ||
* > Replace any U+0000 NULL or surrogate code points in input with U+FFFD REPLACEMENT CHARACTER (�). | ||
* | ||
* https://www.w3.org/TR/css-syntax-3/#input-preprocessing | ||
*/ | ||
$input = str_replace( array( "\r\n" ), "\n", $input ); | ||
$input = str_replace( array( "\r", "\f" ), "\n", $input ); | ||
$input = str_replace( "\0", "\u{FFFD}", $input ); | ||
|
||
$offset = 0; | ||
|
||
$selector = self::parse_complex_selector( $input, $offset ); | ||
if ( null === $selector ) { | ||
return null; | ||
} | ||
self::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; | ||
self::parse_whitespace( $input, $offset ); | ||
$selector = self::parse_complex_selector( $input, $offset ); | ||
if ( null === $selector ) { | ||
return null; | ||
} | ||
$selectors[] = $selector; | ||
self::parse_whitespace( $input, $offset ); | ||
} | ||
|
||
return new self( $selectors ); | ||
} | ||
|
||
/* | ||
* ------------------------------ | ||
* Selector parsing functionality | ||
* ------------------------------ | ||
*/ | ||
|
||
/** | ||
* Parses a complex selector. | ||
* | ||
* > <complex-selector> = [ <type-selector> <combinator>? ]* <compound-selector> | ||
* | ||
* @return WP_CSS_Complex_Selector|null | ||
*/ | ||
final protected static function parse_complex_selector( string $input, int &$offset ): ?WP_CSS_Complex_Selector { | ||
if ( $offset >= strlen( $input ) ) { | ||
return null; | ||
} | ||
|
||
$updated_offset = $offset; | ||
$selector = self::parse_compound_selector( $input, $updated_offset ); | ||
if ( null === $selector ) { | ||
return null; | ||
} | ||
|
||
$selectors = array( $selector ); | ||
$has_preceding_subclass_selector = null !== $selector->subclass_selectors; | ||
|
||
$found_whitespace = self::parse_whitespace( $input, $updated_offset ); | ||
while ( $updated_offset < strlen( $input ) ) { | ||
if ( | ||
WP_CSS_Complex_Selector::COMBINATOR_CHILD === $input[ $updated_offset ] || | ||
WP_CSS_Complex_Selector::COMBINATOR_NEXT_SIBLING === $input[ $updated_offset ] || | ||
WP_CSS_Complex_Selector::COMBINATOR_SUBSEQUENT_SIBLING === $input[ $updated_offset ] | ||
) { | ||
$combinator = $input[ $updated_offset ]; | ||
++$updated_offset; | ||
self::parse_whitespace( $input, $updated_offset ); | ||
|
||
// Failure to find a selector here is a parse error | ||
$selector = self::parse_compound_selector( $input, $updated_offset ); | ||
} elseif ( $found_whitespace ) { | ||
/* | ||
* Whitespace is ambiguous, it could be a descendant combinator or | ||
* insignificant whitespace. | ||
*/ | ||
$selector = self::parse_compound_selector( $input, $updated_offset ); | ||
if ( null === $selector ) { | ||
break; | ||
} | ||
$combinator = WP_CSS_Complex_Selector::COMBINATOR_DESCENDANT; | ||
} else { | ||
break; | ||
} | ||
|
||
if ( null === $selector ) { | ||
return null; | ||
} | ||
|
||
// `div > .className` is valid, but `.className > div` is not. | ||
if ( $has_preceding_subclass_selector ) { | ||
throw new Exception( 'Unsupported non-final subclass selector.' ); | ||
} | ||
$has_preceding_subclass_selector = null !== $selector->subclass_selectors; | ||
|
||
$selectors[] = $combinator; | ||
$selectors[] = $selector; | ||
|
||
$found_whitespace = self::parse_whitespace( $input, $updated_offset ); | ||
} | ||
$offset = $updated_offset; | ||
return new WP_CSS_Complex_Selector( $selectors ); | ||
} | ||
} |
Oops, something went wrong.