WP_HTML_Processor::step_in_bodyprivateWP 6.4.0

Parses next element in the 'in body' insertion mode.

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

Метод класса: WP_HTML_Processor{}

Хуков нет.

Возвращает

true|false. Whether an element was found.

Использование

// private - только в коде основоного (родительского) класса
$result = $this->step_in_body(): bool;

Заметки

Список изменений

С версии 6.4.0 Введена.

Код WP_HTML_Processor::step_in_body() WP 6.8.2

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

	switch ( $op ) {
		case '#text':
			/*
			 * > A character token that is U+0000 NULL
			 *
			 * Any successive sequence of NULL bytes is ignored and won't
			 * trigger active format reconstruction. Therefore, if the text
			 * only comprises NULL bytes then the token should be ignored
			 * here, but if there are any other characters in the stream
			 * the active formats should be reconstructed.
			 */
			if ( parent::TEXT_IS_NULL_SEQUENCE === $this->text_node_classification ) {
				// Parse error: ignore the token.
				return $this->step();
			}

			$this->reconstruct_active_formatting_elements();

			/*
			 * 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_html_element( $this->state->current_token );
			return true;

		case '#comment':
		case '#funky-comment':
		case '#presumptuous-tag':
			$this->insert_html_element( $this->state->current_token );
			return true;

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

		/*
		 * > A start tag whose tag name is "html"
		 */
		case '+HTML':
			if ( ! $this->state->stack_of_open_elements->contains( 'TEMPLATE' ) ) {
				/*
				 * > Otherwise, for each attribute on the token, check to see if the attribute
				 * > is already present on the top element of the stack of open elements. If
				 * > it is not, add the attribute and its corresponding value to that element.
				 *
				 * This parser does not currently support this behavior: ignore the token.
				 */
			}

			// Ignore the token.
			return $this->step();

		/*
		 * > A start tag whose tag name is one of: "base", "basefont", "bgsound", "link",
		 * > "meta", "noframes", "script", "style", "template", "title"
		 * >
		 * > An end tag whose tag name is "template"
		 */
		case '+BASE':
		case '+BASEFONT':
		case '+BGSOUND':
		case '+LINK':
		case '+META':
		case '+NOFRAMES':
		case '+SCRIPT':
		case '+STYLE':
		case '+TEMPLATE':
		case '+TITLE':
		case '-TEMPLATE':
			return $this->step_in_head();

		/*
		 * > A start tag whose tag name is "body"
		 *
		 * This tag in the IN BODY insertion mode is a parse error.
		 */
		case '+BODY':
			if (
				1 === $this->state->stack_of_open_elements->count() ||
				'BODY' !== ( $this->state->stack_of_open_elements->at( 2 )->node_name ?? null ) ||
				$this->state->stack_of_open_elements->contains( 'TEMPLATE' )
			) {
				// Ignore the token.
				return $this->step();
			}

			/*
			 * > Otherwise, set the frameset-ok flag to "not ok"; then, for each attribute
			 * > on the token, check to see if the attribute is already present on the body
			 * > element (the second element) on the stack of open elements, and if it is
			 * > not, add the attribute and its corresponding value to that element.
			 *
			 * This parser does not currently support this behavior: ignore the token.
			 */
			$this->state->frameset_ok = false;
			return $this->step();

		/*
		 * > A start tag whose tag name is "frameset"
		 *
		 * This tag in the IN BODY insertion mode is a parse error.
		 */
		case '+FRAMESET':
			if (
				1 === $this->state->stack_of_open_elements->count() ||
				'BODY' !== ( $this->state->stack_of_open_elements->at( 2 )->node_name ?? null ) ||
				false === $this->state->frameset_ok
			) {
				// Ignore the token.
				return $this->step();
			}

			/*
			 * > Otherwise, run the following steps:
			 */
			$this->bail( 'Cannot process non-ignored FRAMESET tags.' );
			break;

		/*
		 * > An end tag whose tag name is "body"
		 */
		case '-BODY':
			if ( ! $this->state->stack_of_open_elements->has_element_in_scope( 'BODY' ) ) {
				// Parse error: ignore the token.
				return $this->step();
			}

			/*
			 * > Otherwise, if there is a node in the stack of open elements that is not either a
			 * > dd element, a dt element, an li element, an optgroup element, an option element,
			 * > a p element, an rb element, an rp element, an rt element, an rtc element, a tbody
			 * > element, a td element, a tfoot element, a th element, a thread element, a tr
			 * > element, the body element, or the html element, then this is a parse error.
			 *
			 * There is nothing to do for this parse error, so don't check for it.
			 */

			$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_AFTER_BODY;
			/*
			 * The BODY element is not removed from the stack of open elements.
			 * Only internal state has changed, this does not qualify as a "step"
			 * in terms of advancing through the document to another token.
			 * Nothing has been pushed or popped.
			 * Proceed to parse the next item.
			 */
			return $this->step();

		/*
		 * > An end tag whose tag name is "html"
		 */
		case '-HTML':
			if ( ! $this->state->stack_of_open_elements->has_element_in_scope( 'BODY' ) ) {
				// Parse error: ignore the token.
				return $this->step();
			}

			/*
			 * > Otherwise, if there is a node in the stack of open elements that is not either a
			 * > dd element, a dt element, an li element, an optgroup element, an option element,
			 * > a p element, an rb element, an rp element, an rt element, an rtc element, a tbody
			 * > element, a td element, a tfoot element, a th element, a thread element, a tr
			 * > element, the body element, or the html element, then this is a parse error.
			 *
			 * There is nothing to do for this parse error, so don't check for it.
			 */

			$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_AFTER_BODY;
			return $this->step( self::REPROCESS_CURRENT_NODE );

		/*
		 * > A start tag whose tag name is one of: "address", "article", "aside",
		 * > "blockquote", "center", "details", "dialog", "dir", "div", "dl",
		 * > "fieldset", "figcaption", "figure", "footer", "header", "hgroup",
		 * > "main", "menu", "nav", "ol", "p", "search", "section", "summary", "ul"
		 */
		case '+ADDRESS':
		case '+ARTICLE':
		case '+ASIDE':
		case '+BLOCKQUOTE':
		case '+CENTER':
		case '+DETAILS':
		case '+DIALOG':
		case '+DIR':
		case '+DIV':
		case '+DL':
		case '+FIELDSET':
		case '+FIGCAPTION':
		case '+FIGURE':
		case '+FOOTER':
		case '+HEADER':
		case '+HGROUP':
		case '+MAIN':
		case '+MENU':
		case '+NAV':
		case '+OL':
		case '+P':
		case '+SEARCH':
		case '+SECTION':
		case '+SUMMARY':
		case '+UL':
			if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
				$this->close_a_p_element();
			}

			$this->insert_html_element( $this->state->current_token );
			return true;

		/*
		 * > A start tag whose tag name is one of: "h1", "h2", "h3", "h4", "h5", "h6"
		 */
		case '+H1':
		case '+H2':
		case '+H3':
		case '+H4':
		case '+H5':
		case '+H6':
			if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
				$this->close_a_p_element();
			}

			if (
				in_array(
					$this->state->stack_of_open_elements->current_node()->node_name,
					array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ),
					true
				)
			) {
				// @todo Indicate a parse error once it's possible.
				$this->state->stack_of_open_elements->pop();
			}

			$this->insert_html_element( $this->state->current_token );
			return true;

		/*
		 * > A start tag whose tag name is one of: "pre", "listing"
		 */
		case '+PRE':
		case '+LISTING':
			if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
				$this->close_a_p_element();
			}

			/*
			 * > If the next token is a U+000A LINE FEED (LF) character token,
			 * > then ignore that token and move on to the next one. (Newlines
			 * > at the start of pre blocks are ignored as an authoring convenience.)
			 *
			 * This is handled in `get_modifiable_text()`.
			 */

			$this->insert_html_element( $this->state->current_token );
			$this->state->frameset_ok = false;
			return true;

		/*
		 * > A start tag whose tag name is "form"
		 */
		case '+FORM':
			$stack_contains_template = $this->state->stack_of_open_elements->contains( 'TEMPLATE' );

			if ( isset( $this->state->form_element ) && ! $stack_contains_template ) {
				// Parse error: ignore the token.
				return $this->step();
			}

			if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
				$this->close_a_p_element();
			}

			$this->insert_html_element( $this->state->current_token );
			if ( ! $stack_contains_template ) {
				$this->state->form_element = $this->state->current_token;
			}

			return true;

		/*
		 * > A start tag whose tag name is "li"
		 * > A start tag whose tag name is one of: "dd", "dt"
		 */
		case '+DD':
		case '+DT':
		case '+LI':
			$this->state->frameset_ok = false;
			$node                     = $this->state->stack_of_open_elements->current_node();
			$is_li                    = 'LI' === $token_name;

			in_body_list_loop:
			/*
			 * The logic for LI and DT/DD is the same except for one point: LI elements _only_
			 * close other LI elements, but a DT or DD element closes _any_ open DT or DD element.
			 */
			if ( $is_li ? 'LI' === $node->node_name : ( 'DD' === $node->node_name || 'DT' === $node->node_name ) ) {
				$node_name = $is_li ? 'LI' : $node->node_name;
				$this->generate_implied_end_tags( $node_name );
				if ( ! $this->state->stack_of_open_elements->current_node_is( $node_name ) ) {
					// @todo Indicate a parse error once it's possible. This error does not impact the logic here.
				}

				$this->state->stack_of_open_elements->pop_until( $node_name );
				goto in_body_list_done;
			}

			if (
				'ADDRESS' !== $node->node_name &&
				'DIV' !== $node->node_name &&
				'P' !== $node->node_name &&
				self::is_special( $node )
			) {
				/*
				 * > If node is in the special category, but is not an address, div,
				 * > or p element, then jump to the step labeled done below.
				 */
				goto in_body_list_done;
			} else {
				/*
				 * > Otherwise, set node to the previous entry in the stack of open elements
				 * > and return to the step labeled loop.
				 */
				foreach ( $this->state->stack_of_open_elements->walk_up( $node ) as $item ) {
					$node = $item;
					break;
				}
				goto in_body_list_loop;
			}

			in_body_list_done:
			if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
				$this->close_a_p_element();
			}

			$this->insert_html_element( $this->state->current_token );
			return true;

		case '+PLAINTEXT':
			if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
				$this->close_a_p_element();
			}

			/*
			 * @todo This may need to be handled in the Tag Processor and turn into
			 *       a single self-contained tag like TEXTAREA, whose modifiable text
			 *       is the rest of the input document as plaintext.
			 */
			$this->bail( 'Cannot process PLAINTEXT elements.' );
			break;

		/*
		 * > A start tag whose tag name is "button"
		 */
		case '+BUTTON':
			if ( $this->state->stack_of_open_elements->has_element_in_scope( 'BUTTON' ) ) {
				// @todo Indicate a parse error once it's possible. This error does not impact the logic here.
				$this->generate_implied_end_tags();
				$this->state->stack_of_open_elements->pop_until( 'BUTTON' );
			}

			$this->reconstruct_active_formatting_elements();
			$this->insert_html_element( $this->state->current_token );
			$this->state->frameset_ok = false;

			return true;

		/*
		 * > An end tag whose tag name is one of: "address", "article", "aside", "blockquote",
		 * > "button", "center", "details", "dialog", "dir", "div", "dl", "fieldset",
		 * > "figcaption", "figure", "footer", "header", "hgroup", "listing", "main",
		 * > "menu", "nav", "ol", "pre", "search", "section", "summary", "ul"
		 */
		case '-ADDRESS':
		case '-ARTICLE':
		case '-ASIDE':
		case '-BLOCKQUOTE':
		case '-BUTTON':
		case '-CENTER':
		case '-DETAILS':
		case '-DIALOG':
		case '-DIR':
		case '-DIV':
		case '-DL':
		case '-FIELDSET':
		case '-FIGCAPTION':
		case '-FIGURE':
		case '-FOOTER':
		case '-HEADER':
		case '-HGROUP':
		case '-LISTING':
		case '-MAIN':
		case '-MENU':
		case '-NAV':
		case '-OL':
		case '-PRE':
		case '-SEARCH':
		case '-SECTION':
		case '-SUMMARY':
		case '-UL':
			if ( ! $this->state->stack_of_open_elements->has_element_in_scope( $token_name ) ) {
				// @todo Report parse error.
				// Ignore the token.
				return $this->step();
			}

			$this->generate_implied_end_tags();
			if ( ! $this->state->stack_of_open_elements->current_node_is( $token_name ) ) {
				// @todo Record parse error: this error doesn't impact parsing.
			}
			$this->state->stack_of_open_elements->pop_until( $token_name );
			return true;

		/*
		 * > An end tag whose tag name is "form"
		 */
		case '-FORM':
			if ( ! $this->state->stack_of_open_elements->contains( 'TEMPLATE' ) ) {
				$node                      = $this->state->form_element;
				$this->state->form_element = null;

				/*
				 * > If node is null or if the stack of open elements does not have node
				 * > in scope, then this is a parse error; return and ignore the token.
				 *
				 * @todo It's necessary to check if the form token itself is in scope, not
				 *       simply whether any FORM is in scope.
				 */
				if (
					null === $node ||
					! $this->state->stack_of_open_elements->has_element_in_scope( 'FORM' )
				) {
					// Parse error: ignore the token.
					return $this->step();
				}

				$this->generate_implied_end_tags();
				if ( $node !== $this->state->stack_of_open_elements->current_node() ) {
					// @todo Indicate a parse error once it's possible. This error does not impact the logic here.
					$this->bail( 'Cannot close a FORM when other elements remain open as this would throw off the breadcrumbs for the following tokens.' );
				}

				$this->state->stack_of_open_elements->remove_node( $node );
				return true;
			} else {
				/*
				 * > If the stack of open elements does not have a form element in scope,
				 * > then this is a parse error; return and ignore the token.
				 *
				 * Note that unlike in the clause above, this is checking for any FORM in scope.
				 */
				if ( ! $this->state->stack_of_open_elements->has_element_in_scope( 'FORM' ) ) {
					// Parse error: ignore the token.
					return $this->step();
				}

				$this->generate_implied_end_tags();

				if ( ! $this->state->stack_of_open_elements->current_node_is( 'FORM' ) ) {
					// @todo Indicate a parse error once it's possible. This error does not impact the logic here.
				}

				$this->state->stack_of_open_elements->pop_until( 'FORM' );
				return true;
			}
			break;

		/*
		 * > An end tag whose tag name is "p"
		 */
		case '-P':
			if ( ! $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
				$this->insert_html_element( $this->state->current_token );
			}

			$this->close_a_p_element();
			return true;

		/*
		 * > An end tag whose tag name is "li"
		 * > An end tag whose tag name is one of: "dd", "dt"
		 */
		case '-DD':
		case '-DT':
		case '-LI':
			if (
				/*
				 * An end tag whose tag name is "li":
				 * If the stack of open elements does not have an li element in list item scope,
				 * then this is a parse error; ignore the token.
				 */
				(
					'LI' === $token_name &&
					! $this->state->stack_of_open_elements->has_element_in_list_item_scope( 'LI' )
				) ||
				/*
				 * An end tag whose tag name is one of: "dd", "dt":
				 * If the stack of open elements does not have an element in scope that is an
				 * HTML element with the same tag name as that of the token, then this is a
				 * parse error; ignore the token.
				 */
				(
					'LI' !== $token_name &&
					! $this->state->stack_of_open_elements->has_element_in_scope( $token_name )
				)
			) {
				/*
				 * This is a parse error, ignore the token.
				 *
				 * @todo Indicate a parse error once it's possible.
				 */
				return $this->step();
			}

			$this->generate_implied_end_tags( $token_name );

			if ( ! $this->state->stack_of_open_elements->current_node_is( $token_name ) ) {
				// @todo Indicate a parse error once it's possible. This error does not impact the logic here.
			}

			$this->state->stack_of_open_elements->pop_until( $token_name );
			return true;

		/*
		 * > An end tag whose tag name is one of: "h1", "h2", "h3", "h4", "h5", "h6"
		 */
		case '-H1':
		case '-H2':
		case '-H3':
		case '-H4':
		case '-H5':
		case '-H6':
			if ( ! $this->state->stack_of_open_elements->has_element_in_scope( '(internal: H1 through H6 - do not use)' ) ) {
				/*
				 * This is a parse error; ignore the token.
				 *
				 * @todo Indicate a parse error once it's possible.
				 */
				return $this->step();
			}

			$this->generate_implied_end_tags();

			if ( ! $this->state->stack_of_open_elements->current_node_is( $token_name ) ) {
				// @todo Record parse error: this error doesn't impact parsing.
			}

			$this->state->stack_of_open_elements->pop_until( '(internal: H1 through H6 - do not use)' );
			return true;

		/*
		 * > A start tag whose tag name is "a"
		 */
		case '+A':
			foreach ( $this->state->active_formatting_elements->walk_up() as $item ) {
				switch ( $item->node_name ) {
					case 'marker':
						break 2;

					case 'A':
						$this->run_adoption_agency_algorithm();
						$this->state->active_formatting_elements->remove_node( $item );
						$this->state->stack_of_open_elements->remove_node( $item );
						break 2;
				}
			}

			$this->reconstruct_active_formatting_elements();
			$this->insert_html_element( $this->state->current_token );
			$this->state->active_formatting_elements->push( $this->state->current_token );
			return true;

		/*
		 * > A start tag whose tag name is one of: "b", "big", "code", "em", "font", "i",
		 * > "s", "small", "strike", "strong", "tt", "u"
		 */
		case '+B':
		case '+BIG':
		case '+CODE':
		case '+EM':
		case '+FONT':
		case '+I':
		case '+S':
		case '+SMALL':
		case '+STRIKE':
		case '+STRONG':
		case '+TT':
		case '+U':
			$this->reconstruct_active_formatting_elements();
			$this->insert_html_element( $this->state->current_token );
			$this->state->active_formatting_elements->push( $this->state->current_token );
			return true;

		/*
		 * > A start tag whose tag name is "nobr"
		 */
		case '+NOBR':
			$this->reconstruct_active_formatting_elements();

			if ( $this->state->stack_of_open_elements->has_element_in_scope( 'NOBR' ) ) {
				// Parse error.
				$this->run_adoption_agency_algorithm();
				$this->reconstruct_active_formatting_elements();
			}

			$this->insert_html_element( $this->state->current_token );
			$this->state->active_formatting_elements->push( $this->state->current_token );
			return true;

		/*
		 * > An end tag whose tag name is one of: "a", "b", "big", "code", "em", "font", "i",
		 * > "nobr", "s", "small", "strike", "strong", "tt", "u"
		 */
		case '-A':
		case '-B':
		case '-BIG':
		case '-CODE':
		case '-EM':
		case '-FONT':
		case '-I':
		case '-NOBR':
		case '-S':
		case '-SMALL':
		case '-STRIKE':
		case '-STRONG':
		case '-TT':
		case '-U':
			$this->run_adoption_agency_algorithm();
			return true;

		/*
		 * > A start tag whose tag name is one of: "applet", "marquee", "object"
		 */
		case '+APPLET':
		case '+MARQUEE':
		case '+OBJECT':
			$this->reconstruct_active_formatting_elements();
			$this->insert_html_element( $this->state->current_token );
			$this->state->active_formatting_elements->insert_marker();
			$this->state->frameset_ok = false;
			return true;

		/*
		 * > A end tag token whose tag name is one of: "applet", "marquee", "object"
		 */
		case '-APPLET':
		case '-MARQUEE':
		case '-OBJECT':
			if ( ! $this->state->stack_of_open_elements->has_element_in_scope( $token_name ) ) {
				// Parse error: ignore the token.
				return $this->step();
			}

			$this->generate_implied_end_tags();
			if ( ! $this->state->stack_of_open_elements->current_node_is( $token_name ) ) {
				// This is a parse error.
			}

			$this->state->stack_of_open_elements->pop_until( $token_name );
			$this->state->active_formatting_elements->clear_up_to_last_marker();
			return true;

		/*
		 * > A start tag whose tag name is "table"
		 */
		case '+TABLE':
			/*
			 * > If the Document is not set to quirks mode, and the stack of open elements
			 * > has a p element in button scope, then close a p element.
			 */
			if (
				WP_HTML_Tag_Processor::QUIRKS_MODE !== $this->compat_mode &&
				$this->state->stack_of_open_elements->has_p_in_button_scope()
			) {
				$this->close_a_p_element();
			}

			$this->insert_html_element( $this->state->current_token );
			$this->state->frameset_ok    = false;
			$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE;
			return true;

		/*
		 * > An end tag whose tag name is "br"
		 *
		 * This is prevented from happening because the Tag Processor
		 * reports all closing BR tags as if they were opening tags.
		 */

		/*
		 * > A start tag whose tag name is one of: "area", "br", "embed", "img", "keygen", "wbr"
		 */
		case '+AREA':
		case '+BR':
		case '+EMBED':
		case '+IMG':
		case '+KEYGEN':
		case '+WBR':
			$this->reconstruct_active_formatting_elements();
			$this->insert_html_element( $this->state->current_token );
			$this->state->frameset_ok = false;
			return true;

		/*
		 * > A start tag whose tag name is "input"
		 */
		case '+INPUT':
			$this->reconstruct_active_formatting_elements();
			$this->insert_html_element( $this->state->current_token );

			/*
			 * > If the token does not have an attribute with the name "type", or if it does,
			 * > but that attribute's value is not an ASCII case-insensitive match for the
			 * > string "hidden", then: set the frameset-ok flag to "not ok".
			 */
			$type_attribute = $this->get_attribute( 'type' );
			if ( ! is_string( $type_attribute ) || 'hidden' !== strtolower( $type_attribute ) ) {
				$this->state->frameset_ok = false;
			}

			return true;

		/*
		 * > A start tag whose tag name is one of: "param", "source", "track"
		 */
		case '+PARAM':
		case '+SOURCE':
		case '+TRACK':
			$this->insert_html_element( $this->state->current_token );
			return true;

		/*
		 * > A start tag whose tag name is "hr"
		 */
		case '+HR':
			if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
				$this->close_a_p_element();
			}
			$this->insert_html_element( $this->state->current_token );
			$this->state->frameset_ok = false;
			return true;

		/*
		 * > A start tag whose tag name is "image"
		 */
		case '+IMAGE':
			/*
			 * > Parse error. Change the token's tag name to "img" and reprocess it. (Don't ask.)
			 *
			 * Note that this is handled elsewhere, so it should not be possible to reach this code.
			 */
			$this->bail( "Cannot process an IMAGE tag. (Don't ask.)" );
			break;

		/*
		 * > A start tag whose tag name is "textarea"
		 */
		case '+TEXTAREA':
			$this->insert_html_element( $this->state->current_token );

			/*
			 * > If the next token is a U+000A LINE FEED (LF) character token, then ignore
			 * > that token and move on to the next one. (Newlines at the start of
			 * > textarea elements are ignored as an authoring convenience.)
			 *
			 * This is handled in `get_modifiable_text()`.
			 */

			$this->state->frameset_ok = false;

			/*
			 * > Switch the insertion mode to "text".
			 *
			 * As a self-contained node, this behavior is handled in the Tag Processor.
			 */
			return true;

		/*
		 * > A start tag whose tag name is "xmp"
		 */
		case '+XMP':
			if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
				$this->close_a_p_element();
			}

			$this->reconstruct_active_formatting_elements();
			$this->state->frameset_ok = false;

			/*
			 * > Follow the generic raw text element parsing algorithm.
			 *
			 * As a self-contained node, this behavior is handled in the Tag Processor.
			 */
			$this->insert_html_element( $this->state->current_token );
			return true;

		/*
		 * A start tag whose tag name is "iframe"
		 */
		case '+IFRAME':
			$this->state->frameset_ok = false;

			/*
			 * > Follow the generic raw text element parsing algorithm.
			 *
			 * As a self-contained node, this behavior is handled in the Tag Processor.
			 */
			$this->insert_html_element( $this->state->current_token );
			return true;

		/*
		 * > A start tag whose tag name is "noembed"
		 * > A start tag whose tag name is "noscript", if the scripting flag is enabled
		 *
		 * The scripting flag is never enabled in this parser.
		 */
		case '+NOEMBED':
			$this->insert_html_element( $this->state->current_token );
			return true;

		/*
		 * > A start tag whose tag name is "select"
		 */
		case '+SELECT':
			$this->reconstruct_active_formatting_elements();
			$this->insert_html_element( $this->state->current_token );
			$this->state->frameset_ok = false;

			switch ( $this->state->insertion_mode ) {
				/*
				 * > If the insertion mode is one of "in table", "in caption", "in table body", "in row",
				 * > or "in cell", then switch the insertion mode to "in select in table".
				 */
				case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE:
				case WP_HTML_Processor_State::INSERTION_MODE_IN_CAPTION:
				case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY:
				case WP_HTML_Processor_State::INSERTION_MODE_IN_ROW:
				case WP_HTML_Processor_State::INSERTION_MODE_IN_CELL:
					$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT_IN_TABLE;
					break;

				/*
				 * > Otherwise, switch the insertion mode to "in select".
				 */
				default:
					$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT;
					break;
			}
			return true;

		/*
		 * > A start tag whose tag name is one of: "optgroup", "option"
		 */
		case '+OPTGROUP':
		case '+OPTION':
			if ( $this->state->stack_of_open_elements->current_node_is( 'OPTION' ) ) {
				$this->state->stack_of_open_elements->pop();
			}
			$this->reconstruct_active_formatting_elements();
			$this->insert_html_element( $this->state->current_token );
			return true;

		/*
		 * > A start tag whose tag name is one of: "rb", "rtc"
		 */
		case '+RB':
		case '+RTC':
			if ( $this->state->stack_of_open_elements->has_element_in_scope( 'RUBY' ) ) {
				$this->generate_implied_end_tags();

				if ( $this->state->stack_of_open_elements->current_node_is( 'RUBY' ) ) {
					// @todo Indicate a parse error once it's possible.
				}
			}

			$this->insert_html_element( $this->state->current_token );
			return true;

		/*
		 * > A start tag whose tag name is one of: "rp", "rt"
		 */
		case '+RP':
		case '+RT':
			if ( $this->state->stack_of_open_elements->has_element_in_scope( 'RUBY' ) ) {
				$this->generate_implied_end_tags( 'RTC' );

				$current_node_name = $this->state->stack_of_open_elements->current_node()->node_name;
				if ( 'RTC' === $current_node_name || 'RUBY' === $current_node_name ) {
					// @todo Indicate a parse error once it's possible.
				}
			}

			$this->insert_html_element( $this->state->current_token );
			return true;

		/*
		 * > A start tag whose tag name is "math"
		 */
		case '+MATH':
			$this->reconstruct_active_formatting_elements();

			/*
			 * @todo Adjust MathML attributes for the token. (This fixes the case of MathML attributes that are not all lowercase.)
			 * @todo Adjust foreign attributes for the token. (This fixes the use of namespaced attributes, in particular XLink.)
			 *
			 * These ought to be handled in the attribute methods.
			 */
			$this->state->current_token->namespace = 'math';
			$this->insert_html_element( $this->state->current_token );
			if ( $this->state->current_token->has_self_closing_flag ) {
				$this->state->stack_of_open_elements->pop();
			}
			return true;

		/*
		 * > A start tag whose tag name is "svg"
		 */
		case '+SVG':
			$this->reconstruct_active_formatting_elements();

			/*
			 * @todo Adjust SVG attributes for the token. (This fixes the case of SVG attributes that are not all lowercase.)
			 * @todo Adjust foreign attributes for the token. (This fixes the use of namespaced attributes, in particular XLink in SVG.)
			 *
			 * These ought to be handled in the attribute methods.
			 */
			$this->state->current_token->namespace = 'svg';
			$this->insert_html_element( $this->state->current_token );
			if ( $this->state->current_token->has_self_closing_flag ) {
				$this->state->stack_of_open_elements->pop();
			}
			return true;

		/*
		 * > A start tag whose tag name is one of: "caption", "col", "colgroup",
		 * > "frame", "head", "tbody", "td", "tfoot", "th", "thead", "tr"
		 */
		case '+CAPTION':
		case '+COL':
		case '+COLGROUP':
		case '+FRAME':
		case '+HEAD':
		case '+TBODY':
		case '+TD':
		case '+TFOOT':
		case '+TH':
		case '+THEAD':
		case '+TR':
			// Parse error. Ignore the token.
			return $this->step();
	}

	if ( ! parent::is_tag_closer() ) {
		/*
		 * > Any other start tag
		 */
		$this->reconstruct_active_formatting_elements();
		$this->insert_html_element( $this->state->current_token );
		return true;
	} else {
		/*
		 * > Any other end tag
		 */

		/*
		 * Find the corresponding tag opener in the stack of open elements, if
		 * it exists before reaching a special element, which provides a kind
		 * of boundary in the stack. For example, a `</custom-tag>` should not
		 * close anything beyond its containing `P` or `DIV` element.
		 */
		foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) {
			if ( 'html' === $node->namespace && $token_name === $node->node_name ) {
				break;
			}

			if ( self::is_special( $node ) ) {
				// This is a parse error, ignore the token.
				return $this->step();
			}
		}

		$this->generate_implied_end_tags( $token_name );
		if ( $node !== $this->state->stack_of_open_elements->current_node() ) {
			// @todo Record parse error: this error doesn't impact parsing.
		}

		foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) {
			$this->state->stack_of_open_elements->pop();
			if ( $node === $item ) {
				return true;
			}
		}
	}

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