@@ -627,6 +627,15 @@ class WP_HTML_Tag_Processor {
627627 */
628628 private $ token_length ;
629629
630+ /**
631+ * Whether the current tag token has the self-closing flag.
632+ *
633+ * @since 7.1.0
634+ *
635+ * @var bool
636+ */
637+ private $ has_self_closing_flag = false ;
638+
630639 /**
631640 * Byte offset in input document where current tag name starts.
632641 *
@@ -1074,11 +1083,12 @@ private function base_class_next_token(): bool {
10741083 * the closing tag to point to the opening of the special atomic
10751084 * tag sequence.
10761085 */
1077- $ tag_name_starts_at = $ this ->tag_name_starts_at ;
1078- $ tag_name_length = $ this ->tag_name_length ;
1079- $ tag_ends_at = $ this ->token_starts_at + $ this ->token_length ;
1080- $ attributes = $ this ->attributes ;
1081- $ duplicate_attributes = $ this ->duplicate_attributes ;
1086+ $ tag_name_starts_at = $ this ->tag_name_starts_at ;
1087+ $ tag_name_length = $ this ->tag_name_length ;
1088+ $ tag_ends_at = $ this ->token_starts_at + $ this ->token_length ;
1089+ $ has_self_closing_flag = $ this ->has_self_closing_flag ;
1090+ $ attributes = $ this ->attributes ;
1091+ $ duplicate_attributes = $ this ->duplicate_attributes ;
10821092
10831093 // Find the closing tag if necessary.
10841094 switch ( $ tag_name ) {
@@ -1128,14 +1138,15 @@ private function base_class_next_token(): bool {
11281138 * functions that skip the contents have moved all the internal cursors past
11291139 * the inner content of the tag.
11301140 */
1131- $ this ->token_starts_at = $ was_at ;
1132- $ this ->token_length = $ this ->bytes_already_parsed - $ this ->token_starts_at ;
1133- $ this ->text_starts_at = $ tag_ends_at ;
1134- $ this ->text_length = $ this ->tag_name_starts_at - $ this ->text_starts_at ;
1135- $ this ->tag_name_starts_at = $ tag_name_starts_at ;
1136- $ this ->tag_name_length = $ tag_name_length ;
1137- $ this ->attributes = $ attributes ;
1138- $ this ->duplicate_attributes = $ duplicate_attributes ;
1141+ $ this ->token_starts_at = $ was_at ;
1142+ $ this ->token_length = $ this ->bytes_already_parsed - $ this ->token_starts_at ;
1143+ $ this ->text_starts_at = $ tag_ends_at ;
1144+ $ this ->text_length = $ this ->tag_name_starts_at - $ this ->text_starts_at ;
1145+ $ this ->tag_name_starts_at = $ tag_name_starts_at ;
1146+ $ this ->tag_name_length = $ tag_name_length ;
1147+ $ this ->has_self_closing_flag = $ has_self_closing_flag ;
1148+ $ this ->attributes = $ attributes ;
1149+ $ this ->duplicate_attributes = $ duplicate_attributes ;
11391150
11401151 return true ;
11411152 }
@@ -2140,13 +2151,34 @@ private function parse_next_attribute(): bool {
21402151 $ doc_length = strlen ( $ this ->html );
21412152
21422153 // Skip whitespace and slashes.
2143- $ this ->bytes_already_parsed += strspn ( $ this ->html , " \t\f\r\n/ " , $ this ->bytes_already_parsed );
2154+ $ skipped_length = strspn ( $ this ->html , " \t\f\r\n/ " , $ this ->bytes_already_parsed );
2155+ $ this ->bytes_already_parsed += $ skipped_length ;
21442156 if ( $ this ->bytes_already_parsed >= $ doc_length ) {
21452157 $ this ->parser_state = self ::STATE_INCOMPLETE_INPUT ;
21462158
21472159 return false ;
21482160 }
21492161
2162+ /**
2163+ * This block serves two purposes:
2164+ *
2165+ * - A fast path for common tag-ending `>`.
2166+ * - A check for the self-closing flag which must appear as `/>`.
2167+ *
2168+ * In a tag like `<g attr=/>`, `/` is the attribute value, not a self-closing
2169+ * flag. When it appears in this form, the parser has already consumed the
2170+ * attribute value, `$skipped_length` is 0, and this checks below correctly
2171+ * identify whether there is a self-closing flag.
2172+ *
2173+ * Note: Both start and end tags may have the self-closing flag.
2174+ */
2175+ if ( '> ' === $ this ->html [ $ this ->bytes_already_parsed ] ) {
2176+ if ( $ skipped_length > 0 && '/ ' === $ this ->html [ $ this ->bytes_already_parsed - 1 ] ) {
2177+ $ this ->has_self_closing_flag = true ;
2178+ }
2179+ return false ;
2180+ }
2181+
21502182 /*
21512183 * Treat the equal sign as a part of the attribute
21522184 * name if it is the first encountered byte.
@@ -2324,6 +2356,7 @@ private function after_tag(): void {
23242356
23252357 $ this ->token_starts_at = null ;
23262358 $ this ->token_length = null ;
2359+ $ this ->has_self_closing_flag = false ;
23272360 $ this ->tag_name_starts_at = null ;
23282361 $ this ->tag_name_length = null ;
23292362 $ this ->text_starts_at = 0 ;
@@ -3332,15 +3365,7 @@ public function has_self_closing_flag(): bool {
33323365 return false ;
33333366 }
33343367
3335- /*
3336- * The self-closing flag is the solidus at the _end_ of the tag, not the beginning.
3337- *
3338- * Example:
3339- *
3340- * <figure />
3341- * ^ this appears one character before the end of the closing ">".
3342- */
3343- return '/ ' === $ this ->html [ $ this ->token_starts_at + $ this ->token_length - 2 ];
3368+ return $ this ->has_self_closing_flag ;
33443369 }
33453370
33463371 /**
0 commit comments