WP_HTML_Processor::step_in_foreign_content(): bool

This function’s access is marked private. This means it is not intended for use by plugin or theme developers, only in other core functions. It is listed here for completeness. Use https://html.spec.whatwg.org/#parsing-main-inforeign instead.

Parses next element in the ‘in foreign content’ insertion mode.

Description

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

See also

Return

bool Whether an element was found.

Source

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;
}

Changelog

VersionDescription
6.7.0Introduced.

User Contributed Notes

You must log in before being able to contribute a note or feedback.