From eec2470904742c182866fef072d7e6667896afd9 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Mon, 12 Jun 2023 15:57:47 +0300 Subject: [PATCH] HTML-API: Backport updates from Core This is a "blessed" PR, bringing back changes that were introduced to the HTML API in Trac/SVG that need to exist in Gutenberg to avoid introducing breakage and disharmony between the two repositories. cc: @sergeybiryukov --- .../html-api/class-wp-html-tag-processor.php | 380 ++++++++---------- ...class-gutenberg-html-tag-processor-6-3.php | 325 +++++++-------- 2 files changed, 312 insertions(+), 393 deletions(-) diff --git a/lib/compat/wordpress-6.2/html-api/class-wp-html-tag-processor.php b/lib/compat/wordpress-6.2/html-api/class-wp-html-tag-processor.php index f06302d4b742ff..5eee939c9c531f 100644 --- a/lib/compat/wordpress-6.2/html-api/class-wp-html-tag-processor.php +++ b/lib/compat/wordpress-6.2/html-api/class-wp-html-tag-processor.php @@ -38,12 +38,11 @@ * 3. Request changes to the attributes in those tag(s). * * Example: - * ```php - * $tags = new WP_HTML_Tag_Processor( $html ); - * if ( $tags->next_tag( 'option' ) ) { - * $tags->set_attribute( 'selected', true ); - * } - * ``` + * + * $tags = new WP_HTML_Tag_Processor( $html ); + * if ( $tags->next_tag( 'option' ) ) { + * $tags->set_attribute( 'selected', true ); + * } * * ### Finding tags * @@ -54,9 +53,8 @@ * regardless of what kind it is. * * If you want to _find whatever the next tag is_: - * ```php - * $tags->next_tag(); - * ``` + * + * $tags->next_tag(); * * | Goal | Query | * |-----------------------------------------------------------|---------------------------------------------------------------------------------| @@ -87,19 +85,18 @@ * provided by the processor or external state or variables. * * Example: - * ```php - * // Paint up to the first five DIV or SPAN tags marked with the "jazzy" style. - * $remaining_count = 5; - * while ( $remaining_count > 0 && $tags->next_tag() ) { - * if ( - * ( 'DIV' === $tags->get_tag() || 'SPAN' === $tags->get_tag() ) && - * 'jazzy' === $tags->get_attribute( 'data-style' ) - * ) { - * $tags->add_class( 'theme-style-everest-jazz' ); - * $remaining_count--; + * + * // Paint up to the first five DIV or SPAN tags marked with the "jazzy" style. + * $remaining_count = 5; + * while ( $remaining_count > 0 && $tags->next_tag() ) { + * if ( + * ( 'DIV' === $tags->get_tag() || 'SPAN' === $tags->get_tag() ) && + * 'jazzy' === $tags->get_attribute( 'data-style' ) + * ) { + * $tags->add_class( 'theme-style-everest-jazz' ); + * $remaining_count--; + * } * } - * } - * ``` * * `get_attribute()` will return `null` if the attribute wasn't present * on the tag when it was called. It may return `""` (the empty string) @@ -116,12 +113,11 @@ * nothing and move on to the next opening tag. * * Example: - * ```php - * if ( $tags->next_tag( array( 'class' => 'wp-group-block' ) ) ) { - * $tags->set_attribute( 'title', 'This groups the contained content.' ); - * $tags->remove_attribute( 'data-test-id' ); - * } - * ``` + * + * if ( $tags->next_tag( array( 'class' => 'wp-group-block' ) ) ) { + * $tags->set_attribute( 'title', 'This groups the contained content.' ); + * $tags->remove_attribute( 'data-test-id' ); + * } * * If `set_attribute()` is called for an existing attribute it will * overwrite the existing value. Similarly, calling `remove_attribute()` @@ -141,31 +137,30 @@ * entire `class` attribute will be removed. * * Example: - * ```php - * // from `Yippee!` - * // to `Yippee!` - * $tags->add_class( 'is-active' ); * - * // from `Yippee!` - * // to `Yippee!` - * $tags->add_class( 'is-active' ); + * // from `Yippee!` + * // to `Yippee!` + * $tags->add_class( 'is-active' ); + * + * // from `Yippee!` + * // to `Yippee!` + * $tags->add_class( 'is-active' ); * - * // from `Yippee!` - * // to `Yippee!` - * $tags->add_class( 'is-active' ); + * // from `Yippee!` + * // to `Yippee!` + * $tags->add_class( 'is-active' ); * - * // from `` - * // to ` - * $tags->remove_class( 'rugby' ); + * // from `` + * // to ` + * $tags->remove_class( 'rugby' ); * - * // from `` - * // to ` - * $tags->remove_class( 'rugby' ); + * // from `` + * // to ` + * $tags->remove_class( 'rugby' ); * - * // from `` - * // to ` - * $tags->remove_class( 'rugby' ); - * ``` + * // from `` + * // to ` + * $tags->remove_class( 'rugby' ); * * When class changes are enqueued but a direct change to `class` is made via * `set_attribute` then the changes to `set_attribute` (or `remove_attribute`) @@ -184,26 +179,24 @@ * and so on. It's fine from a performance standpoint to create a * bookmark and update it frequently, such as within a loop. * - * ```php - * $total_todos = 0; - * while ( $p->next_tag( array( 'tag_name' => 'UL', 'class_name' => 'todo' ) ) ) { - * $p->set_bookmark( 'list-start' ); - * while ( $p->next_tag( array( 'tag_closers' => 'visit' ) ) ) { - * if ( 'UL' === $p->get_tag() && $p->is_tag_closer() ) { - * $p->set_bookmark( 'list-end' ); - * $p->seek( 'list-start' ); - * $p->set_attribute( 'data-contained-todos', (string) $total_todos ); - * $total_todos = 0; - * $p->seek( 'list-end' ); - * break; - * } + * $total_todos = 0; + * while ( $p->next_tag( array( 'tag_name' => 'UL', 'class_name' => 'todo' ) ) ) { + * $p->set_bookmark( 'list-start' ); + * while ( $p->next_tag( array( 'tag_closers' => 'visit' ) ) ) { + * if ( 'UL' === $p->get_tag() && $p->is_tag_closer() ) { + * $p->set_bookmark( 'list-end' ); + * $p->seek( 'list-start' ); + * $p->set_attribute( 'data-contained-todos', (string) $total_todos ); + * $total_todos = 0; + * $p->seek( 'list-end' ); + * break; + * } * - * if ( 'LI' === $p->get_tag() && ! $p->is_tag_closer() ) { - * $total_todos++; + * if ( 'LI' === $p->get_tag() && ! $p->is_tag_closer() ) { + * $total_todos++; + * } * } * } - * } - * ``` * * ## Design and limitations * @@ -229,11 +222,11 @@ * The Tag Processor's design incorporates a "garbage-in-garbage-out" philosophy. * HTML5 specifies that certain invalid content be transformed into different forms * for display, such as removing null bytes from an input document and replacing - * invalid characters with the Unicode replacement character `U+FFFD` (visually "�"). - * Where errors or transformations exist within the HTML5 specification, the Tag Processor - * leaves those invalid inputs untouched, passing them through to the final browser - * to handle. While this implies that certain operations will be non-spec-compliant, - * such as reading the value of an attribute with invalid content, it also preserves a + * invalid characters with the Unicode replacement character U+FFFD �. Where errors + * or transformations exist within the HTML5 specification, the Tag Processor leaves + * those invalid inputs untouched, passing them through to the final browser to handle. + * While this implies that certain operations will be non-spec-compliant, such as + * reading the value of an attribute with invalid content, it also preserves a * simplicity and efficiency for handling those error cases. * * Most operations within the Tag Processor are designed to minimize the difference @@ -335,11 +328,10 @@ class WP_HTML_Tag_Processor { * Byte offset in input document where current tag name starts. * * Example: - * ``` - *
... - * 01234 - * - tag name starts at 1 - * ``` + * + *
... + * 01234 + * - tag name starts at 1 * * @since 6.2.0 * @var int|null @@ -350,11 +342,10 @@ class WP_HTML_Tag_Processor { * Byte length of current tag name. * * Example: - * ``` - *
... - * 01234 - * --- tag name length is 3 - * ``` + * + *
... + * 01234 + * --- tag name length is 3 * * @since 6.2.0 * @var int|null @@ -365,12 +356,11 @@ class WP_HTML_Tag_Processor { * Byte offset in input document where current tag token ends. * * Example: - * ``` - *
... - * 0 1 | - * 01234567890123456 - * --- tag name ends at 14 - * ``` + * + *
... + * 0 1 | + * 01234567890123456 + * --- tag name ends at 14 * * @since 6.2.0 * @var int|null @@ -388,25 +378,24 @@ class WP_HTML_Tag_Processor { * Lazily-built index of attributes found within an HTML tag, keyed by the attribute name. * * Example: - * ```php - * // supposing the parser is working through this content - * // and stops after recognizing the `id` attribute - * //
- * // ^ parsing will continue from this point - * $this->attributes = array( - * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 ) - * ); - * - * // when picking up parsing again, or when asking to find the - * // `class` attribute we will continue and add to this array - * $this->attributes = array( - * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 ), - * 'class' => new WP_HTML_Attribute_Match( 'class', 'outline', 18, 32 ) - * ); - * - * // Note that only the `class` attribute value is stored in the index. - * // That's because it is the only value used by this class at the moment. - * ``` + * + * // Supposing the parser is working through this content + * // and stops after recognizing the `id` attribute. + * //
+ * // ^ parsing will continue from this point. + * $this->attributes = array( + * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 ) + * ); + * + * // When picking up parsing again, or when asking to find the + * // `class` attribute we will continue and add to this array. + * $this->attributes = array( + * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 ), + * 'class' => new WP_HTML_Attribute_Match( 'class', 'outline', 18, 32 ) + * ); + * + * // Note that only the `class` attribute value is stored in the index. + * // That's because it is the only value used by this class at the moment. * * @since 6.2.0 * @var WP_HTML_Attribute_Token[] @@ -425,14 +414,13 @@ class WP_HTML_Tag_Processor { * into a single `set_attribute( 'class', $changes )` call. * * Example: - * ```php - * // Add the `wp-block-group` class, remove the `wp-group` class. - * $classname_updates = array( - * // Indexed by a comparable class name - * 'wp-block-group' => WP_HTML_Tag_Processor::ADD_CLASS, - * 'wp-group' => WP_HTML_Tag_Processor::REMOVE_CLASS - * ); - * ``` + * + * // Add the `wp-block-group` class, remove the `wp-group` class. + * $classname_updates = array( + * // Indexed by a comparable class name. + * 'wp-block-group' => WP_HTML_Tag_Processor::ADD_CLASS, + * 'wp-group' => WP_HTML_Tag_Processor::REMOVE_CLASS + * ); * * @since 6.2.0 * @var bool[] @@ -479,18 +467,17 @@ class WP_HTML_Tag_Processor { * copies when applying many updates to a single document. * * Example: - * ```php - * // Replace an attribute stored with a new value, indices - * // sourced from the lazily-parsed HTML recognizer. - * $start = $attributes['src']->start; - * $end = $attributes['src']->end; - * $modifications[] = new WP_HTML_Text_Replacement( $start, $end, $new_value ); - * - * // Correspondingly, something like this will appear in this array. - * $lexical_updates = array( - * WP_HTML_Text_Replacement( 14, 28, 'https://my-site.my-domain/wp-content/uploads/2014/08/kittens.jpg' ) - * ); - * ``` + * + * // Replace an attribute stored with a new value, indices + * // sourced from the lazily-parsed HTML recognizer. + * $start = $attributes['src']->start; + * $end = $attributes['src']->end; + * $modifications[] = new WP_HTML_Text_Replacement( $start, $end, $new_value ); + * + * // Correspondingly, something like this will appear in this array. + * $lexical_updates = array( + * WP_HTML_Text_Replacement( 14, 28, 'https://my-site.my-domain/wp-content/uploads/2014/08/kittens.jpg' ) + * ); * * @since 6.2.0 * @var WP_HTML_Text_Replacement[] @@ -608,7 +595,7 @@ public function next_tag( $query = null ) { * Release bookmarks when they are no longer needed. * * Example: - * ``` + * *

