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 @@ -44,6 +44,18 @@ class WP_HTML_Active_Formatting_Elements {
*/
private $stack = array();

/**
* Returns the node at the given 1-offset index in the list of active formatting elements.
*
* @since 7.0.0
*
* @param int $nth Number of nodes from the top node to return.
* @return WP_HTML_Token|null Node at the given index in the stack, if one exists, otherwise null.
*/
public function at( int $nth ): ?WP_HTML_Token {
return $this->stack[ $nth - 1 ] ?? null;
}

/**
* Reports if a specific node is in the stack of active formatting elements.
*
Expand Down Expand Up @@ -111,8 +123,9 @@ public function insert_marker(): void {
* @see https://html.spec.whatwg.org/#push-onto-the-list-of-active-formatting-elements
*
* @param WP_HTML_Token $token Push this node onto the stack.
* @return bool Whether a node was pushed onto the stack of active formatting elements.
*/
public function push( WP_HTML_Token $token ) {
public function push( WP_HTML_Token $token ): bool {
/*
* > 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
Expand All @@ -121,11 +134,31 @@ public function push( WP_HTML_Token $token ) {
* > 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).
*
* @todo Implement the "Noah's Ark clause" to only add up to three of any given kind of formatting elements to the stack.
*/
if ( 'marker' !== $token->node_name ) {
$existing_count = 0;
foreach ( $this->walk_up() as $item ) {
if ( 'marker' === $item->node_name ) {
break;
}

if (
$item->node_name === $token->node_name &&
$item->namespace === $token->namespace
// @todo Compare attributes. For now, bail if there are three matching tag names + namespaces.
) {
++$existing_count;
if ( $existing_count >= 3 ) {
// @todo Implement removing the earliest element and moving forward.
return false;
}
}
}
}

// > Add element to the list of active formatting elements.
$this->stack[] = $token;
return true;
}

/**
Expand Down
17 changes: 14 additions & 3 deletions src/wp-includes/html-api/class-wp-html-open-elements.php
Original file line number Diff line number Diff line change
Expand Up @@ -614,12 +614,23 @@ public function remove_node( WP_HTML_Token $token ): bool {
* see WP_HTML_Open_Elements::walk_up().
*
* @since 6.4.0
* @since 7.0.0 Accepts $below_this_node to start traversal below a given node, if it exists.
*
* @param WP_HTML_Token|null $below_this_node Start traversing below this node, if provided and if the node exists.
*/
public function walk_down() {
$count = count( $this->stack );
public function walk_down( ?WP_HTML_Token $below_this_node = null ) {
$has_found_node = null === $below_this_node;
$count = count( $this->stack );

for ( $i = 0; $i < $count; $i++ ) {
yield $this->stack[ $i ];
$node = $this->stack[ $i ];

if ( ! $has_found_node ) {
$has_found_node = $node === $below_this_node;
continue;
}

yield $node;
}
}

Expand Down
Loading
Loading