WP_HTML_Processor::seekpublicWP 6.4.0

Moves the internal cursor in the HTML Processor to a given bookmark's location.

Be careful! Seeking backwards to a previous location resets the parser to the start of the document and reparses the entire contents up until it finds the sought-after bookmarked location.

In order to prevent accidental infinite loops, there's a maximum limit on the number of times seek() can be called.

Method of the class: WP_HTML_Processor{}

No Hooks.

Returns

true|false. Whether the internal cursor was successfully moved to the bookmark's location.

Usage

$WP_HTML_Processor = new WP_HTML_Processor();
$WP_HTML_Processor->seek( $bookmark_name ): bool;
$bookmark_name(string) (required)
Jump to the place in the document identified by this bookmark name.

Changelog

Since 6.4.0 Introduced.

WP_HTML_Processor::seek() code WP 6.8.1

public function seek( $bookmark_name ): bool {
	// Flush any pending updates to the document before beginning.
	$this->get_updated_html();

	$actual_bookmark_name = "_{$bookmark_name}";
	$processor_started_at = $this->state->current_token
		? $this->bookmarks[ $this->state->current_token->bookmark_name ]->start
		: 0;
	$bookmark_starts_at   = $this->bookmarks[ $actual_bookmark_name ]->start;
	$direction            = $bookmark_starts_at > $processor_started_at ? 'forward' : 'backward';

	/*
	 * If seeking backwards, it's possible that the sought-after bookmark exists within an element
	 * which has been closed before the current cursor; in other words, it has already been removed
	 * from the stack of open elements. This means that it's insufficient to simply pop off elements
	 * from the stack of open elements which appear after the bookmarked location and then jump to
	 * that location, as the elements which were open before won't be re-opened.
	 *
	 * In order to maintain consistency, the HTML Processor rewinds to the start of the document
	 * and reparses everything until it finds the sought-after bookmark.
	 *
	 * There are potentially better ways to do this: cache the parser state for each bookmark and
	 * restore it when seeking; store an immutable and idempotent register of where elements open
	 * and close.
	 *
	 * If caching the parser state it will be essential to properly maintain the cached stack of
	 * open elements and active formatting elements when modifying the document. This could be a
	 * tedious and time-consuming process as well, and so for now will not be performed.
	 *
	 * It may be possible to track bookmarks for where elements open and close, and in doing so
	 * be able to quickly recalculate breadcrumbs for any element in the document. It may even
	 * be possible to remove the stack of open elements and compute it on the fly this way.
	 * If doing this, the parser would need to track the opening and closing locations for all
	 * tokens in the breadcrumb path for any and all bookmarks. By utilizing bookmarks themselves
	 * this list could be automatically maintained while modifying the document. Finding the
	 * breadcrumbs would then amount to traversing that list from the start until the token
	 * being inspected. Once an element closes, if there are no bookmarks pointing to locations
	 * within that element, then all of these locations may be forgotten to save on memory use
	 * and computation time.
	 */
	if ( 'backward' === $direction ) {

		/*
		 * When moving backward, stateful stacks should be cleared.
		 */
		foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) {
			$this->state->stack_of_open_elements->remove_node( $item );
		}

		foreach ( $this->state->active_formatting_elements->walk_up() as $item ) {
			$this->state->active_formatting_elements->remove_node( $item );
		}

		/*
		 * **After** clearing stacks, more processor state can be reset.
		 * This must be done after clearing the stack because those stacks generate events that
		 * would appear on a subsequent call to `next_token()`.
		 */
		$this->state->frameset_ok                       = true;
		$this->state->stack_of_template_insertion_modes = array();
		$this->state->head_element                      = null;
		$this->state->form_element                      = null;
		$this->state->current_token                     = null;
		$this->current_element                          = null;
		$this->element_queue                            = array();

		/*
		 * The absence of a context node indicates a full parse.
		 * The presence of a context node indicates a fragment parser.
		 */
		if ( null === $this->context_node ) {
			$this->change_parsing_namespace( 'html' );
			$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_INITIAL;
			$this->breadcrumbs           = array();

			$this->bookmarks['initial'] = new WP_HTML_Span( 0, 0 );
			parent::seek( 'initial' );
			unset( $this->bookmarks['initial'] );
		} else {

			/*
			 * Push the root-node (HTML) back onto the stack of open elements.
			 *
			 * Fragment parsers require this extra bit of setup.
			 * It's handled in full parsers by advancing the processor state.
			 */
			$this->state->stack_of_open_elements->push(
				new WP_HTML_Token(
					'root-node',
					'HTML',
					false
				)
			);

			$this->change_parsing_namespace(
				$this->context_node->integration_node_type
					? 'html'
					: $this->context_node->namespace
			);

			if ( 'TEMPLATE' === $this->context_node->node_name ) {
				$this->state->stack_of_template_insertion_modes[] = WP_HTML_Processor_State::INSERTION_MODE_IN_TEMPLATE;
			}

			$this->reset_insertion_mode_appropriately();
			$this->breadcrumbs = array_slice( $this->breadcrumbs, 0, 2 );
			parent::seek( $this->context_node->bookmark_name );
		}
	}

	/*
	 * Here, the processor moves forward through the document until it matches the bookmark.
	 * do-while is used here because the processor is expected to already be stopped on
	 * a token than may match the bookmarked location.
	 */
	do {
		/*
		 * The processor will stop on virtual tokens, but bookmarks may not be set on them.
		 * They should not be matched when seeking a bookmark, skip them.
		 */
		if ( $this->is_virtual() ) {
			continue;
		}
		if ( $bookmark_starts_at === $this->bookmarks[ $this->state->current_token->bookmark_name ]->start ) {
			return true;
		}
	} while ( $this->next_token() );

	return false;
}