Changeset 57264
- Timestamp:
- 01/10/2024 02:03:57 PM (9 months ago)
- Location:
- trunk
- Files:
-
- 1 added
- 8 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/phpcs.xml.dist
r57017 r57264 251 251 </rule> 252 252 253 <!-- Exclude forbidding goto in the HTML Processor, which mimics algorithms that are written 254 this way in the HTML specification, and these particular algorithms are complex and 255 highly imperative. Avoiding the goto introduces a number of risks that could make it 256 more difficult to maintain the relationship to the standard, lead to subtle differences 257 in the parsing, and distance the code from its standard. --> 258 <rule ref="Generic.PHP.DiscourageGoto.Found"> 259 <exclude-pattern>/wp-includes/html-api/class-wp-html-processor\.php</exclude-pattern> 260 </rule> 261 253 262 <!-- Exclude sample config from modernization to prevent breaking CI workflows based on WP-CLI scaffold. 254 263 See: https://core.trac.wordpress.org/ticket/48082#comment:16 --> -
trunk/src/wp-includes/html-api/class-wp-html-open-elements.php
r57186 r57264 130 130 131 131 if ( in_array( $node->node_name, $termination_list, true ) ) { 132 return true;132 return false; 133 133 } 134 134 } … … 167 167 * 168 168 * @since 6.4.0 169 * @since 6.5.0 Implemented: no longer throws on every invocation. 169 170 * 170 171 * @see https://html.spec.whatwg.org/#has-an-element-in-list-item-scope 171 *172 * @throws WP_HTML_Unsupported_Exception Always until this function is implemented.173 172 * 174 173 * @param string $tag_name Name of tag to check. … … 176 175 */ 177 176 public function has_element_in_list_item_scope( $tag_name ) { 178 throw new WP_HTML_Unsupported_Exception( 'Cannot process elements depending on list item scope.' ); 179 180 return false; // The linter requires this unreachable code until the function is implemented and can return. 177 return $this->has_element_in_specific_scope( 178 $tag_name, 179 array( 180 // There are more elements that belong here which aren't currently supported. 181 'OL', 182 'UL', 183 ) 184 ); 181 185 } 182 186 … … 376 380 * 377 381 * @since 6.4.0 378 */ 379 public function walk_up() { 382 * @since 6.5.0 Accepts $above_this_node to start traversal above a given node, if it exists. 383 * 384 * @param ?WP_HTML_Token $above_this_node Start traversing above this node, if provided and if the node exists. 385 */ 386 public function walk_up( $above_this_node = null ) { 387 $has_found_node = null === $above_this_node; 388 380 389 for ( $i = count( $this->stack ) - 1; $i >= 0; $i-- ) { 381 yield $this->stack[ $i ]; 390 $node = $this->stack[ $i ]; 391 392 if ( ! $has_found_node ) { 393 $has_found_node = $node === $above_this_node; 394 continue; 395 } 396 397 yield $node; 382 398 } 383 399 } -
trunk/src/wp-includes/html-api/class-wp-html-processor.php
r57248 r57264 106 106 * - Heading elements: H1, H2, H3, H4, H5, H6, HGROUP. 107 107 * - Links: A. 108 * - Lists: D L.108 * - Lists: DD, DL, DT, LI, OL, LI. 109 109 * - Media elements: AUDIO, CANVAS, FIGCAPTION, FIGURE, IMG, MAP, PICTURE, VIDEO. 110 110 * - Paragraph: P. … … 649 649 case '+MENU': 650 650 case '+NAV': 651 case '+OL': 651 652 case '+P': 652 653 case '+SEARCH': 653 654 case '+SECTION': 654 655 case '+SUMMARY': 656 case '+UL': 655 657 if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) { 656 658 $this->close_a_p_element(); … … 686 688 case '-MENU': 687 689 case '-NAV': 690 case '-OL': 688 691 case '-SEARCH': 689 692 case '-SECTION': 690 693 case '-SUMMARY': 694 case '-UL': 691 695 if ( ! $this->state->stack_of_open_elements->has_element_in_scope( $tag_name ) ) { 692 696 // @todo Report parse error. … … 754 758 755 759 $this->state->stack_of_open_elements->pop_until( '(internal: H1 through H6 - do not use)' ); 760 return true; 761 762 /* 763 * > A start tag whose tag name is "li" 764 * > A start tag whose tag name is one of: "dd", "dt" 765 */ 766 case '+DD': 767 case '+DT': 768 case '+LI': 769 $this->state->frameset_ok = false; 770 $node = $this->state->stack_of_open_elements->current_node(); 771 $is_li = 'LI' === $tag_name; 772 773 in_body_list_loop: 774 /* 775 * The logic for LI and DT/DD is the same except for one point: LI elements _only_ 776 * close other LI elements, but a DT or DD element closes _any_ open DT or DD element. 777 */ 778 if ( $is_li ? 'LI' === $node->node_name : ( 'DD' === $node->node_name || 'DT' === $node->node_name ) ) { 779 $node_name = $is_li ? 'LI' : $node->node_name; 780 $this->generate_implied_end_tags( $node_name ); 781 if ( $node_name !== $this->state->stack_of_open_elements->current_node()->node_name ) { 782 // @todo Indicate a parse error once it's possible. This error does not impact the logic here. 783 } 784 785 $this->state->stack_of_open_elements->pop_until( $node_name ); 786 goto in_body_list_done; 787 } 788 789 if ( 790 'ADDRESS' !== $node->node_name && 791 'DIV' !== $node->node_name && 792 'P' !== $node->node_name && 793 $this->is_special( $node->node_name ) 794 ) { 795 /* 796 * > If node is in the special category, but is not an address, div, 797 * > or p element, then jump to the step labeled done below. 798 */ 799 goto in_body_list_done; 800 } else { 801 /* 802 * > Otherwise, set node to the previous entry in the stack of open elements 803 * > and return to the step labeled loop. 804 */ 805 foreach ( $this->state->stack_of_open_elements->walk_up( $node ) as $item ) { 806 $node = $item; 807 break; 808 } 809 goto in_body_list_loop; 810 } 811 812 in_body_list_done: 813 if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) { 814 $this->close_a_p_element(); 815 } 816 817 $this->insert_html_element( $this->state->current_token ); 818 return true; 819 820 /* 821 * > An end tag whose tag name is "li" 822 * > An end tag whose tag name is one of: "dd", "dt" 823 */ 824 case '-DD': 825 case '-DT': 826 case '-LI': 827 if ( 828 /* 829 * An end tag whose tag name is "li": 830 * If the stack of open elements does not have an li element in list item scope, 831 * then this is a parse error; ignore the token. 832 */ 833 ( 834 'LI' === $tag_name && 835 ! $this->state->stack_of_open_elements->has_element_in_list_item_scope( 'LI' ) 836 ) || 837 /* 838 * An end tag whose tag name is one of: "dd", "dt": 839 * If the stack of open elements does not have an element in scope that is an 840 * HTML element with the same tag name as that of the token, then this is a 841 * parse error; ignore the token. 842 */ 843 ( 844 'LI' !== $tag_name && 845 ! $this->state->stack_of_open_elements->has_element_in_scope( $tag_name ) 846 ) 847 ) { 848 /* 849 * This is a parse error, ignore the token. 850 * 851 * @todo Indicate a parse error once it's possible. 852 */ 853 return $this->step(); 854 } 855 856 $this->generate_implied_end_tags( $tag_name ); 857 858 if ( $tag_name !== $this->state->stack_of_open_elements->current_node()->node_name ) { 859 // @todo Indicate a parse error once it's possible. This error does not impact the logic here. 860 } 861 862 $this->state->stack_of_open_elements->pop_until( $tag_name ); 756 863 return true; 757 864 … … 1224 1331 private function generate_implied_end_tags( $except_for_this_element = null ) { 1225 1332 $elements_with_implied_end_tags = array( 1333 'DD', 1334 'DT', 1335 'LI', 1226 1336 'P', 1227 1337 ); … … 1249 1359 private function generate_implied_end_tags_thoroughly() { 1250 1360 $elements_with_implied_end_tags = array( 1361 'DD', 1362 'DT', 1363 'LI', 1251 1364 'P', 1252 1365 ); -
trunk/tests/phpunit/tests/html-api/wpHtmlProcessor.php
r57248 r57264 169 169 'COL' => array( 'COL' ), 170 170 'COLGROUP' => array( 'COLGROUP' ), 171 'DD' => array( 'DD' ),172 'DT' => array( 'DT' ),173 171 'EMBED' => array( 'EMBED' ), 174 172 'FORM' => array( 'FORM' ), … … 181 179 'INPUT' => array( 'INPUT' ), 182 180 'KEYGEN' => array( 'KEYGEN' ), 183 'LI' => array( 'LI' ),184 181 'LINK' => array( 'LINK' ), 185 182 'LISTING' => array( 'LISTING' ), … … 192 189 'NOSCRIPT' => array( 'NOSCRIPT' ), 193 190 'OBJECT' => array( 'OBJECT' ), 194 'OL' => array( 'OL' ),195 191 'OPTGROUP' => array( 'OPTGROUP' ), 196 192 'OPTION' => array( 'OPTION' ), … … 219 215 'TR' => array( 'TR' ), 220 216 'TRACK' => array( 'TRACK' ), 221 'UL' => array( 'UL' ),222 217 'WBR' => array( 'WBR' ), 223 218 'XMP' => array( 'XMP' ), -
trunk/tests/phpunit/tests/html-api/wpHtmlProcessorBreadcrumbs.php
r57248 r57264 39 39 'A', 40 40 'ABBR', 41 'ACRONYM', // Neutralized 41 'ACRONYM', // Neutralized. 42 42 'ADDRESS', 43 43 'ARTICLE', … … 48 48 'BDO', 49 49 'BIG', 50 'BLINK', // Deprecated 50 'BLINK', // Deprecated. 51 51 'BUTTON', 52 52 'CANVAS', 53 'CENTER', // Neutralized 53 'CENTER', // Neutralized. 54 54 'CITE', 55 55 'CODE', 56 56 'DATA', 57 'DD', 57 58 'DATALIST', 58 59 'DFN', … … 63 64 'DIV', 64 65 'DL', 66 'DT', 65 67 'EM', 66 68 'FIELDSET', … … 80 82 'IMG', 81 83 'INS', 84 'LI', 82 85 'ISINDEX', // Deprecated 83 86 'KBD', … … 92 95 'NAV', 93 96 'NEXTID', // Deprecated 97 'OL', 94 98 'OUTPUT', 95 99 'P', … … 113 117 'TT', 114 118 'U', 119 'UL', 115 120 'VAR', 116 121 'VIDEO', … … 157 162 public function data_unsupported_elements() { 158 163 $unsupported_elements = array( 159 'APPLET', // Deprecated 164 'APPLET', // Deprecated. 160 165 'AREA', 161 166 'BASE', … … 166 171 'COL', 167 172 'COLGROUP', 168 'DD',169 'DT',170 173 'EMBED', 171 174 'FORM', … … 177 180 'IFRAME', 178 181 'INPUT', 179 'KEYGEN', // Deprecated; void 180 'LI', 182 'KEYGEN', // Deprecated; void. 181 183 'LINK', 182 184 'LISTING', // Deprecated, use PRE instead. 183 'MARQUEE', // Deprecated 185 'MARQUEE', // Deprecated. 184 186 'MATH', 185 187 'META', 186 'NOBR', // Neutralized 187 'NOEMBED', // Neutralized 188 'NOFRAMES', // Neutralized 188 'NOBR', // Neutralized. 189 'NOEMBED', // Neutralized. 190 'NOFRAMES', // Neutralized. 189 191 'NOSCRIPT', 190 192 'OBJECT', 191 'OL',192 193 'OPTGROUP', 193 194 'OPTION', 194 'PLAINTEXT', // Neutralized 195 'PLAINTEXT', // Neutralized. 195 196 'PRE', 196 'RB', // Neutralized 197 'RB', // Neutralized. 197 198 'RP', 198 199 'RT', 199 'RTC', // Neutralized 200 'RTC', // Neutralized. 200 201 'SCRIPT', 201 202 'SELECT', … … 214 215 'TR', 215 216 'TRACK', 216 'UL',217 217 'WBR', 218 218 'XMP', // Deprecated, use PRE instead. … … 349 349 'MAIN inside MAIN inside SPAN' => array( '<span><main><main target>', array( 'HTML', 'BODY', 'SPAN', 'MAIN', 'MAIN' ), 1 ), 350 350 'MAIN next to unclosed P' => array( '<p><main target>', array( 'HTML', 'BODY', 'MAIN' ), 1 ), 351 'LI after unclosed LI' => array( '<li>one<li>two<li target>three', array( 'HTML', 'BODY', 'LI' ), 3 ), 352 'LI in UL in LI' => array( '<ul><li>one<ul><li target>two', array( 'HTML', 'BODY', 'UL', 'LI', 'UL', 'LI' ), 1 ), 353 'DD and DT mutually close, LI self-closes (dt 2)' => array( '<dd><dd><dt><dt target><dd><li><li>', array( 'HTML', 'BODY', 'DT' ), 2 ), 354 'DD and DT mutually close, LI self-closes (dd 3)' => array( '<dd><dd><dt><dt><dd target><li><li>', array( 'HTML', 'BODY', 'DD' ), 3 ), 355 'DD and DT mutually close, LI self-closes (li 1)' => array( '<dd><dd><dt><dt><dd><li target><li>', array( 'HTML', 'BODY', 'DD', 'LI' ), 1 ), 356 'DD and DT mutually close, LI self-closes (li 2)' => array( '<dd><dd><dt><dt><dd><li><li target>', array( 'HTML', 'BODY', 'DD', 'LI' ), 2 ), 351 357 352 358 // H1 - H6 close out _any_ H1 - H6 when encountering _any_ of H1 - H6, making this section surprising. -
trunk/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php
r57244 r57264 226 226 227 227 /** 228 * Verifies that H1 through H6 elements close an open P element. 229 * 230 * @ticket 60215 231 * 232 * @dataProvider data_heading_elements 233 * 234 * @param string $tag_name Name of H1 - H6 element under test. 235 */ 236 public function test_in_body_heading_element_closes_open_p_tag( $tag_name ) { 237 $processor = WP_HTML_Processor::create_fragment( 238 "<p>Open<{$tag_name}>Closed P</{$tag_name}><img></p>" 239 ); 240 241 $processor->next_tag( $tag_name ); 242 $this->assertSame( 243 array( 'HTML', 'BODY', $tag_name ), 244 $processor->get_breadcrumbs(), 245 "Expected {$tag_name} to be a direct child of the BODY, having closed the open P element." 246 ); 247 248 $processor->next_tag( 'IMG' ); 249 $this->assertSame( 250 array( 'HTML', 'BODY', 'IMG' ), 251 $processor->get_breadcrumbs(), 252 'Expected IMG to be a direct child of BODY, having closed the open P element.' 253 ); 254 } 255 256 /** 257 * Data provider. 258 * 259 * @return array[]. 260 */ 261 public function data_heading_elements() { 262 return array( 263 'H1' => array( 'H1' ), 264 'H2' => array( 'H2' ), 265 'H3' => array( 'H3' ), 266 'H4' => array( 'H4' ), 267 'H5' => array( 'H5' ), 268 'H6' => array( 'H5' ), 269 ); 270 } 271 272 /** 273 * Verifies that H1 through H6 elements close an open H1 through H6 element. 274 * 275 * @ticket 60215 276 * 277 * @dataProvider data_heading_combinations 278 * 279 * @param string $first_heading H1 - H6 element appearing (unclosed) before the second. 280 * @param string $second_heading H1 - H6 element appearing after the first. 281 */ 282 public function test_in_body_heading_element_closes_other_heading_elements( $first_heading, $second_heading ) { 283 $processor = WP_HTML_Processor::create_fragment( 284 "<div><{$first_heading} first> then <{$second_heading} second> and end </{$second_heading}><img></{$first_heading}></div>" 285 ); 286 287 while ( $processor->next_tag() && null === $processor->get_attribute( 'second' ) ) { 288 continue; 289 } 290 291 $this->assertTrue( 292 $processor->get_attribute( 'second' ), 293 "Failed to find expected {$second_heading} tag." 294 ); 295 296 $this->assertSame( 297 array( 'HTML', 'BODY', 'DIV', $second_heading ), 298 $processor->get_breadcrumbs(), 299 "Expected {$second_heading} to be a direct child of the DIV, having closed the open {$first_heading} element." 300 ); 301 302 $processor->next_tag( 'IMG' ); 303 $this->assertSame( 304 array( 'HTML', 'BODY', 'DIV', 'IMG' ), 305 $processor->get_breadcrumbs(), 306 "Expected IMG to be a direct child of DIV, having closed the open {$first_heading} element." 307 ); 308 } 309 310 /** 311 * Data provider. 312 * 313 * @return array[] 314 */ 315 public function data_heading_combinations() { 316 $headings = array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ); 317 318 $combinations = array(); 319 320 // Create all unique pairs of H1 - H6 elements. 321 foreach ( $headings as $first_tag ) { 322 foreach ( $headings as $second_tag ) { 323 $combinations[ "{$first_tag} then {$second_tag}" ] = array( $first_tag, $second_tag ); 324 } 325 } 326 327 return $combinations; 328 } 329 330 /** 228 331 * Verifies that when "in body" and encountering "any other end tag" 229 332 * that the HTML processor ignores the end tag if there's a special -
trunk/tests/phpunit/tests/html-api/wpHtmlSupportRequiredHtmlProcessor.php
r56790 r57264 59 59 */ 60 60 public function test_generate_implied_end_tags_needs_support() { 61 $this->ensure_support_is_added_everywhere( 'DD' );62 $this->ensure_support_is_added_everywhere( 'DT' );63 $this->ensure_support_is_added_everywhere( 'LI' );64 61 $this->ensure_support_is_added_everywhere( 'OPTGROUP' ); 65 62 $this->ensure_support_is_added_everywhere( 'OPTION' ); … … 83 80 $this->ensure_support_is_added_everywhere( 'CAPTION' ); 84 81 $this->ensure_support_is_added_everywhere( 'COLGROUP' ); 85 $this->ensure_support_is_added_everywhere( 'DD' );86 $this->ensure_support_is_added_everywhere( 'DT' );87 $this->ensure_support_is_added_everywhere( 'LI' );88 82 $this->ensure_support_is_added_everywhere( 'OPTGROUP' ); 89 83 $this->ensure_support_is_added_everywhere( 'OPTION' ); -
trunk/tests/phpunit/tests/html-api/wpHtmlSupportRequiredOpenElements.php
r57248 r57264 121 121 */ 122 122 $this->ensure_support_is_added_everywhere( 'SVG' ); 123 124 // These elements are specific to list item scope.125 $this->ensure_support_is_added_everywhere( 'OL' );126 $this->ensure_support_is_added_everywhere( 'UL' );127 128 // This element is the only element that depends on list item scope.129 $this->ensure_support_is_added_everywhere( 'LI' );130 123 } 131 124
Note: See TracChangeset
for help on using the changeset viewer.