diff --git a/wp-includes/html-api/class-wp-html-tag-processor.php b/wp-includes/html-api/class-wp-html-tag-processor.php index 8c41e73215..3d0ad82519 100644 --- a/wp-includes/html-api/class-wp-html-tag-processor.php +++ b/wp-includes/html-api/class-wp-html-tag-processor.php @@ -317,23 +317,6 @@ class WP_HTML_Tag_Processor { */ private $stop_on_tag_closers; - /** - * Holds updated HTML as updates are applied. - * - * Updates and unmodified portions of the input document are - * appended to this value as they are applied. It will hold - * a copy of the updated document up until the point of the - * latest applied update. The fully-updated HTML document - * will comprise this value plus the part of the input document - * which follows that latest update. - * - * @see $bytes_already_copied - * - * @since 6.2.0 - * @var string - */ - private $output_buffer = ''; - /** * How many bytes from the original HTML document have been read and parsed. * @@ -346,23 +329,6 @@ class WP_HTML_Tag_Processor { */ private $bytes_already_parsed = 0; - /** - * How many bytes from the input HTML document have already been - * copied into the output buffer. - * - * Lexical updates are enqueued and processed in batches. Prior - * to any given update in the input document, there might exist - * a span of HTML unaffected by any changes. This span ought to - * be copied verbatim into the output buffer before applying the - * following update. This value will point to the starting byte - * offset in the input document where that unaffected span of - * HTML starts. - * - * @since 6.2.0 - * @var int - */ - private $bytes_already_copied = 0; - /** * Byte offset in input document where current tag name starts. * @@ -1303,8 +1269,7 @@ class WP_HTML_Tag_Processor { * @return void */ private function after_tag() { - $this->class_name_updates_to_attributes_updates(); - $this->apply_attributes_updates(); + $this->get_updated_html(); $this->tag_name_starts_at = null; $this->tag_name_length = null; $this->tag_ends_at = null; @@ -1460,15 +1425,19 @@ class WP_HTML_Tag_Processor { * Applies attribute updates to HTML document. * * @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. * - * @return void + * @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. */ - private function apply_attributes_updates() { + private function apply_attributes_updates( $shift_this_point = 0 ) { if ( ! count( $this->lexical_updates ) ) { - return; + return 0; } + $accumulated_shift_for_given_point = 0; + /* * Attribute updates can be enqueued in any order but updates * to the document must occur in lexical order; that is, each @@ -1481,12 +1450,28 @@ class WP_HTML_Tag_Processor { */ usort( $this->lexical_updates, array( self::class, 'sort_start_ascending' ) ); + $bytes_already_copied = 0; + $output_buffer = ''; foreach ( $this->lexical_updates as $diff ) { - $this->output_buffer .= substr( $this->html, $this->bytes_already_copied, $diff->start - $this->bytes_already_copied ); - $this->output_buffer .= $diff->text; - $this->bytes_already_copied = $diff->end; + $shift = strlen( $diff->text ) - ( $diff->end - $diff->start ); + + // Adjust the cursor position by however much an update affects it. + if ( $diff->start <= $this->bytes_already_parsed ) { + $this->bytes_already_parsed += $shift; + } + + // Accumulate shift of the given pointer within this function call. + if ( $diff->start <= $shift_this_point ) { + $accumulated_shift_for_given_point += $shift; + } + + $output_buffer .= substr( $this->html, $bytes_already_copied, $diff->start - $bytes_already_copied ); + $output_buffer .= $diff->text; + $bytes_already_copied = $diff->end; } + $this->html = $output_buffer . substr( $this->html, $bytes_already_copied ); + /* * Adjust bookmark locations to account for how the text * replacements adjust offsets in the input document. @@ -1527,6 +1512,8 @@ class WP_HTML_Tag_Processor { } $this->lexical_updates = array(); + + return $accumulated_shift_for_given_point; } /** @@ -1576,8 +1563,6 @@ class WP_HTML_Tag_Processor { // Point this tag processor before the sought tag opener and consume it. $this->bytes_already_parsed = $this->bookmarks[ $bookmark_name ]->start; - $this->bytes_already_copied = $this->bytes_already_parsed; - $this->output_buffer = substr( $this->html, 0, $this->bytes_already_copied ); return $this->next_tag( array( 'tag_closers' => 'visit' ) ); } @@ -2122,6 +2107,7 @@ class WP_HTML_Tag_Processor { * Returns the string representation of the HTML Tag Processor. * * @since 6.2.0 + * @since 6.2.1 Shifts the internal cursor corresponding to the applied updates. * * @return string The processed HTML. */ @@ -2132,46 +2118,24 @@ class WP_HTML_Tag_Processor { * When there is nothing more to update and nothing has already been * updated, return the original document and avoid a string copy. */ - if ( $requires_no_updating && 0 === $this->bytes_already_copied ) { + if ( $requires_no_updating ) { return $this->html; } /* - * If there are no updates left to apply, but some have already - * been applied, then finish by copying the rest of the input - * to the end of the updated document and return. + * Keep track of the position right before the current tag. This will + * be necessary for reparsing the current tag after updating the HTML. */ - if ( $requires_no_updating && $this->bytes_already_copied > 0 ) { - $this->html = $this->output_buffer . substr( $this->html, $this->bytes_already_copied ); - $this->bytes_already_copied = strlen( $this->output_buffer ); - return $this->output_buffer . substr( $this->html, $this->bytes_already_copied ); - } - - // Apply the updates, rewind to before the current tag, and reparse the attributes. - $content_up_to_opened_tag_name = $this->output_buffer . substr( - $this->html, - $this->bytes_already_copied, - $this->tag_name_starts_at + $this->tag_name_length - $this->bytes_already_copied - ); + $before_current_tag = $this->tag_name_starts_at - 1; /* - * 1. Apply the edits by flushing them to the output buffer and updating the copied byte count. - * - * Note: `apply_attributes_updates()` modifies `$this->output_buffer`. + * 1. Apply the enqueued edits and update all the pointers to reflect those changes. */ $this->class_name_updates_to_attributes_updates(); - $this->apply_attributes_updates(); + $before_current_tag += $this->apply_attributes_updates( $before_current_tag ); /* - * 2. Replace the original HTML with the now-updated HTML so that it's possible to - * seek to a previous location and have a consistent view of the updated document. - */ - $this->html = $this->output_buffer . substr( $this->html, $this->bytes_already_copied ); - $this->output_buffer = $content_up_to_opened_tag_name; - $this->bytes_already_copied = strlen( $this->output_buffer ); - - /* - * 3. Point this tag processor at the original tag opener and consume it + * 2. Rewind to before the current tag and reparse to get updated attributes. * * At this point the internal cursor points to the end of the tag name. * Rewind before the tag name starts so that it's as if the cursor didn't @@ -2183,9 +2147,19 @@ class WP_HTML_Tag_Processor { * ^ | back up by the length of the tag name plus the opening < * \<-/ back up by strlen("em") + 1 ==> 3 */ - $this->bytes_already_parsed = strlen( $content_up_to_opened_tag_name ) - $this->tag_name_length - 1; + + // Store existing state so it can be restored after reparsing. + $previous_parsed_byte_count = $this->bytes_already_parsed; + $previous_query = $this->last_query; + + // Reparse attributes. + $this->bytes_already_parsed = $before_current_tag; $this->next_tag(); + // Restore previous state. + $this->bytes_already_parsed = $previous_parsed_byte_count; + $this->parse_query( $previous_query ); + return $this->html; } diff --git a/wp-includes/version.php b/wp-includes/version.php index 7d76aae0ed..1993cd9570 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.3-alpha-55705'; +$wp_version = '6.3-alpha-55706'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.