forked from WordPress/wordpress-develop
-
Notifications
You must be signed in to change notification settings - Fork 0
CSS API #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
sirreal
wants to merge
46
commits into
trunk
Choose a base branch
from
css-api/add-css-token-processor
base: trunk
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
CSS API #33
Changes from all commits
Commits
Show all changes
46 commits
Select commit
Hold shift + click to select a range
8dc9446
Add css token processor
sirreal cc0ddbf
phpcbf
sirreal f7d3940
Fix missing methods
sirreal c514409
Add test group
sirreal 8d5cb76
rename test file
sirreal 8f1edc8
Fix css test path
sirreal cb470bf
Include css token processor
sirreal 552c2d5
Add create_css_string
sirreal 9940a5b
Update test
sirreal 2415bf8
Sync string escapes with GB PR
sirreal 906d826
Merge branch 'trunk' into css-api/add-css-token-processor
sirreal c07634a
Fix example phpdoc spacing
sirreal b7a302f
Adjust some documentation
sirreal ab67840
Try improving token types
sirreal 2e8767e
Add CSS builder class
sirreal 272a747
Font: add and use normalize_css_font_face_font_family method
sirreal 0abb128
Add css builder tests
sirreal 8d7b59b
Harmonize with https://github.com/WordPress/gutenberg/pull/76782 tests
sirreal 65f91e1
Use printed Unicode replacement character
sirreal da70697
Add CSS string token round-trip tests
sirreal 8ee9122
try webdriver/qunit tests
sirreal 9b70c59
Fix switch continue
sirreal f26fc68
Revert "try webdriver/qunit tests"
sirreal d3f231d
Merge branch 'trunk' into css-api/add-css-token-processor
sirreal 2e58814
Fix variable name typo
sirreal dd9c108
Update tests for quoted @font-face font-family
sirreal cc6bf38
Add method test suite
sirreal b920d7d
More nice test cases
sirreal c83aa4e
Cleanup function
sirreal c2da39f
On normalization failure, stringify entire input as CSS
sirreal 2c3c2f9
Trim CSS whitespace
sirreal da0311f
Update existing test for behavioral change
sirreal 4f0cf3b
Add more test cases
sirreal 52fb57c
Use css string builder in maybe_add_quotes
sirreal 9d0be2e
DROPME: Add dev files
sirreal 22d7a39
Scaffold CSS processor
sirreal c8a0ee2
DROPME: update dev files
sirreal 10b4ead
ident + font family style normaliztion i1
sirreal ff0e180
Add font normalization tests
sirreal 417d7a0
parse_a_rule TDD scaffold
sirreal 6e21363
Implement parse_a_rule
sirreal 5a7b36b
Implement parse_a_rule()
sirreal ac0ad63
Implement normalization
sirreal 9cc1030
Add parse_a_list_of_declarations()
sirreal 2829bb1
implemenet declaration list processing
sirreal c56e56d
Add wp_scrub_utf8() to inputs of string and ident methods
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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,241 @@ | ||
| <?php | ||
|
|
||
| abstract class WP_CSS_Builder { | ||
| /** | ||
| * Create a CSS ident token from a plain PHP string value. | ||
| * | ||
| * Characters not valid in CSS identifiers are hex-escaped. This uses | ||
| * the same safety escaping as {@see WP_CSS_Builder::string()} for HTML | ||
| * and CSS-sensitive characters, plus escaping of whitespace and other | ||
| * characters not permitted in idents. | ||
| * | ||
| * @see https://www.w3.org/TR/css-syntax-3/#escaping | ||
| * @see https://www.w3.org/TR/css-syntax-3/#would-start-an-identifier | ||
| * | ||
| * @param string $value Decoded string value to encode as a CSS ident. | ||
| * @return string CSS ident token text. | ||
| */ | ||
| public static function ident( string $value ): string { | ||
| $value = wp_scrub_utf8( $value ); | ||
| $result = ''; | ||
| $length = strlen( $value ); | ||
|
|
||
| for ( $i = 0; $i < $length; $i++ ) { | ||
| $byte = ord( $value[ $i ] ); | ||
|
|
||
| // NULL → U+FFFD REPLACEMENT CHARACTER. | ||
| if ( 0x00 === $byte ) { | ||
| $result .= "\u{FFFD}"; | ||
| continue; | ||
| } | ||
|
|
||
| // Non-ASCII bytes (≥ 0x80): valid in idents, pass through. | ||
| if ( $byte >= 0x80 ) { | ||
| $result .= $value[ $i ]; | ||
| continue; | ||
| } | ||
|
|
||
| // ASCII letters and underscore: always valid in idents. | ||
| if ( | ||
| ( $byte >= 0x41 && $byte <= 0x5A ) || // A-Z | ||
| ( $byte >= 0x61 && $byte <= 0x7A ) || // a-z | ||
| 0x5F === $byte // _ | ||
| ) { | ||
| $result .= $value[ $i ]; | ||
| continue; | ||
| } | ||
|
|
||
| // Hyphen: valid in idents, but check for hyphen-digit at start. | ||
| if ( 0x2D === $byte ) { | ||
| // Hyphen at position 0 followed by a digit at position 1: escape the digit. | ||
| if ( 0 === $i && $i + 1 < $length && ord( $value[ $i + 1 ] ) >= 0x30 && ord( $value[ $i + 1 ] ) <= 0x39 ) { | ||
| $result .= '-'; | ||
| ++$i; | ||
| $result .= sprintf( '\\%X ', ord( $value[ $i ] ) ); | ||
| continue; | ||
| } | ||
| $result .= '-'; | ||
| continue; | ||
| } | ||
|
|
||
| // Digits: valid except at position 0. | ||
| if ( $byte >= 0x30 && $byte <= 0x39 ) { | ||
| if ( 0 === $i ) { | ||
| $result .= sprintf( '\\%X ', $byte ); | ||
| } else { | ||
| $result .= $value[ $i ]; | ||
| } | ||
| continue; | ||
| } | ||
|
|
||
| // Everything else: hex-escape. | ||
| $result .= sprintf( '\\%X ', $byte ); | ||
| } | ||
|
|
||
| return $result; | ||
| } | ||
|
|
||
| /** | ||
| * Create a quoted CSS string from a plain PHP string value. | ||
| * | ||
| * Example: | ||
| * $value = 'CSS & a "<style>" tag\'s strings'; | ||
| * $css_string = WP_CSS_Builder::string( $value ); | ||
| * echo "<style>*::before { content: {$css_string}; }</style>"; | ||
| * | ||
| * CSS strings are quoted many characters that are problematic in HTML | ||
| * or may be complicated for rudimentary CSS or HTML processors to handle | ||
| * are encoded using Unicode escape sequences. | ||
| * | ||
| * @see https://www.w3.org/TR/css-syntax-3/#escaping | ||
| */ | ||
| public static function string( string $value ): string { | ||
| $value = wp_scrub_utf8( $value ); | ||
| $escaped = strtr( | ||
| $value, | ||
| array( | ||
| // Escape existing backslashes to prevent unintentional escapes in result. | ||
| '\\' => '\\5C ', | ||
|
|
||
| // Pre-processing replaces NULLs and some newlines. Replace and escape as necessary. | ||
| "\0" => "\u{FFFD}", | ||
|
|
||
| // Normalize and replace newlines. https://www.w3.org/TR/css-syntax-3/#input-preprocessing | ||
| "\r\n" => '\\A ', | ||
| "\r" => '\\A ', | ||
| "\f" => '\\A ', | ||
|
|
||
| // Newlines must be escaped in CSS strings. | ||
| "\n" => '\\A ', | ||
|
|
||
| // Arbitrary characters for Unicode escaping: | ||
|
|
||
| // HTML syntax may be problematic. | ||
| '<' => '\\3C ', | ||
| '>' => '\\3E ', | ||
| '&' => '\\26 ', | ||
|
|
||
| // CSS syntax may be problematic. | ||
| ',' => '\\2C ', | ||
| ';' => '\\3B ', | ||
| '{' => '\\7B ', | ||
| '}' => '\\7D ', | ||
| '"' => '\\22 ', | ||
| "'" => '\\27 ', | ||
| ) | ||
| ); | ||
| return "\"{$escaped}\""; | ||
| } | ||
|
|
||
| public static function normalize_and_escape_css( string $css ): string { | ||
| $css = wp_scrub_utf8( $css ); | ||
| $processor = WP_CSS_Token_Processor::create( $css ); | ||
| if ( null === $processor ) { | ||
| return ''; | ||
| } | ||
|
|
||
| $normalized_css = ''; | ||
|
|
||
| while ( $processor->next_token() ) { | ||
| switch ( $processor->get_token_type() ) { | ||
|
|
||
| // Basic punctuation: | ||
| case WP_CSS_Token_Processor::TOKEN_SEMICOLON: $normalized_css .= ';'; break; | ||
| case WP_CSS_Token_Processor::TOKEN_COMMA: $normalized_css .= ','; break; | ||
| case WP_CSS_Token_Processor::TOKEN_WHITESPACE: $normalized_css .= ' '; break; | ||
| case WP_CSS_Token_Processor::TOKEN_COLON: $normalized_css .= ':'; break; | ||
|
|
||
| // Paired punctuation: | ||
| case WP_CSS_Token_Processor::TOKEN_LEFT_BRACE: $normalized_css .= '{'; break; | ||
| case WP_CSS_Token_Processor::TOKEN_RIGHT_BRACE: $normalized_css .= '}'; break; | ||
| case WP_CSS_Token_Processor::TOKEN_LEFT_PAREN: $normalized_css .= '('; break; | ||
| case WP_CSS_Token_Processor::TOKEN_RIGHT_PAREN: $normalized_css .= ')'; break; | ||
| case WP_CSS_Token_Processor::TOKEN_LEFT_BRACKET: $normalized_css .= '['; break; | ||
| case WP_CSS_Token_Processor::TOKEN_RIGHT_BRACKET: $normalized_css .= ']'; break; | ||
|
|
||
| // "@" + ident | ||
| case WP_CSS_Token_Processor::TOKEN_AT_KEYWORD: | ||
| $normalized_css .= '@' . self::ident( $processor->get_token_value() ); | ||
| break; | ||
|
|
||
| // ident + "(" | ||
| case WP_CSS_Token_Processor::TOKEN_FUNCTION: | ||
| $normalized_css .= self::ident( $processor->get_token_value() ) . '('; | ||
| break; | ||
|
|
||
| /* | ||
| * Hash tokens are not idents but their value can be escaped as such. | ||
| * | ||
| * ‖→ "#" →─┐ ┌──────────────────────────────┐ ┌─→‖ | ||
| * ├─→─┤ a-z A-Z 0-9 _ - or non-ASCII ├─→─┤ | ||
| * │ └──────────────────────────────┘ │ | ||
| * │ ┌──────────────────────────────┐ │ | ||
| * ├─→─┤ escape ├─→─┤ | ||
| * │ └──────────────────────────────┘ │ | ||
| * └──────────────────←───────────────────┘ | ||
| */ | ||
| case WP_CSS_Token_Processor::TOKEN_HASH: | ||
| $normalized_css .= '#' . self::ident( $processor->get_token_value() ); | ||
| break; | ||
|
|
||
| case WP_CSS_Token_Processor::TOKEN_DIMENSION: | ||
| $normalized_css .= $processor->get_token_value() . $processor->get_token_unit(); | ||
| break; | ||
|
|
||
| case WP_CSS_Token_Processor::TOKEN_PERCENTAGE: | ||
| $normalized_css .= "%{$processor->get_token_value()}"; | ||
| break; | ||
|
|
||
| case WP_CSS_Token_Processor::TOKEN_NUMBER: | ||
| $normalized_css .= $processor->get_token_value(); | ||
| break; | ||
|
|
||
| case WP_CSS_Token_Processor::TOKEN_DELIM: | ||
| $normalized_css .= $processor->get_token_value(); | ||
| break; | ||
|
|
||
| case WP_CSS_Token_Processor::TOKEN_IDENT: | ||
| $normalized_css .= self::ident( $processor->get_token_value() ); | ||
| break; | ||
|
|
||
| case WP_CSS_Token_Processor::TOKEN_STRING: | ||
| var_dump( $processor->get_token_value() ); | ||
| $normalized_css .= self::string( $processor->get_token_value() ); | ||
| break; | ||
|
|
||
| // Keep or strip comments? | ||
| case WP_CSS_Token_Processor::TOKEN_COMMENT: | ||
| $normalized_css .= substr( $css, $processor->get_token_start(), $processor->get_token_length() ); | ||
| break; | ||
|
|
||
| /** | ||
| * A <bad-string-token> is an open string that reaches a newline. | ||
| * | ||
| * @see https://www.w3.org/TR/css-syntax-3/#consume-string-token | ||
| * | ||
| * @see https://www.w3.org/TR/css-syntax-3/#preserved-tokens | ||
| * > Note: The tokens <}-token>s, <)-token>s, <]-token>, <bad-string-token>, and <bad-url-token> are always parse errors, but they are preserved in the token stream by this specification to allow other specs, such as Media Queries, to define more fine-grained error-handling than just dropping an entire declaration or block. | ||
| */ | ||
| case WP_CSS_Token_Processor::TOKEN_BAD_STRING: | ||
| $normalized_css .= substr( $css, $processor->get_token_start(), $processor->get_token_length() ) . "\n"; | ||
| break; | ||
|
|
||
| case WP_CSS_Token_Processor::TOKEN_URL: | ||
| case WP_CSS_Token_Processor::TOKEN_BAD_URL: | ||
| case WP_CSS_Token_Processor::TOKEN_CDC: | ||
| case WP_CSS_Token_Processor::TOKEN_CDO: | ||
| default: | ||
| throw new Error( 'unhandled token type ' . $processor->get_token_type() . ' with value ' . var_export( $processor->get_token_value(), true ) ); | ||
| } | ||
| } | ||
|
|
||
| return strtr( | ||
| $normalized_css, | ||
| array( | ||
| ' ' => '␠', | ||
| "\t" => "␉\t", | ||
| "\n" => "␊\n", | ||
| ) | ||
| ); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot Add
wp_scrub_utf8()to the inputs ofstringandidentmethods.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added
wp_scrub_utf8()to bothident()andstring()method inputs in c56e56d.