Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,11 @@ public function insert_marker(): void {
*/
public function push( WP_HTML_Token $token ) {
/*
* > If there are already three elements in the list of active formatting elements after the last marker,
* > if any, or anywhere in the list if there are no markers, that have the same tag name, namespace, and
* > attributes as element, then remove the earliest such element from the list of active formatting
* > elements. For these purposes, the attributes must be compared as they were when the elements were
* > created by the parser; two elements have the same attributes if all their parsed attributes can be
* > paired such that the two attributes in each pair have identical names, namespaces, and values
* > (the order of the attributes does not matter).
* The "Noah's Ark clause", which limits the list to three elements sharing
* a tag name, namespace, and attributes, requires reading the attributes
* of the source tags and is enforced by the HTML Processor before pushing.
*
* @todo Implement the "Noah's Ark clause" to only add up to three of any given kind of formatting elements to the stack.
* @see WP_HTML_Processor::push_onto_active_formatting_elements
*/
// > Add element to the list of active formatting elements.
$this->stack[] = $token;
Expand Down Expand Up @@ -150,6 +146,82 @@ public function remove_node( WP_HTML_Token $token ) {
return false;
}

/**
* Returns the position of a node in the list of active formatting elements.
*
* Positions are counted from the start of the list: the earliest entry
* is at position zero.
*
* @since 7.1.0
*
* @param WP_HTML_Token $token Find this node in the list of active formatting elements.
* @return int|null Position of the node, or `null` if it isn't in the list.
*/
public function position_of( WP_HTML_Token $token ): ?int {
foreach ( $this->stack as $position => $item ) {
if ( $token === $item ) {
return $position;
}
}

return null;
}

/**
* Removes the node at the given position in the list of active formatting elements.
*
* @since 7.1.0
*
* @param int $position Remove the node at this position, counting from the start of the list.
* @return bool Whether a node was removed, false when the position was out of range.
*/
public function remove_at( int $position ): bool {
if ( $position < 0 || $position >= count( $this->stack ) ) {
return false;
}

array_splice( $this->stack, $position, 1 );
return true;
}

/**
* Inserts a node at the given position in the list of active formatting elements.
*
* A node inserted at position zero becomes the earliest entry in the list,
* while one inserted at the position returned by {@see self::count} becomes
* the last (most recently added) entry.
*
* @since 7.1.0
*
* @param int $position Insert the node at this position, counting from the start of the list.
* @param WP_HTML_Token $token Insert this node.
*/
public function insert_at( int $position, WP_HTML_Token $token ): void {
array_splice( $this->stack, $position, 0, array( $token ) );
}

/**
* Replaces a node in the list of active formatting elements with another node.
*
* This is distinct from removing the existing node and pushing the new one:
* the replacement occupies the exact position of the node it replaces.
*
* @since 7.1.0
*
* @param WP_HTML_Token $old_node Node to find and replace.
* @param WP_HTML_Token $new_node Node to substitute in its place.
* @return bool Whether the node was found and replaced.
*/
public function replace_node( WP_HTML_Token $old_node, WP_HTML_Token $new_node ): bool {
$position = $this->position_of( $old_node );
if ( null === $position ) {
return false;
}

$this->stack[ $position ] = $new_node;
return true;
}

/**
* Steps through the stack of active formatting elements, starting with the
* top element (added first) and walking downwards to the one added last.
Expand Down
96 changes: 73 additions & 23 deletions src/wp-includes/html-api/class-wp-html-open-elements.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,45 @@
* @see WP_HTML_Processor
*/
class WP_HTML_Open_Elements {
/**
* Elements which terminate the search when determining whether an
* element is "in scope".
*
* > The stack of open elements is said to have a particular element in
* > scope when it has that element in the specific scope consisting of
* > the following element types: …
*
* @since 7.1.0
*
* @see https://html.spec.whatwg.org/#has-an-element-in-scope
* @see WP_HTML_Open_Elements::has_element_in_scope
* @see WP_HTML_Open_Elements::has_node_in_scope
*
* @var string[]
*/
const ELEMENT_IN_SCOPE_TERMINATION_LIST = array(
'APPLET',
'CAPTION',
'HTML',
'TABLE',
'TD',
'TH',
'MARQUEE',
'OBJECT',
'TEMPLATE',

'math MI',
'math MO',
'math MN',
'math MS',
'math MTEXT',
'math ANNOTATION-XML',

'svg FOREIGNOBJECT',
'svg DESC',
'svg TITLE',
);

/**
* Holds the stack of open element references.
*
Expand Down Expand Up @@ -301,31 +340,42 @@ public function has_element_in_specific_scope( string $tag_name, $termination_li
* @return bool Whether given element is in scope.
*/
public function has_element_in_scope( string $tag_name ): bool {
return $this->has_element_in_specific_scope(
$tag_name,
array(
'APPLET',
'CAPTION',
'HTML',
'TABLE',
'TD',
'TH',
'MARQUEE',
'OBJECT',
'TEMPLATE',
return $this->has_element_in_specific_scope( $tag_name, self::ELEMENT_IN_SCOPE_TERMINATION_LIST );
}

'math MI',
'math MO',
'math MN',
'math MS',
'math MTEXT',
'math ANNOTATION-XML',
/**
* Returns whether a specific node is in scope.
*
* Whereas {@see self::has_element_in_scope} reports whether *any* element
* of a given tag name is in scope, this reports whether the given node
* itself is. The two may disagree when multiple elements sharing the tag
* name are in the stack of open elements: the adoption agency algorithm,
* for example, must determine whether a specific formatting element is in
* scope, regardless of other elements with the same tag name.
*
* @since 7.1.0
*
* @see https://html.spec.whatwg.org/#has-an-element-in-scope
*
* @param WP_HTML_Token $token Check whether this node is in scope.
* @return bool Whether the given node is in scope.
*/
public function has_node_in_scope( WP_HTML_Token $token ): bool {
foreach ( $this->walk_up() as $node ) {
if ( $token === $node ) {
return true;
}

'svg FOREIGNOBJECT',
'svg DESC',
'svg TITLE',
)
);
$namespaced_name = 'html' === $node->namespace
? $node->node_name
: "{$node->namespace} {$node->node_name}";

if ( in_array( $namespaced_name, self::ELEMENT_IN_SCOPE_TERMINATION_LIST, true ) ) {
return false;
}
}

return false;
}

/**
Expand Down
Loading
Loading