Skip to content

Commit 5d767ea

Browse files
committed
Lazily parse style attribute declarations
1 parent e1149d6 commit 5d767ea

2 files changed

Lines changed: 53 additions & 11 deletions

File tree

src/wp-includes/html-api/class-wp-html-style-attribute-processor.php

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,15 @@
1010
/**
1111
* Core class used to inspect and modify CSS declarations in an HTML style attribute.
1212
*
13-
* The processor operates on decoded style attribute values. Values read from
14-
* {@see WP_HTML_Tag_Processor::get_attribute()} can be passed directly to this
15-
* class, and values returned by {@see WP_HTML_Style_Attribute_Processor::get_updated_style()}
16-
* can be passed back to {@see WP_HTML_Tag_Processor::set_attribute()}.
13+
* The processor operates on the decoded CSS text value of a style attribute:
14+
* the CSS declaration-list contents of a declaration block, excluding the
15+
* delimiting braces. It does not parse HTML attribute syntax, decode character
16+
* references, or operate on raw HTML markup.
17+
*
18+
* Values read from {@see WP_HTML_Tag_Processor::get_attribute()} can be passed
19+
* directly to this class, and values returned by
20+
* {@see WP_HTML_Style_Attribute_Processor::get_updated_style()} can be passed
21+
* back to {@see WP_HTML_Tag_Processor::set_attribute()}.
1722
*
1823
* Unlike associative-array based style helpers, this processor preserves the
1924
* declaration list model of CSS. Multiple declarations with the same property
@@ -50,6 +55,13 @@ class WP_HTML_Style_Attribute_Processor {
5055
*/
5156
private $declarations = array();
5257

58+
/**
59+
* Whether the style attribute value has been parsed.
60+
*
61+
* @var bool
62+
*/
63+
private $parsed = false;
64+
5365
/**
5466
* Index of the current declaration, or -1 before the first declaration.
5567
*
@@ -81,7 +93,7 @@ class WP_HTML_Style_Attribute_Processor {
8193
/**
8294
* Constructor.
8395
*
84-
* @param string|bool|null $style Decoded style attribute value.
96+
* @param string|bool|null $style Decoded CSS text value from a style attribute.
8597
*/
8698
public function __construct( $style = '' ) {
8799
if ( null === $style || true === $style ) {
@@ -91,7 +103,6 @@ public function __construct( $style = '' ) {
91103
}
92104

93105
$this->style = $style;
94-
$this->parse();
95106
}
96107

97108
/**
@@ -100,10 +111,12 @@ public function __construct( $style = '' ) {
100111
* When a property name is provided, normal CSS properties are matched
101112
* ASCII-case-insensitively while custom properties are matched exactly.
102113
*
103-
* @param string|null $property_name Optional property name to match.
114+
* @param string|null $property_name Optional declaration property name to match.
104115
* @return bool Whether a matching declaration was found.
105116
*/
106117
public function next_declaration( ?string $property_name = null ): bool {
118+
$this->ensure_parsed();
119+
107120
for ( $i = $this->current_declaration + 1; $i < count( $this->declarations ); $i++ ) {
108121
if (
109122
null === $property_name ||
@@ -234,6 +247,8 @@ public function append_declaration( string $property_name, string $value, bool $
234247
return false;
235248
}
236249

250+
$this->ensure_parsed();
251+
237252
$old_declaration_count = count( $this->declarations );
238253
$trimmed_style = rtrim( $this->style, self::WHITESPACE );
239254
$insert_at = strlen( $trimmed_style );
@@ -265,9 +280,9 @@ public function append_declaration( string $property_name, string $value, bool $
265280
}
266281

267282
/**
268-
* Returns the updated style attribute value.
283+
* Returns the updated decoded CSS text value for the style attribute.
269284
*
270-
* @return string Updated style attribute value.
285+
* @return string Updated decoded CSS text value for the style attribute.
271286
*/
272287
public function get_updated_style(): string {
273288
if ( empty( $this->lexical_updates ) ) {
@@ -305,8 +320,12 @@ static function ( $a, $b ) {
305320
* Parses CSS tokens and declarations from the style attribute value.
306321
*/
307322
private function parse(): void {
323+
$this->tokens = array();
324+
$this->declarations = array();
325+
308326
$processor = WP_CSS_Token_Processor::create( $this->style );
309327
if ( null === $processor ) {
328+
$this->parsed = true;
310329
return;
311330
}
312331

@@ -323,6 +342,18 @@ private function parse(): void {
323342
}
324343

325344
$this->parse_declaration_list();
345+
$this->parsed = true;
346+
}
347+
348+
/**
349+
* Lazily parses the style attribute value when declaration access needs it.
350+
*/
351+
private function ensure_parsed(): void {
352+
if ( $this->parsed ) {
353+
return;
354+
}
355+
356+
$this->parse();
326357
}
327358

328359
/**
@@ -733,12 +764,11 @@ private function get_current_declaration(): ?array {
733764
private function apply_lexical_updates( int $current_declaration, bool $current_declaration_removed = false ): void {
734765
$this->style = $this->get_updated_style();
735766

736-
$this->tokens = array();
737-
$this->declarations = array();
738767
$this->lexical_updates = array();
739768
$this->lexical_update_order = 0;
740769
$this->current_declaration = $current_declaration;
741770
$this->current_declaration_removed = $current_declaration_removed;
771+
$this->parsed = false;
742772

743773
$this->parse();
744774

@@ -881,6 +911,7 @@ private function is_parseable_append( int $insert_at, string $separator, string
881911
$expected_after = $expected_start + strlen( $declaration_text );
882912
$candidate_style = substr( $this->style, 0, $insert_at ) . $separator . $declaration_text . substr( $this->style, $insert_at );
883913
$candidate = new self( $candidate_style );
914+
$candidate->ensure_parsed();
884915

885916
if ( count( $candidate->declarations ) !== $old_declaration_count + 1 ) {
886917
return false;

tests/phpunit/tests/html-api/wpHtmlStyleAttributeProcessor.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,17 @@ public function test_next_declaration_query_visits_matching_duplicate_properties
8484
$this->assertFalse( $processor->next_declaration( '--tone' ) );
8585
}
8686

87+
/**
88+
* @covers ::next_declaration
89+
*/
90+
public function test_next_declaration_accepts_named_property_name_argument() {
91+
$processor = new WP_HTML_Style_Attribute_Processor( 'color: red; background: white;' );
92+
93+
$this->assertTrue( $processor->next_declaration( property_name: 'background' ) );
94+
$this->assertSame( 'background', $processor->get_property_name() );
95+
$this->assertSame( 'white', $processor->get_value() );
96+
}
97+
8798
/**
8899
* @covers ::is_important
89100
* @covers ::get_value

0 commit comments

Comments
 (0)