WP_HTML_Processor::step_in_foreign_content()privateWP 6.7.0

Parses next element in the 'in foreign content' insertion mode.

This internal function performs the 'in foreign content' insertion mode logic for the generalized WP_HTML_Processor::step() function.

Method of the class: WP_HTML_Processor{}

No Hooks.

Return

true|false. Whether an element was found.

Usage

// private - for code of main (parent) class only
$result = $this->step_in_foreign_content(): bool;

Notes

Changelog

Since 6.7.0 Introduced.
Since 6.7.0 Stub implementation.

WP_HTML_Processor::step_in_foreign_content() code WP 6.7.1

private function step_in_foreign_content(): bool {
	$tag_name   = $this->get_token_name();
	$token_type = $this->get_token_type();
	$op_sigil   = '#tag' === $token_type ? ( $this->is_tag_closer() ? '-' : '+' ) : '';
	$op         = "{$op_sigil}{$tag_name}";

	/*
	 * > A start tag whose name is "font", if the token has any attributes named "color", "face", or "size"
	 *
	 * This section drawn out above the switch to more easily incorporate
	 * the additional rules based on the presence of the attributes.
	 */
	if (
		'+FONT' === $op &&
		(
			null !== $this->get_attribute( 'color' ) ||
			null !== $this->get_attribute( 'face' ) ||
			null !== $this->get_attribute( 'size' )
		)
	) {
		$op = '+FONT with attributes';
	}

	switch ( $op ) {
		case '#text':
			/*
			 * > A character token that is U+0000 NULL
			 *
			 * This is handled by `get_modifiable_text()`.
			 */

			/*
			 * Whitespace-only text does not affect the frameset-ok flag.
			 * It is probably inter-element whitespace, but it may also
			 * contain character references which decode only to whitespace.
			 */
			if ( parent::TEXT_IS_GENERIC === $this->text_node_classification ) {
				$this->state->frameset_ok = false;
			}

			$this->insert_foreign_element( $this->state->current_token, false );
			return true;

		/*
		 * CDATA sections are alternate wrappers for text content and therefore
		 * ought to follow the same rules as text nodes.
		 */
		case '#cdata-section':
			/*
			 * NULL bytes and whitespace do not change the frameset-ok flag.
			 */
			$current_token        = $this->bookmarks[ $this->state->current_token->bookmark_name ];
			$cdata_content_start  = $current_token->start + 9;
			$cdata_content_length = $current_token->length - 12;
			if ( strspn( $this->html, "\0 \t\n\f\r", $cdata_content_start, $cdata_content_length ) !== $cdata_content_length ) {
				$this->state->frameset_ok = false;
			}

			$this->insert_foreign_element( $this->state->current_token, false );
			return true;

		/*
		 * > A comment token
		 */
		case '#comment':
		case '#funky-comment':
		case '#presumptuous-tag':
			$this->insert_foreign_element( $this->state->current_token, false );
			return true;

		/*
		 * > A DOCTYPE token
		 */
		case 'html':
			// Parse error: ignore the token.
			return $this->step();

		/*
		 * > A start tag whose tag name is "b", "big", "blockquote", "body", "br", "center",
		 * > "code", "dd", "div", "dl", "dt", "em", "embed", "h1", "h2", "h3", "h4", "h5",
		 * > "h6", "head", "hr", "i", "img", "li", "listing", "menu", "meta", "nobr", "ol",
		 * > "p", "pre", "ruby", "s", "small", "span", "strong", "strike", "sub", "sup",
		 * > "table", "tt", "u", "ul", "var"
		 *
		 * > A start tag whose name is "font", if the token has any attributes named "color", "face", or "size"
		 *
		 * > An end tag whose tag name is "br", "p"
		 *
		 * Closing BR tags are always reported by the Tag Processor as opening tags.
		 */
		case '+B':
		case '+BIG':
		case '+BLOCKQUOTE':
		case '+BODY':
		case '+BR':
		case '+CENTER':
		case '+CODE':
		case '+DD':
		case '+DIV':
		case '+DL':
		case '+DT':
		case '+EM':
		case '+EMBED':
		case '+H1':
		case '+H2':
		case '+H3':
		case '+H4':
		case '+H5':
		case '+H6':
		case '+HEAD':
		case '+HR':
		case '+I':
		case '+IMG':
		case '+LI':
		case '+LISTING':
		case '+MENU':
		case '+META':
		case '+NOBR':
		case '+OL':
		case '+P':
		case '+PRE':
		case '+RUBY':
		case '+S':
		case '+SMALL':
		case '+SPAN':
		case '+STRONG':
		case '+STRIKE':
		case '+SUB':
		case '+SUP':
		case '+TABLE':
		case '+TT':
		case '+U':
		case '+UL':
		case '+VAR':
		case '+FONT with attributes':
		case '-BR':
		case '-P':
			// @todo Indicate a parse error once it's possible.
			foreach ( $this->state->stack_of_open_elements->walk_up() as $current_node ) {
				if (
					'math' === $current_node->integration_node_type ||
					'html' === $current_node->integration_node_type ||
					'html' === $current_node->namespace
				) {
					break;
				}

				$this->state->stack_of_open_elements->pop();
			}
			goto in_foreign_content_process_in_current_insertion_mode;
	}

	/*
	 * > Any other start tag
	 */
	if ( ! $this->is_tag_closer() ) {
		$this->insert_foreign_element( $this->state->current_token, false );

		/*
		 * > If the token has its self-closing flag set, then run
		 * > the appropriate steps from the following list:
		 * >
		 * >   ↪ the token's tag name is "script", and the new current node is in the SVG namespace
		 * >         Acknowledge the token's self-closing flag, and then act as
		 * >         described in the steps for a "script" end tag below.
		 * >
		 * >   ↪ Otherwise
		 * >         Pop the current node off the stack of open elements and
		 * >         acknowledge the token's self-closing flag.
		 *
		 * Since the rules for SCRIPT below indicate to pop the element off of the stack of
		 * open elements, which is the same for the Otherwise condition, there's no need to
		 * separate these checks. The difference comes when a parser operates with the scripting
		 * flag enabled, and executes the script, which this parser does not support.
		 */
		if ( $this->state->current_token->has_self_closing_flag ) {
			$this->state->stack_of_open_elements->pop();
		}
		return true;
	}

	/*
	 * > An end tag whose name is "script", if the current node is an SVG script element.
	 */
	if ( $this->is_tag_closer() && 'SCRIPT' === $this->state->current_token->node_name && 'svg' === $this->state->current_token->namespace ) {
		$this->state->stack_of_open_elements->pop();
		return true;
	}

	/*
	 * > Any other end tag
	 */
	if ( $this->is_tag_closer() ) {
		$node = $this->state->stack_of_open_elements->current_node();
		if ( $tag_name !== $node->node_name ) {
			// @todo Indicate a parse error once it's possible.
		}
		in_foreign_content_end_tag_loop:
		if ( $node === $this->state->stack_of_open_elements->at( 1 ) ) {
			return true;
		}

		/*
		 * > If node's tag name, converted to ASCII lowercase, is the same as the tag name
		 * > of the token, pop elements from the stack of open elements until node has
		 * > been popped from the stack, and then return.
		 */
		if ( 0 === strcasecmp( $node->node_name, $tag_name ) ) {
			foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) {
				$this->state->stack_of_open_elements->pop();
				if ( $node === $item ) {
					return true;
				}
			}
		}

		foreach ( $this->state->stack_of_open_elements->walk_up( $node ) as $item ) {
			$node = $item;
			break;
		}

		if ( 'html' !== $node->namespace ) {
			goto in_foreign_content_end_tag_loop;
		}

		in_foreign_content_process_in_current_insertion_mode:
		switch ( $this->state->insertion_mode ) {
			case WP_HTML_Processor_State::INSERTION_MODE_INITIAL:
				return $this->step_initial();

			case WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HTML:
				return $this->step_before_html();

			case WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HEAD:
				return $this->step_before_head();

			case WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD:
				return $this->step_in_head();

			case WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD_NOSCRIPT:
				return $this->step_in_head_noscript();

			case WP_HTML_Processor_State::INSERTION_MODE_AFTER_HEAD:
				return $this->step_after_head();

			case WP_HTML_Processor_State::INSERTION_MODE_IN_BODY:
				return $this->step_in_body();

			case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE:
				return $this->step_in_table();

			case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_TEXT:
				return $this->step_in_table_text();

			case WP_HTML_Processor_State::INSERTION_MODE_IN_CAPTION:
				return $this->step_in_caption();

			case WP_HTML_Processor_State::INSERTION_MODE_IN_COLUMN_GROUP:
				return $this->step_in_column_group();

			case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY:
				return $this->step_in_table_body();

			case WP_HTML_Processor_State::INSERTION_MODE_IN_ROW:
				return $this->step_in_row();

			case WP_HTML_Processor_State::INSERTION_MODE_IN_CELL:
				return $this->step_in_cell();

			case WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT:
				return $this->step_in_select();

			case WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT_IN_TABLE:
				return $this->step_in_select_in_table();

			case WP_HTML_Processor_State::INSERTION_MODE_IN_TEMPLATE:
				return $this->step_in_template();

			case WP_HTML_Processor_State::INSERTION_MODE_AFTER_BODY:
				return $this->step_after_body();

			case WP_HTML_Processor_State::INSERTION_MODE_IN_FRAMESET:
				return $this->step_in_frameset();

			case WP_HTML_Processor_State::INSERTION_MODE_AFTER_FRAMESET:
				return $this->step_after_frameset();

			case WP_HTML_Processor_State::INSERTION_MODE_AFTER_AFTER_BODY:
				return $this->step_after_after_body();

			case WP_HTML_Processor_State::INSERTION_MODE_AFTER_AFTER_FRAMESET:
				return $this->step_after_after_frameset();

			// This should be unreachable but PHP doesn't have total type checking on switch.
			default:
				$this->bail( "Unaware of the requested parsing mode: '{$this->state->insertion_mode}'." );
		}
	}

	$this->bail( 'Should not have been able to reach end of IN FOREIGN CONTENT processing. Check HTML API code.' );
	// This unnecessary return prevents tools from inaccurately reporting type errors.
	return false;
}