Skip to content

Style attribute processor - #78

Draft
sirreal wants to merge 22 commits into
css-api/css-token-processor-foundationsfrom
css-token-style-attr-processor
Draft

sirreal wants to merge 22 commits into
css-api/css-token-processor-foundationsfrom
css-token-style-attr-processor

Conversation

@sirreal

@sirreal sirreal commented Jun 29, 2026 •

Copy link
Copy Markdown
Owner

Depends on #92.

Adds WP_HTML_Style_Attribute_Processor, a forward-only processor for safely inspecting and mutating decoded CSS declaration lists from HTML style attributes.

This PR is stacked on the CSS token processor foundations in #92. That dependency provides WP_CSS_Token_Processor, WP_CSS_Builder, their test corpus, and focused unit tests. The reviewable change introduced here is the style-attribute processor, its specification, WordPress loading, and its tests. It does not include the earlier selector or select() API explorations.

Summary

  • Adds forward-only traversal over declarations while preserving duplicates.
  • Matches ordinary property names ASCII-case-insensitively and custom properties exactly.
  • Adds safe mutation through set_value(), set_important(), remove_declaration(), and append_declaration().
  • Preserves comments, invalid fragments, authored property spelling, and surrounding source when doing so is structurally safe.
  • Rejects malformed or declaration-breaking values atomically with false.
  • Repairs component values implicitly closed at EOF before appending, but only when the required closing delimiters can be determined precisely.
  • Documents the intended API and mutation guarantees in docs/wp-html-style-attribute-processor-spec.md.

API and scope

The processor accepts decoded style-attribute text, not raw HTML attribute syntax:

$styles = WP_HTML_Style_Attribute_Processor::create( $decoded_style );

while ( $styles->next_declaration( 'color' ) ) {
	$styles->set_value( 'green' );
}

$tags->set_attribute( 'style', $styles->get_updated_style() );

The initial API intentionally does not expose raw declaration values, bookmarks, cascade resolution, or property-specific browser grammar validation. It validates CSS declaration structure and safe placement rather than whether a particular property accepts a particular value.

Validation

Run on the combined branch based on current upstream/trunk:

  • WP_TESTS_SKIP_INSTALL=1 vendor/bin/phpunit tests/phpunit/tests/css-api/wpCssBuilder.php — 46 tests, 200 assertions
  • WP_TESTS_SKIP_INSTALL=1 vendor/bin/phpunit tests/phpunit/tests/css-api/wpCssTokenProcessor.php — 266 tests, 385 assertions
  • WP_TESTS_SKIP_INSTALL=1 vendor/bin/phpunit tests/phpunit/tests/html-api/wpHtmlStyleAttributeProcessor.php — 56 tests, 268 assertions
  • vendor/bin/phpcs --standard=phpcs.xml.dist on the six affected PHP source and test files
  • git diff --check

Trac ticket: https://core.trac.wordpress.org/ticket/62653

Use of AI Tools

AI assistance: Yes
Tool(s): ChatGPT/Codex
Model(s): GPT-5
Used for: extracting the CSS foundations into the dependent PR, refining the specification and implementation, adding tests, adversarial review, and updating this PR description.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

