Skip to content

Commit d1b71f9

Browse files
committed
HTML API: Introduce wp_split_class_names().
This patch introduces a new CSS helper module containing a new function, `wp_split_class_names()`. This function wraps some code to rely on the HTML API to take an HTML `class` attribute value and return a `Generator` to iterate over the classes in that value. Many existing functions perform ad-hoc parsing of CSS class names, usually by splitting on a space character. However, there are issues with this approach: - There is no decoding of HTML character references, which is normative inside HTML attributes. - There is no handling of null bytes. - Class names can be split by more than just the space character. - There is no handling of duplicates, and while mostly benign, code forgetting to account for duplicates can lead to defects. The new function handles the nuances to let developers focus on reading CSS class names, adding new class names, and removing class names. This serves a middleground between legacy code interacting with CSS class names in isolation and code processing full HTML documents.
1 parent 1885bee commit d1b71f9

File tree

2 files changed

+59
-0
lines changed

2 files changed

+59
-0
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
/**
3+
* HTML API: Utility functions for working with CSS and CSS-adjacent content.
4+
*
5+
* @package WordPress
6+
* @subpackage HTML-API
7+
* @since 6.9.0
8+
*/
9+
10+
/**
11+
* Iterates over the class names represented by an HTML `class` attribute value.
12+
*
13+
* Because this is aware of the HTML and CSS semantics, duplicate values will be
14+
* skipped and classes will be properly split according to the whitespace rules.
15+
* Because this comes without a parent document, it will be parsed as NO QUIRKS
16+
* mode and therefore treats class names as byte-identical names rather than as
17+
* case-insensitive names.
18+
*
19+
* Example:
20+
*
21+
* foreach ( wp_split_css_class_list( 'alignleft wide alignleft' ) as $name ) {
22+
* echo $name . ' ';
23+
* }
24+
* // "alignleft wide "
25+
*
26+
* // The missing semicolon demonstrates one way this follows HTML semantics.
27+
* foreach ( wp_split_css_class_list( "One one ONE &#x80" ) as $name ) {
28+
* echo $name . ' ';
29+
* }
30+
* // "One one ONE € "
31+
*
32+
* // It can also be converted into an array.
33+
* $names = iterator_to_array( wp_split_css_class_list( "one &#x61nd two ze&#x00;\x00ro" ) );
34+
* $names === array( 'one', 'and', 'two', 'ze��ro' );
35+
*
36+
* // Can be used to join classes uniquely.
37+
* $combined = wp_split_css_class_list( "{$existing} {$new}" );
38+
* $combined = implode( ' ', iterator_to_array( $combined ) );
39+
*
40+
* @since 6.9.0
41+
*
42+
* @param string $class_attribute_string Class names as found in an HTML `class` attribute.
43+
* @return Generator<non-empty-string> Use this in a foreach loop to iterate over the class names.
44+
*/
45+
function wp_split_css_class_list( $class_attribute_string ): Generator {
46+
if ( '' === $class_attribute_string || ! is_string( $class_attribute_string ) ) {
47+
return;
48+
}
49+
50+
// Get these from the HTML API to avoid ad-hoc parsing HTML or CSS class names.
51+
$processor = new WP_HTML_Tag_Processor( '<wp-noop>' );
52+
$processor->next_token();
53+
$processor->set_attribute( 'class', $class_attribute_string );
54+
55+
foreach ( $processor->class_list() as $class_name ) {
56+
yield $class_name;
57+
}
58+
}

src/wp-settings.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@
268268
require ABSPATH . WPINC . '/html-api/class-wp-html-stack-event.php';
269269
require ABSPATH . WPINC . '/html-api/class-wp-html-processor-state.php';
270270
require ABSPATH . WPINC . '/html-api/class-wp-html-processor.php';
271+
require ABSPATH . WPINC . '/html-api/css-helpers.php';
271272
require ABSPATH . WPINC . '/class-wp-block-processor.php';
272273
require ABSPATH . WPINC . '/class-wp-http.php';
273274
require ABSPATH . WPINC . '/class-wp-http-streams.php';

0 commit comments

Comments
 (0)