@@ -813,8 +813,14 @@ private function next_visitable_token(): bool {
813813 * until there are events or until there are no more
814814 * tokens works in the meantime and isn't obviously wrong.
815815 */
816- if ( empty ( $ this ->element_queue ) && $ this ->step () ) {
817- return $ this ->next_visitable_token ();
816+ if ( empty ( $ this ->element_queue ) ) {
817+ if ( $ this ->step () ) {
818+ return $ this ->next_visitable_token ();
819+ }
820+
821+ if ( isset ( $ this ->last_error ) ) {
822+ return false ;
823+ }
818824 }
819825
820826 // Process the next event on the queue.
@@ -1401,6 +1407,7 @@ public function serialize_token(): string {
14011407 $ tag_name = str_replace ( "\x00" , "\u{FFFD}" , $ this ->get_tag () );
14021408 $ in_html = 'html ' === $ this ->get_namespace ();
14031409 $ qualified_name = $ in_html ? strtolower ( $ tag_name ) : $ this ->get_qualified_tag_name ();
1410+ $ qualified_name = str_replace ( "\x00" , "\u{FFFD}" , $ qualified_name );
14041411
14051412 if ( $ this ->is_tag_closer () ) {
14061413 $ html .= "</ {$ qualified_name }> " ;
@@ -1414,15 +1421,36 @@ public function serialize_token(): string {
14141421 }
14151422
14161423 $ html .= "< {$ qualified_name }" ;
1424+
1425+ $ previous_attribute_was_true = false ;
1426+ $ seen_attribute_names = array ();
14171427 foreach ( $ attribute_names as $ attribute_name ) {
1418- $ html .= " {$ this ->get_qualified_attribute_name ( $ attribute_name )}" ;
1428+ $ qualified_attribute_name = $ this ->get_qualified_attribute_name ( $ attribute_name );
1429+ $ qualified_attribute_name = str_replace ( "\x00" , "\u{FFFD}" , $ qualified_attribute_name );
1430+ $ qualified_attribute_name = wp_scrub_utf8 ( $ qualified_attribute_name );
1431+ if ( isset ( $ seen_attribute_names [ $ qualified_attribute_name ] ) ) {
1432+ continue ;
1433+ } else {
1434+ $ seen_attribute_names [ $ qualified_attribute_name ] = true ;
1435+ }
1436+
1437+ if (
1438+ $ previous_attribute_was_true &&
1439+ isset ( $ qualified_attribute_name [0 ] ) &&
1440+ '= ' === $ qualified_attribute_name [0 ]
1441+ ) {
1442+ $ html .= '="" ' ;
1443+ }
1444+
1445+ $ html .= " {$ qualified_attribute_name }" ;
14191446 $ value = $ this ->get_attribute ( $ attribute_name );
14201447
14211448 if ( is_string ( $ value ) ) {
14221449 $ html .= '=" ' . htmlspecialchars ( $ value , ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5 ) . '" ' ;
14231450 }
14241451
1425- $ html = str_replace ( "\x00" , "\u{FFFD}" , $ html );
1452+ $ previous_attribute_was_true = true === $ value ;
1453+ $ html = str_replace ( "\x00" , "\u{FFFD}" , $ html );
14261454 }
14271455
14281456 if ( ! $ in_html && $ this ->has_self_closing_flag () ) {
@@ -2667,8 +2695,7 @@ private function step_in_body(): bool {
26672695 */
26682696 case '-FORM ' :
26692697 if ( ! $ this ->state ->stack_of_open_elements ->contains ( 'TEMPLATE ' ) ) {
2670- $ node = $ this ->state ->form_element ;
2671- $ this ->state ->form_element = null ;
2698+ $ node = $ this ->state ->form_element ;
26722699
26732700 /*
26742701 * > If node is null or if the stack of open elements does not have node
@@ -2681,10 +2708,20 @@ private function step_in_body(): bool {
26812708 null === $ node ||
26822709 ! $ this ->state ->stack_of_open_elements ->has_element_in_scope ( 'FORM ' )
26832710 ) {
2684- // Parse error: ignore the token.
2711+ /*
2712+ * Parse error: ignore the token.
2713+ *
2714+ * Keep the form pointer intact when the end tag is ignored, such as
2715+ * when a FORM closing tag appears inside an SVG TITLE integration
2716+ * point. Otherwise the ignored token changes parser state in a way
2717+ * that serialization cannot represent, allowing a later FORM opener
2718+ * to appear in the first normalization pass and disappear on the second.
2719+ */
26852720 return $ this ->step ();
26862721 }
26872722
2723+ $ this ->state ->form_element = null ;
2724+
26882725 $ this ->generate_implied_end_tags ();
26892726 if ( $ node !== $ this ->state ->stack_of_open_elements ->current_node () ) {
26902727 // @todo Indicate a parse error once it's possible. This error does not impact the logic here.
0 commit comments