| We are here';
+ * foreach ( $stack->walk_up_until_marker() as $node ) {
+ * echo "{$node->node_name} -> ";
+ * }
+ * > I
+ *
+ * @since 6.7.0
+ */
+ public function walk_up_until_marker() {
+ foreach ( $this->walk_up() as $item ) {
+ if ( $item instanceof AFE_Marker ) {
+ break;
+ }
+
+ yield $item;
+ }
+ }
+
/**
* Clears the list of active formatting elements up to the last marker.
*
@@ -237,9 +272,45 @@ public function walk_up() {
public function clear_up_to_last_marker(): void {
foreach ( $this->walk_up() as $item ) {
array_pop( $this->stack );
- if ( 'marker' === $item->node_name ) {
+ if ( $item instanceof AFE_Marker ) {
break;
}
}
}
}
+
+class AFE_Marker {}
+class AFE_Element {
+ /** @var string */
+ public $namespace;
+ /** @var string */
+ public $tag_name;
+ /** @var array */
+ public $attributes;
+ /** @var WP_HTML_Token */
+ public $token;
+
+ public function is_equivalent( self $afe ): bool {
+ if (
+ $this->namespace !== $afe->namespace ||
+ $this->tag_name !== $afe->tag_name ||
+ count( $this->attributes ) !== count( $afe->attributes )
+ ) {
+ return false;
+ }
+
+ foreach ( $this->attributes as $name => $value ) {
+ if ( ! array_key_exists( $name, $afe->attributes ) || $value !== $afe->attributes[ $name ] ) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public function __construct( string $tag_namespace, string $tag_name, array $attributes, WP_HTML_Token $token ) {
+ $this->namespace = $tag_namespace;
+ $this->tag_name = $tag_name;
+ $this->attributes = $attributes;
+ $this->token = $token;
+ }
+}
diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php
index cd3fa57fd7860..cfcb809abdb93 100644
--- a/src/wp-includes/html-api/class-wp-html-processor.php
+++ b/src/wp-includes/html-api/class-wp-html-processor.php
@@ -442,7 +442,7 @@ private function bail( string $message ) {
$active_formats = array();
foreach ( $this->state->active_formatting_elements->walk_down() as $item ) {
- $active_formats[] = $item->node_name;
+ $active_formats[] = $item instanceof AFE_Marker ? '(marker)' : $item->tag_name;
}
$this->last_error = self::ERROR_UNSUPPORTED;
@@ -2350,21 +2350,21 @@ private function step_in_body(): bool {
*/
case '+A':
foreach ( $this->state->active_formatting_elements->walk_up() as $item ) {
- switch ( $item->node_name ) {
- case 'marker':
- break;
-
+ if ( $item instanceof AFE_Marker ) {
+ break;
+ }
+ switch ( $item->tag_name ) {
case 'A':
$this->run_adoption_agency_algorithm();
$this->state->active_formatting_elements->remove_node( $item );
- $this->state->stack_of_open_elements->remove_node( $item );
- break;
+ $this->state->stack_of_open_elements->remove_node( $item->token );
+ break 2;
}
}
$this->reconstruct_active_formatting_elements();
$this->insert_html_element( $this->state->current_token );
- $this->state->active_formatting_elements->push( $this->state->current_token );
+ $this->push_active_formatting_element( $this->state->current_token );
return true;
/*
@@ -2385,7 +2385,7 @@ private function step_in_body(): bool {
case '+U':
$this->reconstruct_active_formatting_elements();
$this->insert_html_element( $this->state->current_token );
- $this->state->active_formatting_elements->push( $this->state->current_token );
+ $this->push_active_formatting_element( $this->state->current_token );
return true;
/*
@@ -2401,7 +2401,7 @@ private function step_in_body(): bool {
}
$this->insert_html_element( $this->state->current_token );
- $this->state->active_formatting_elements->push( $this->state->current_token );
+ $this->push_active_formatting_element( $this->state->current_token );
return true;
/*
@@ -2415,6 +2415,7 @@ private function step_in_body(): bool {
case '-EM':
case '-FONT':
case '-I':
+ case '-NOBR':
case '-S':
case '-SMALL':
case '-STRIKE':
@@ -5121,7 +5122,7 @@ public function seek( $bookmark_name ): bool {
}
foreach ( $this->state->active_formatting_elements->walk_up() as $item ) {
- if ( 'context-node' === $item->bookmark_name ) {
+ if ( 'context-node' === $item->token->bookmark_name ) {
break;
}
@@ -5401,8 +5402,8 @@ private function reconstruct_active_formatting_elements(): bool {
* > elements, then there is nothing to reconstruct; stop this algorithm.
*/
if (
- 'marker' === $last_entry->node_name ||
- $this->state->stack_of_open_elements->contains_node( $last_entry )
+ $last_entry instanceof AFE_Marker ||
+ $this->state->stack_of_open_elements->contains_node( $last_entry->token )
) {
return false;
}
@@ -5433,8 +5434,8 @@ private function reconstruct_active_formatting_elements(): bool {
* > the stack of open elements, go to the step labeled rewind.
*/
if (
- 'marker' !== $entry->node_name &&
- ! $this->state->stack_of_open_elements->contains_node( $entry )
+ ! $entry instanceof AFE_Marker &&
+ ! $this->state->stack_of_open_elements->contains_node( $entry->token )
) {
goto rewind;
}
@@ -5451,7 +5452,10 @@ private function reconstruct_active_formatting_elements(): bool {
* > element entry was created, to obtain new element.
*/
create:
- $this->insert_html_element( $entry );
+ if ( array() !== $entry->attributes ) {
+ $this->bail( 'Cannot create active formatting elements with attributes.' );
+ }
+ $this->insert_html_element( $entry->token );
/*
* > 9. Replace the entry for entry in the list with an entry for new element.
@@ -5690,11 +5694,11 @@ private function run_adoption_agency_algorithm(): void {
*/
$formatting_element = null;
foreach ( $this->state->active_formatting_elements->walk_up() as $item ) {
- if ( 'marker' === $item->node_name ) {
+ if ( $item instanceof AFE_Marker ) {
break;
}
- if ( $subject === $item->node_name ) {
+ if ( $subject === $item->tag_name ) {
$formatting_element = $item;
break;
}
@@ -5706,13 +5710,13 @@ private function run_adoption_agency_algorithm(): void {
}
// > If formatting element is not in the stack of open elements, then this is a parse error; remove the element from the list, and return.
- if ( ! $this->state->stack_of_open_elements->contains_node( $formatting_element ) ) {
+ if ( ! $this->state->stack_of_open_elements->contains_node( $formatting_element->token ) ) {
$this->state->active_formatting_elements->remove_node( $formatting_element );
return;
}
// > If formatting element is in the stack of open elements, but the element is not in scope, then this is a parse error; return.
- if ( ! $this->state->stack_of_open_elements->has_element_in_scope( $formatting_element->node_name ) ) {
+ if ( ! $this->state->stack_of_open_elements->has_element_in_scope( $formatting_element->tag_name ) ) {
return;
}
@@ -5723,7 +5727,7 @@ private function run_adoption_agency_algorithm(): void {
$is_above_formatting_element = true;
$furthest_block = null;
foreach ( $this->state->stack_of_open_elements->walk_down() as $item ) {
- if ( $is_above_formatting_element && $formatting_element->bookmark_name !== $item->bookmark_name ) {
+ if ( $is_above_formatting_element && $formatting_element->token->bookmark_name !== $item->bookmark_name ) {
continue;
}
@@ -5747,7 +5751,7 @@ private function run_adoption_agency_algorithm(): void {
foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) {
$this->state->stack_of_open_elements->pop();
- if ( $formatting_element->bookmark_name === $item->bookmark_name ) {
+ if ( $formatting_element->token->bookmark_name === $item->bookmark_name ) {
$this->state->active_formatting_elements->remove_node( $formatting_element );
return;
}
@@ -6211,4 +6215,24 @@ protected static function get_encoding( string $label ): ?string {
* @access private
*/
const CONSTRUCTOR_UNLOCK_CODE = 'Use WP_HTML_Processor::create_fragment() instead of calling the class constructor directly.';
+
+ private function push_active_formatting_element( WP_HTML_Token $token ) {
+ $bookmark = $this->bookmarks[ $token->bookmark_name ];
+ $proc = new WP_HTML_Tag_Processor(
+ substr( $this->html, $bookmark->start, $bookmark->length )
+ );
+ $proc->change_parsing_namespace( $token->namespace );
+ $proc->next_tag();
+ $attributes = array();
+ foreach ( $proc->get_attribute_names_with_prefix( '' ) as $name ) {
+ $attributes[ $name ] = $proc->get_attribute( $name );
+ }
+ $afe = new AFE_Element(
+ $token->namespace,
+ $token->node_name,
+ $attributes,
+ $token
+ );
+ $this->state->active_formatting_elements->push( $afe );
+ }
}
diff --git a/src/wp-includes/html-api/class-wp-html-token.php b/src/wp-includes/html-api/class-wp-html-token.php
index d5e51ac29007f..cd0a9efa12e71 100644
--- a/src/wp-includes/html-api/class-wp-html-token.php
+++ b/src/wp-includes/html-api/class-wp-html-token.php
@@ -31,7 +31,7 @@ class WP_HTML_Token {
*
* @var string
*/
- public $bookmark_name = null;
+ public $bookmark_name;
/**
* Name of node; lowercase names such as "marker" are not HTML elements.
@@ -90,13 +90,13 @@ class WP_HTML_Token {
*
* @since 6.4.0
*
- * @param string|null $bookmark_name Name of bookmark corresponding to location in HTML where token is found,
+ * @param string $bookmark_name Name of bookmark corresponding to location in HTML where token is found,
* or `null` for markers and nodes without a bookmark.
* @param string $node_name Name of node token represents; if uppercase, an HTML element; if lowercase, a special value like "marker".
* @param bool $has_self_closing_flag Whether the source token contains the self-closing flag, regardless of whether it's valid.
* @param callable|null $on_destroy Optional. Function to call when destroying token, useful for releasing the bookmark.
*/
- public function __construct( ?string $bookmark_name, string $node_name, bool $has_self_closing_flag, ?callable $on_destroy = null ) {
+ public function __construct( string $bookmark_name, string $node_name, bool $has_self_closing_flag, ?callable $on_destroy = null ) {
$this->bookmark_name = $bookmark_name;
$this->namespace = 'html';
$this->node_name = $node_name;
|