@sirreal
sirreal force-pushed the css-token-style-attr-processor branch from 1318192 to b458f80 Compare July 23, 2026 10:33
@sirreal
sirreal force-pushed the css-token-style-attr-processor branch from 1d1fe7a to 202991d Compare August 26, 2026 09:57
@sirreal
sirreal changed the base branch from trunk to css-api/css-token-processor-foundations August 26, 2026 09:57
@sirreal
sirreal force-pushed the css-token-style-attr-processor branch 2 times, most recently from 62b1cbd to 0f7d282 Compare August 26, 2026 10:48
@sirreal
sirreal force-pushed the css-token-style-attr-processor branch from e560ec9 to 71b865f Compare September 25, 2026 07:07
@sirreal
sirreal force-pushed the css-api/css-token-processor-foundations branch from 2ed7203 to 7711c31 Compare September 25, 2026 07:07
@sirreal
sirreal force-pushed the css-api/css-token-processor-foundations branch from 7711c31 to a9d4b5d Compare October 9, 2026 08:13
@sirreal
sirreal force-pushed the css-token-style-attr-processor branch 2 times, most recently from b0640e6 to 8c9719a Compare October 12, 2026 09:54
set_value() and append_declaration() trimmed CSS whitespace bytes from the
end of the value. When the last token is an escape of a space or tab, or a
backslash delimiter followed by a newline, the trim removed the byte that
completed it, so the `;` written after the value became part of an escape:
append_declaration( '--x', "a\\ " ) wrote `--x: a\;`, whose value is the
ident `a;`. set_value() rejected the same values because its replacement
check saw the declaration absorb the next one.

Trim only whitespace tokens, and keep one newline after a final backslash
delimiter.
The processor kept an array per token with the type, the decoded value
and unit, and the offsets, and get_value_tokens() returned those arrays.
It now stores the WP_CSS_Token snapshots from
WP_CSS_Token_Processor::get_token() and returns them, so values are
decoded when read rather than for every token during parsing, and
consumers read the flag and termination that the arrays did not carry.

Declarations stay private arrays, and the processor uses the token
processor rather than extending it.
get_eof_repair() decided whether a comment or url token at the end of
the style was closed by reading bytes: a comment counted as closed when
the style ended in `*/`, and a url token was re-tokenized with a
sentinel appended. WP_CSS_Token::is_terminated() records the answer, so
use it and delete is_url_token_closed().

The byte check counted `/*/` as a closed comment. Appending after it
still failed, because the parse check rejects a declaration absorbed by
the comment; the new test pins that result, which is the same before
and after this change.
Code that reads a declaration and needs to write it back, such as a
filter that keeps some declarations and drops others, had no public way
to do so: WP_CSS_Token_Serializer is internal, and the processor wrote
text only through mutations.

serialize_declaration() returns the declaration at the cursor as
`name: value;` or `name: value !important;`, as CSSOM writes a
declaration, or null when the cursor is not on a declaration, like the
other getters. The name is escaped as an identifier and the value is
written by WP_CSS_Token_Serializer, so comments are dropped, whitespace
runs become one space, and blocks left open at the end of the style are
closed. The name follows WP_HTML_Processor::serialize_token().

The private formatter it shares with set_value() and
append_declaration() is renamed format_declaration().
Every successful mutation parses the whole style text again, so removing
declarations one at a time takes time that grows with the square of the
style's length. Measured on this branch, removing every declaration of a
style takes 12, 189 and 744 ms for 50, 200 and 400 declarations, while
reading them all and joining serialize_declaration() takes 0.3, 1.2 and
2.4 ms. Say so on remove_declaration() and in the spec, and point to the
alternative.
set_value() and append_declaration() checked that a supplied value parses
as one declaration value, then wrote the caller's bytes: comments,
whitespace runs, raw control characters inside strings and any escape
spelling reached the style. They now write the declaration as
serialize_declaration() does: the name is escaped as an identifier and
the value is written by WP_CSS_Token_Serializer from its tokens, with the
whitespace and comments around it removed. set_value() writes the
decoded name, so `c\6f lor` becomes `color`.

trim_value() and the raw_name field have no other use and are removed.
CSS closes a string or a comment that the end of the input cuts off, and
so does WP_CSS_Token_Serializer::serialize(), but get_eof_repair()
closed only blocks and urls: append_declaration() and set_important()
returned false after `font-family: "a` or `color: red /* x`. The repair
now writes the string's own quote or `*/` before the block closers. A `\`
at the end of the style still makes the append fail, because no appended
text can end the escape it starts.
@sirreal
sirreal force-pushed the css-token-style-attr-processor branch from 8c9719a to 755bff5 Compare October 14, 2026 06:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant