Skip to content

Commit 199c0c6

Browse files
committed
Merge branch 'html-api/set-inner-html' into html-api/spawn_fragment_parser
2 parents f7d9b1b + 0bab23f commit 199c0c6

2 files changed

Lines changed: 153 additions & 1 deletion

File tree

src/wp-includes/html-api/class-wp-html-processor.php

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5020,6 +5020,158 @@ public function get_comment_type(): ?string {
50205020
return $this->is_virtual() ? null : parent::get_comment_type();
50215021
}
50225022

5023+
/**
5024+
* Normalize an HTML string by serializing it.
5025+
*
5026+
* This removes any partial syntax at the end of the string.
5027+
*
5028+
* @since 6.7.0
5029+
*
5030+
* @param string $html Input HTML to normalize.
5031+
*
5032+
* @return string|null Normalized output, or `null` if unable to normalize.
5033+
*/
5034+
public static function normalize( string $html ): ?string {
5035+
return static::create_fragment( $html )->serialize();
5036+
}
5037+
5038+
/**
5039+
* Generate normalized markup for the HTML in the provided processor.
5040+
*
5041+
* This removes any partial syntax at the end of the string.
5042+
*
5043+
* @since 6.7.0
5044+
*
5045+
* @return string|null Normalized HTML markup represented by processor,
5046+
* or `null` if unable to generate serialization.
5047+
*/
5048+
public function serialize(): ?string {
5049+
if ( WP_HTML_Tag_Processor::STATE_READY !== $this->parser_state ) {
5050+
return null;
5051+
}
5052+
5053+
$html = '';
5054+
while ( $this->next_token() ) {
5055+
$token_type = $this->get_token_type();
5056+
5057+
switch ( $token_type ) {
5058+
case '#text':
5059+
$html .= htmlspecialchars( $this->get_modifiable_text(), ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8' );
5060+
break;
5061+
5062+
case '#funky-comment':
5063+
case '#comment':
5064+
$html .= "<!--{$this->get_modifiable_text()}-->";
5065+
break;
5066+
5067+
case '#cdata-section':
5068+
$html .= "<![CDATA[{$this->get_modifiable_text()}]]>";
5069+
break;
5070+
5071+
case 'html':
5072+
$html .= '<!DOCTYPE html>';
5073+
break;
5074+
}
5075+
5076+
if ( '#tag' !== $token_type ) {
5077+
continue;
5078+
}
5079+
5080+
if ( $this->is_tag_closer() ) {
5081+
$html .= "</{$this->get_qualified_tag_name()}>";
5082+
continue;
5083+
}
5084+
5085+
$attribute_names = $this->get_attribute_names_with_prefix( '' );
5086+
if ( ! isset( $attribute_names ) ) {
5087+
$html .= "<{$this->get_qualified_tag_name()}>";
5088+
continue;
5089+
}
5090+
5091+
$html .= "<{$this->get_qualified_tag_name()}";
5092+
foreach ( $attribute_names as $attribute_name ) {
5093+
$html .= " {$this->get_qualified_attribute_name( $attribute_name )}";
5094+
$value = $this->get_attribute( $attribute_name );
5095+
5096+
if ( is_string( $value ) ) {
5097+
$html .= '="' . htmlspecialchars( $value, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5 ) . '"';
5098+
}
5099+
}
5100+
5101+
if ( 'html' !== $this->get_namespace() && $this->has_self_closing_flag() ) {
5102+
$html .= '/';
5103+
}
5104+
5105+
$html .= '>';
5106+
}
5107+
5108+
if ( null !== $this->get_last_error() ) {
5109+
return null;
5110+
}
5111+
5112+
return $html;
5113+
}
5114+
5115+
/**
5116+
* Replaces the inner markup of the currently-matched tag with provided HTML.
5117+
*
5118+
* This function will normalize the given input and enforce the boundaries
5119+
* within the existing HTML where it's called.
5120+
*
5121+
* @since 6.8.0
5122+
*
5123+
* @param string $new_inner_html New HTML to inject as inner HTML for the currently-matched tag.
5124+
* @return bool Whether the inner markup was modified for the currently-matched tag, or `NULL`
5125+
* if called on a node which doesn't allow changing the inner HTML.
5126+
*/
5127+
public function set_inner_html( string $new_inner_html ): ?bool {
5128+
$tag_name = $this->get_tag();
5129+
5130+
if (
5131+
WP_HTML_Tag_Processor::STATE_MATCHED_TAG !== $this->parser_state ||
5132+
$this->is_tag_closer() ||
5133+
( 'html' === $this->get_namespace() &&
5134+
(
5135+
self::is_void( $tag_name ) ||
5136+
in_array( $tag_name, array( 'IFRAME', 'NOEMBED', 'NOFRAMES', 'SCRIPT', 'STYLE', 'TEXTAREA', 'TITLE', 'XMP' ), true )
5137+
)
5138+
)
5139+
) {
5140+
// @todo Support setting inner HTML for SCRIPT, STYLE, TEXTAREA, and TITLE.
5141+
return null;
5142+
}
5143+
5144+
$fragment = $this->spawn_fragment_parser( $new_inner_html );
5145+
$new_markup = $fragment->serialize();
5146+
5147+
$this->set_bookmark( 'start' );
5148+
$depth = $this->get_current_depth();
5149+
while ( $this->get_current_depth() >= $depth && $this->next_token() ) {
5150+
continue;
5151+
}
5152+
5153+
if (
5154+
$this->paused_at_incomplete_token() ||
5155+
null !== $this->get_last_error()
5156+
) {
5157+
return false;
5158+
}
5159+
5160+
$this->set_bookmark( 'end' );
5161+
$start = $this->bookmarks['_start'];
5162+
$end = $this->bookmarks['_end'];
5163+
5164+
$this->lexical_updates[] = new WP_HTML_Text_Replacement(
5165+
$start->start + $start->length,
5166+
$end->start - ( $start->start + $start->length ),
5167+
$new_markup
5168+
);
5169+
5170+
$this->get_updated_html();
5171+
$this->seek( 'start' );
5172+
return true;
5173+
}
5174+
50235175
/**
50245176
* Removes a bookmark that is no longer needed.
50255177
*

src/wp-includes/html-api/class-wp-html-tag-processor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2840,7 +2840,7 @@ public function get_qualified_tag_name(): ?string {
28402840
}
28412841

28422842
if ( 'html' === $this->get_namespace() ) {
2843-
return $tag_name;
2843+
return strtolower( $tag_name );
28442844
}
28452845

28462846
$lower_tag_name = strtolower( $tag_name );

0 commit comments

Comments
 (0)