Surprising fact you may not know!

* ^ ^ * \-|-- this `H2` opener bookmark tracks the token @@ -616,14 +603,13 @@ public function next_tag( $query = null ) { *

Surprising fact you may no… * ^ ^ * \-|-- it shifts with edits - * ``` * * Bookmarks provide the ability to seek to a previously-scanned * place in the HTML document. This avoids the need to re-scan * the entire document. * * Example: - * ``` + * *
  • One
  • Two
  • Three
* ^^^^ * want to note this last item @@ -650,7 +636,6 @@ public function next_tag( $query = null ) { * $p->set_bookmark( 'last-li' ); * } * } - * ``` * * Bookmarks intentionally hide the internal string offsets * to which they refer. They are maintained internally as @@ -727,7 +712,7 @@ public function release_bookmark( $name ) { * * @see https://html.spec.whatwg.org/multipage/parsing.html#rcdata-state * - * @param string $tag_name – the lowercase tag name which will close the RCDATA region. + * @param string $tag_name The lowercase tag name which will close the RCDATA region. * @return bool Whether an end to the RCDATA region was found before the end of the document. */ private function skip_rcdata( $tag_name ) { @@ -1430,7 +1415,6 @@ private function class_name_updates_to_attributes_updates() { * * @since 6.2.0 * @since 6.2.1 Accumulates shift for internal cursor and passed pointer. - * @since 6.3.0 Invalidate any bookmarks whose targets are overwritten. * * @param int $shift_this_point Accumulate and return shift for this position. * @return int How many bytes the given pointer moved in response to the updates. @@ -1480,7 +1464,7 @@ private function apply_attributes_updates( $shift_this_point = 0 ) { * Adjust bookmark locations to account for how the text * replacements adjust offsets in the input document. */ - foreach ( $this->bookmarks as $bookmark_name => $bookmark ) { + foreach ( $this->bookmarks as $bookmark ) { /* * Each lexical update which appears before the bookmark's endpoints * might shift the offsets for those endpoints. Loop through each change @@ -1491,22 +1475,20 @@ private function apply_attributes_updates( $shift_this_point = 0 ) { $tail_delta = 0; foreach ( $this->lexical_updates as $diff ) { - if ( $bookmark->start < $diff->start && $bookmark->end < $diff->start ) { - break; - } + $update_head = $bookmark->start >= $diff->start; + $update_tail = $bookmark->end >= $diff->start; - if ( $bookmark->start >= $diff->start && $bookmark->end < $diff->end ) { - $this->release_bookmark( $bookmark_name ); - continue 2; + if ( ! $update_head && ! $update_tail ) { + break; } $delta = strlen( $diff->text ) - ( $diff->end - $diff->start ); - if ( $bookmark->start >= $diff->start ) { + if ( $update_head ) { $head_delta += $delta; } - if ( $bookmark->end >= $diff->end ) { + if ( $update_tail ) { $tail_delta += $delta; } } @@ -1520,18 +1502,6 @@ private function apply_attributes_updates( $shift_this_point = 0 ) { return $accumulated_shift_for_given_point; } - /** - * Checks whether a bookmark with the given name exists. - * - * @since 6.3.0 - * - * @param string $bookmark_name Name to identify a bookmark that potentially exists. - * @return bool Whether that bookmark exists. - */ - public function has_bookmark( $bookmark_name ) { - return array_key_exists( $bookmark_name, $this->bookmarks ); - } - /** * Move the internal cursor in the Tag Processor to a given bookmark's location. * @@ -1630,10 +1600,9 @@ private function get_enqueued_attribute_value( $comparable_name ) { * or trailing whitespace, and that the casing follows the name given in `set_attribute`. * * Example: - * ``` + * * $p->set_attribute( 'data-TEST-id', 'update' ); * 'update' === $p->get_enqueued_attribute_value( 'data-test-id' ); - * ``` * * Detect this difference based on the absence of the `=`, which _must_ exist in any * attribute containing a value, e.g. ``. @@ -1664,16 +1633,15 @@ private function get_enqueued_attribute_value( $comparable_name ) { * Returns the value of a requested attribute from a matched tag opener if that attribute exists. * * Example: - * ```php - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag( array( 'class_name' => 'test' ) ) === true; - * $p->get_attribute( 'data-test-id' ) === '14'; - * $p->get_attribute( 'enabled' ) === true; - * $p->get_attribute( 'aria-label' ) === null; * - * $p->next_tag() === false; - * $p->get_attribute( 'class' ) === null; - * ``` + * $p = new WP_HTML_Tag_Processor( '
Test
' ); + * $p->next_tag( array( 'class_name' => 'test' ) ) === true; + * $p->get_attribute( 'data-test-id' ) === '14'; + * $p->get_attribute( 'enabled' ) === true; + * $p->get_attribute( 'aria-label' ) === null; + * + * $p->next_tag() === false; + * $p->get_attribute( 'class' ) === null; * * @since 6.2.0 * @@ -1745,14 +1713,13 @@ public function get_attribute( $name ) { * - HTML 5 spec * * Example: - * ```php - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag( array( 'class_name' => 'test' ) ) === true; - * $p->get_attribute_names_with_prefix( 'data-' ) === array( 'data-enabled', 'data-test-id' ); * - * $p->next_tag() === false; - * $p->get_attribute_names_with_prefix( 'data-' ) === null; - * ``` + * $p = new WP_HTML_Tag_Processor( '
Test
' ); + * $p->next_tag( array( 'class_name' => 'test' ) ) === true; + * $p->get_attribute_names_with_prefix( 'data-' ) === array( 'data-enabled', 'data-test-id' ); + * + * $p->next_tag() === false; + * $p->get_attribute_names_with_prefix( 'data-' ) === null; * * @since 6.2.0 * @@ -1781,14 +1748,13 @@ function get_attribute_names_with_prefix( $prefix ) { * Returns the uppercase name of the matched tag. * * Example: - * ```php - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag() === true; - * $p->get_tag() === 'DIV'; * - * $p->next_tag() === false; - * $p->get_tag() === null; - * ``` + * $p = new WP_HTML_Tag_Processor( '
Test
' ); + * $p->next_tag() === true; + * $p->get_tag() === 'DIV'; + * + * $p->next_tag() === false; + * $p->get_tag() === null; * * @since 6.2.0 * @@ -1804,43 +1770,17 @@ public function get_tag() { return strtoupper( $tag_name ); } - /** - * Indicates if the currently matched tag contains the self-closing flag. - * - * No HTML elements ought to have the self-closing flag and for those, the self-closing - * flag will be ignored. For void elements this is benign because they "self close" - * automatically. For non-void HTML elements though problems will appear if someone - * intends to use a self-closing element in place of that element with an empty body. - * For HTML foreign elements and custom elements the self-closing flag determines if - * they self-close or not. - * - * This function does not determine if a tag is self-closing, - * but only if the self-closing flag is present in the syntax. - * - * @since 6.3.0 - * - * @return bool Whether the currently matched tag contains the self-closing flag. - */ - public function has_self_closing_flag() { - if ( ! $this->tag_name_starts_at ) { - return false; - } - - return '/' === $this->html[ $this->tag_ends_at - 1 ]; - } - /** * Indicates if the current tag token is a tag closer. * * Example: - * ```php - * $p = new WP_HTML_Tag_Processor( '
' ); - * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); - * $p->is_tag_closer() === false; * - * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); - * $p->is_tag_closer() === true; - * ``` + * $p = new WP_HTML_Tag_Processor( '
' ); + * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); + * $p->is_tag_closer() === false; + * + * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); + * $p->is_tag_closer() === true; * * @since 6.2.0 * @@ -1946,14 +1886,15 @@ public function set_attribute( $name, $value ) { * Update an existing attribute. * * Example – set attribute id to "new" in
: - *
- * ^-------------^ - * start end - * replacement: `id="new"` * - * Result:
+ *
+ * ^-------------^ + * start end + * replacement: `id="new"` + * + * Result:
*/ - $existing_attribute = $this->attributes[ $comparable_name ]; + $existing_attribute = $this->attributes[ $comparable_name ]; $this->lexical_updates[ $comparable_name ] = new WP_HTML_Text_Replacement( $existing_attribute->start, $existing_attribute->end, @@ -1964,12 +1905,13 @@ public function set_attribute( $name, $value ) { * Create a new attribute at the tag's name end. * * Example – add attribute id="new" to
: - *
- * ^ - * start and end - * replacement: ` id="new"` * - * Result:
+ *
+ * ^ + * start and end + * replacement: ` id="new"` + * + * Result:
*/ $this->lexical_updates[ $comparable_name ] = new WP_HTML_Text_Replacement( $this->tag_name_starts_at + $this->tag_name_length, diff --git a/lib/compat/wordpress-6.3/html-api/class-gutenberg-html-tag-processor-6-3.php b/lib/compat/wordpress-6.3/html-api/class-gutenberg-html-tag-processor-6-3.php index 5b38484f659acc..b869e2ef1e1c16 100644 --- a/lib/compat/wordpress-6.3/html-api/class-gutenberg-html-tag-processor-6-3.php +++ b/lib/compat/wordpress-6.3/html-api/class-gutenberg-html-tag-processor-6-3.php @@ -1,5 +1,7 @@ next_tag( 'option' ) ) { - * $tags->set_attribute( 'selected', true ); - * } - * ``` + * + * $tags = new WP_HTML_Tag_Processor( $html ); + * if ( $tags->next_tag( 'option' ) ) { + * $tags->set_attribute( 'selected', true ); + * } * * ### Finding tags * @@ -54,9 +55,8 @@ * regardless of what kind it is. * * If you want to _find whatever the next tag is_: - * ```php - * $tags->next_tag(); - * ``` + * + * $tags->next_tag(); * * | Goal | Query | * |-----------------------------------------------------------|---------------------------------------------------------------------------------| @@ -87,19 +87,18 @@ * provided by the processor or external state or variables. * * Example: - * ```php - * // Paint up to the first five DIV or SPAN tags marked with the "jazzy" style. - * $remaining_count = 5; - * while ( $remaining_count > 0 && $tags->next_tag() ) { - * if ( - * ( 'DIV' === $tags->get_tag() || 'SPAN' === $tags->get_tag() ) && - * 'jazzy' === $tags->get_attribute( 'data-style' ) - * ) { - * $tags->add_class( 'theme-style-everest-jazz' ); - * $remaining_count--; + * + * // Paint up to the first five DIV or SPAN tags marked with the "jazzy" style. + * $remaining_count = 5; + * while ( $remaining_count > 0 && $tags->next_tag() ) { + * if ( + * ( 'DIV' === $tags->get_tag() || 'SPAN' === $tags->get_tag() ) && + * 'jazzy' === $tags->get_attribute( 'data-style' ) + * ) { + * $tags->add_class( 'theme-style-everest-jazz' ); + * $remaining_count--; + * } * } - * } - * ``` * * `get_attribute()` will return `null` if the attribute wasn't present * on the tag when it was called. It may return `""` (the empty string) @@ -116,12 +115,11 @@ * nothing and move on to the next opening tag. * * Example: - * ```php - * if ( $tags->next_tag( array( 'class' => 'wp-group-block' ) ) ) { - * $tags->set_attribute( 'title', 'This groups the contained content.' ); - * $tags->remove_attribute( 'data-test-id' ); - * } - * ``` + * + * if ( $tags->next_tag( array( 'class' => 'wp-group-block' ) ) ) { + * $tags->set_attribute( 'title', 'This groups the contained content.' ); + * $tags->remove_attribute( 'data-test-id' ); + * } * * If `set_attribute()` is called for an existing attribute it will * overwrite the existing value. Similarly, calling `remove_attribute()` @@ -141,31 +139,30 @@ * entire `class` attribute will be removed. * * Example: - * ```php - * // from `Yippee!` - * // to `Yippee!` - * $tags->add_class( 'is-active' ); * - * // from `Yippee!` - * // to `Yippee!` - * $tags->add_class( 'is-active' ); + * // from `Yippee!` + * // to `Yippee!` + * $tags->add_class( 'is-active' ); * - * // from `Yippee!` - * // to `Yippee!` - * $tags->add_class( 'is-active' ); + * // from `Yippee!` + * // to `Yippee!` + * $tags->add_class( 'is-active' ); * - * // from `` - * // to ` - * $tags->remove_class( 'rugby' ); + * // from `Yippee!` + * // to `Yippee!` + * $tags->add_class( 'is-active' ); * - * // from `` - * // to ` - * $tags->remove_class( 'rugby' ); + * // from `` + * // to ` + * $tags->remove_class( 'rugby' ); * - * // from `` - * // to ` - * $tags->remove_class( 'rugby' ); - * ``` + * // from `` + * // to ` + * $tags->remove_class( 'rugby' ); + * + * // from `` + * // to ` + * $tags->remove_class( 'rugby' ); * * When class changes are enqueued but a direct change to `class` is made via * `set_attribute` then the changes to `set_attribute` (or `remove_attribute`) @@ -184,26 +181,24 @@ * and so on. It's fine from a performance standpoint to create a * bookmark and update it frequently, such as within a loop. * - * ```php - * $total_todos = 0; - * while ( $p->next_tag( array( 'tag_name' => 'UL', 'class_name' => 'todo' ) ) ) { - * $p->set_bookmark( 'list-start' ); - * while ( $p->next_tag( array( 'tag_closers' => 'visit' ) ) ) { - * if ( 'UL' === $p->get_tag() && $p->is_tag_closer() ) { - * $p->set_bookmark( 'list-end' ); - * $p->seek( 'list-start' ); - * $p->set_attribute( 'data-contained-todos', (string) $total_todos ); - * $total_todos = 0; - * $p->seek( 'list-end' ); - * break; - * } + * $total_todos = 0; + * while ( $p->next_tag( array( 'tag_name' => 'UL', 'class_name' => 'todo' ) ) ) { + * $p->set_bookmark( 'list-start' ); + * while ( $p->next_tag( array( 'tag_closers' => 'visit' ) ) ) { + * if ( 'UL' === $p->get_tag() && $p->is_tag_closer() ) { + * $p->set_bookmark( 'list-end' ); + * $p->seek( 'list-start' ); + * $p->set_attribute( 'data-contained-todos', (string) $total_todos ); + * $total_todos = 0; + * $p->seek( 'list-end' ); + * break; + * } * - * if ( 'LI' === $p->get_tag() && ! $p->is_tag_closer() ) { - * $total_todos++; + * if ( 'LI' === $p->get_tag() && ! $p->is_tag_closer() ) { + * $total_todos++; + * } * } * } - * } - * ``` * * ## Design and limitations * @@ -335,11 +330,10 @@ class Gutenberg_HTML_Tag_Processor_6_3 { * Byte offset in input document where current tag name starts. * * Example: - * ``` - *
... - * 01234 - * - tag name starts at 1 - * ``` + * + *
... + * 01234 + * - tag name starts at 1 * * @since 6.2.0 * @var int|null @@ -350,11 +344,10 @@ class Gutenberg_HTML_Tag_Processor_6_3 { * Byte length of current tag name. * * Example: - * ``` - *
... - * 01234 - * --- tag name length is 3 - * ``` + * + *
... + * 01234 + * --- tag name length is 3 * * @since 6.2.0 * @var int|null @@ -365,12 +358,11 @@ class Gutenberg_HTML_Tag_Processor_6_3 { * Byte offset in input document where current tag token ends. * * Example: - * ``` - *
... - * 0 1 | - * 01234567890123456 - * --- tag name ends at 14 - * ``` + * + *
... + * 0 1 | + * 01234567890123456 + * --- tag name ends at 14 * * @since 6.2.0 * @var int|null @@ -388,25 +380,24 @@ class Gutenberg_HTML_Tag_Processor_6_3 { * Lazily-built index of attributes found within an HTML tag, keyed by the attribute name. * * Example: - * ```php - * // supposing the parser is working through this content - * // and stops after recognizing the `id` attribute - * //
- * // ^ parsing will continue from this point - * $this->attributes = array( - * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 ) - * ); - * - * // when picking up parsing again, or when asking to find the - * // `class` attribute we will continue and add to this array - * $this->attributes = array( - * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 ), - * 'class' => new WP_HTML_Attribute_Match( 'class', 'outline', 18, 32 ) - * ); - * - * // Note that only the `class` attribute value is stored in the index. - * // That's because it is the only value used by this class at the moment. - * ``` + * + * // Supposing the parser is working through this content + * // and stops after recognizing the `id` attribute. + * //
+ * // ^ parsing will continue from this point. + * $this->attributes = array( + * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 ) + * ); + * + * // When picking up parsing again, or when asking to find the + * // `class` attribute we will continue and add to this array. + * $this->attributes = array( + * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 ), + * 'class' => new WP_HTML_Attribute_Match( 'class', 'outline', 18, 32 ) + * ); + * + * // Note that only the `class` attribute value is stored in the index. + * // That's because it is the only value used by this class at the moment. * * @since 6.2.0 * @var WP_HTML_Attribute_Token[] @@ -425,14 +416,13 @@ class Gutenberg_HTML_Tag_Processor_6_3 { * into a single `set_attribute( 'class', $changes )` call. * * Example: - * ```php - * // Add the `wp-block-group` class, remove the `wp-group` class. - * $classname_updates = array( - * // Indexed by a comparable class name - * 'wp-block-group' => WP_HTML_Tag_Processor::ADD_CLASS, - * 'wp-group' => WP_HTML_Tag_Processor::REMOVE_CLASS - * ); - * ``` + * + * // Add the `wp-block-group` class, remove the `wp-group` class. + * $classname_updates = array( + * // Indexed by a comparable class name. + * 'wp-block-group' => WP_HTML_Tag_Processor::ADD_CLASS, + * 'wp-group' => WP_HTML_Tag_Processor::REMOVE_CLASS + * ); * * @since 6.2.0 * @var bool[] @@ -479,18 +469,17 @@ class Gutenberg_HTML_Tag_Processor_6_3 { * copies when applying many updates to a single document. * * Example: - * ```php - * // Replace an attribute stored with a new value, indices - * // sourced from the lazily-parsed HTML recognizer. - * $start = $attributes['src']->start; - * $end = $attributes['src']->end; - * $modifications[] = new WP_HTML_Text_Replacement( $start, $end, $new_value ); - * - * // Correspondingly, something like this will appear in this array. - * $lexical_updates = array( - * WP_HTML_Text_Replacement( 14, 28, 'https://my-site.my-domain/wp-content/uploads/2014/08/kittens.jpg' ) - * ); - * ``` + * + * // Replace an attribute stored with a new value, indices + * // sourced from the lazily-parsed HTML recognizer. + * $start = $attributes['src']->start; + * $end = $attributes['src']->end; + * $modifications[] = new WP_HTML_Text_Replacement( $start, $end, $new_value ); + * + * // Correspondingly, something like this will appear in this array. + * $lexical_updates = array( + * WP_HTML_Text_Replacement( 14, 28, 'https://my-site.my-domain/wp-content/uploads/2014/08/kittens.jpg' ) + * ); * * @since 6.2.0 * @var WP_HTML_Text_Replacement[] @@ -608,7 +597,7 @@ public function next_tag( $query = null ) { * Release bookmarks when they are no longer needed. * * Example: - * ``` + * *

Surprising fact you may not know!

* ^ ^ * \-|-- this `H2` opener bookmark tracks the token @@ -616,14 +605,13 @@ public function next_tag( $query = null ) { *

Surprising fact you may no… * ^ ^ * \-|-- it shifts with edits - * ``` * * Bookmarks provide the ability to seek to a previously-scanned * place in the HTML document. This avoids the need to re-scan * the entire document. * * Example: - * ``` + * *
  • One
  • Two
  • Three
* ^^^^ * want to note this last item @@ -650,7 +638,6 @@ public function next_tag( $query = null ) { * $p->set_bookmark( 'last-li' ); * } * } - * ``` * * Bookmarks intentionally hide the internal string offsets * to which they refer. They are maintained internally as @@ -727,7 +714,7 @@ public function release_bookmark( $name ) { * * @see https://html.spec.whatwg.org/multipage/parsing.html#rcdata-state * - * @param string $tag_name – the lowercase tag name which will close the RCDATA region. + * @param string $tag_name The lowercase tag name which will close the RCDATA region. * @return bool Whether an end to the RCDATA region was found before the end of the document. */ private function skip_rcdata( $tag_name ) { @@ -1258,8 +1245,6 @@ private function parse_next_attribute() { * Move the internal cursor past any immediate successive whitespace. * * @since 6.2.0 - * - * @return void */ private function skip_whitespace() { $this->bytes_already_parsed += strspn( $this->html, " \t\f\r\n", $this->bytes_already_parsed ); @@ -1269,8 +1254,6 @@ private function skip_whitespace() { * Applies attribute updates and cleans up once a tag is fully parsed. * * @since 6.2.0 - * - * @return void */ private function after_tag() { $this->get_updated_html(); @@ -1289,8 +1272,6 @@ private function after_tag() { * * @see WP_HTML_Tag_Processor::$lexical_updates * @see WP_HTML_Tag_Processor::$classname_updates - * - * @return void */ private function class_name_updates_to_attributes_updates() { if ( count( $this->classname_updates ) === 0 ) { @@ -1630,10 +1611,9 @@ private function get_enqueued_attribute_value( $comparable_name ) { * or trailing whitespace, and that the casing follows the name given in `set_attribute`. * * Example: - * ``` + * * $p->set_attribute( 'data-TEST-id', 'update' ); * 'update' === $p->get_enqueued_attribute_value( 'data-test-id' ); - * ``` * * Detect this difference based on the absence of the `=`, which _must_ exist in any * attribute containing a value, e.g. ``. @@ -1664,16 +1644,15 @@ private function get_enqueued_attribute_value( $comparable_name ) { * Returns the value of a requested attribute from a matched tag opener if that attribute exists. * * Example: - * ```php - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag( array( 'class_name' => 'test' ) ) === true; - * $p->get_attribute( 'data-test-id' ) === '14'; - * $p->get_attribute( 'enabled' ) === true; - * $p->get_attribute( 'aria-label' ) === null; * - * $p->next_tag() === false; - * $p->get_attribute( 'class' ) === null; - * ``` + * $p = new WP_HTML_Tag_Processor( '
Test
' ); + * $p->next_tag( array( 'class_name' => 'test' ) ) === true; + * $p->get_attribute( 'data-test-id' ) === '14'; + * $p->get_attribute( 'enabled' ) === true; + * $p->get_attribute( 'aria-label' ) === null; + * + * $p->next_tag() === false; + * $p->get_attribute( 'class' ) === null; * * @since 6.2.0 * @@ -1745,14 +1724,13 @@ public function get_attribute( $name ) { * - HTML 5 spec * * Example: - * ```php - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag( array( 'class_name' => 'test' ) ) === true; - * $p->get_attribute_names_with_prefix( 'data-' ) === array( 'data-enabled', 'data-test-id' ); * - * $p->next_tag() === false; - * $p->get_attribute_names_with_prefix( 'data-' ) === null; - * ``` + * $p = new WP_HTML_Tag_Processor( '
Test
' ); + * $p->next_tag( array( 'class_name' => 'test' ) ) === true; + * $p->get_attribute_names_with_prefix( 'data-' ) === array( 'data-enabled', 'data-test-id' ); + * + * $p->next_tag() === false; + * $p->get_attribute_names_with_prefix( 'data-' ) === null; * * @since 6.2.0 * @@ -1781,14 +1759,13 @@ function get_attribute_names_with_prefix( $prefix ) { * Returns the uppercase name of the matched tag. * * Example: - * ```php - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag() === true; - * $p->get_tag() === 'DIV'; * - * $p->next_tag() === false; - * $p->get_tag() === null; - * ``` + * $p = new WP_HTML_Tag_Processor( '
Test
' ); + * $p->next_tag() === true; + * $p->get_tag() === 'DIV'; + * + * $p->next_tag() === false; + * $p->get_tag() === null; * * @since 6.2.0 * @@ -1833,14 +1810,13 @@ public function has_self_closing_flag() { * Indicates if the current tag token is a tag closer. * * Example: - * ```php - * $p = new WP_HTML_Tag_Processor( '
' ); - * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); - * $p->is_tag_closer() === false; * - * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); - * $p->is_tag_closer() === true; - * ``` + * $p = new WP_HTML_Tag_Processor( '
' ); + * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); + * $p->is_tag_closer() === false; + * + * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); + * $p->is_tag_closer() === true; * * @since 6.2.0 * @@ -1946,12 +1922,13 @@ public function set_attribute( $name, $value ) { * Update an existing attribute. * * Example – set attribute id to "new" in
: - *
- * ^-------------^ - * start end - * replacement: `id="new"` * - * Result:
+ *
+ * ^-------------^ + * start end + * replacement: `id="new"` + * + * Result:
*/ $existing_attribute = $this->attributes[ $comparable_name ]; $this->lexical_updates[ $comparable_name ] = new WP_HTML_Text_Replacement( @@ -1964,12 +1941,13 @@ public function set_attribute( $name, $value ) { * Create a new attribute at the tag's name end. * * Example – add attribute id="new" to
: - *
- * ^ - * start and end - * replacement: ` id="new"` * - * Result:
+ *
+ * ^ + * start and end + * replacement: ` id="new"` + * + * Result:
*/ $this->lexical_updates[ $comparable_name ] = new WP_HTML_Text_Replacement( $this->tag_name_starts_at + $this->tag_name_length, @@ -2183,7 +2161,6 @@ public function get_updated_html() { * @type string|null $class_name Tag must contain this class name to match. * @type string $tag_closers "visit" or "skip": whether to stop on tag closers, e.g.
. * } - * @return void */ private function parse_query( $query ) { if ( null !== $query && $query === $this->last_query ) {