[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * HTML API: WP_HTML_Open_Elements class 4 * 5 * @package WordPress 6 * @subpackage HTML-API 7 * @since 6.4.0 8 */ 9 10 /** 11 * Core class used by the HTML processor during HTML parsing 12 * for managing the stack of open elements. 13 * 14 * This class is designed for internal use by the HTML processor. 15 * 16 * > Initially, the stack of open elements is empty. The stack grows 17 * > downwards; the topmost node on the stack is the first one added 18 * > to the stack, and the bottommost node of the stack is the most 19 * > recently added node in the stack (notwithstanding when the stack 20 * > is manipulated in a random access fashion as part of the handling 21 * > for misnested tags). 22 * 23 * @since 6.4.0 24 * 25 * @access private 26 * 27 * @see https://html.spec.whatwg.org/#stack-of-open-elements 28 * @see WP_HTML_Processor 29 */ 30 class WP_HTML_Open_Elements { 31 /** 32 * Holds the stack of open element references. 33 * 34 * @since 6.4.0 35 * 36 * @var WP_HTML_Token[] 37 */ 38 public $stack = array(); 39 40 /** 41 * Whether a P element is in button scope currently. 42 * 43 * This class optimizes scope lookup by pre-calculating 44 * this value when elements are added and removed to the 45 * stack of open elements which might change its value. 46 * This avoids frequent iteration over the stack. 47 * 48 * @since 6.4.0 49 * 50 * @var bool 51 */ 52 private $has_p_in_button_scope = false; 53 54 /** 55 * Reports if a specific node is in the stack of open elements. 56 * 57 * @since 6.4.0 58 * 59 * @param WP_HTML_Token $token Look for this node in the stack. 60 * @return bool Whether the referenced node is in the stack of open elements. 61 */ 62 public function contains_node( $token ) { 63 foreach ( $this->walk_up() as $item ) { 64 if ( $token->bookmark_name === $item->bookmark_name ) { 65 return true; 66 } 67 } 68 69 return false; 70 } 71 72 /** 73 * Returns how many nodes are currently in the stack of open elements. 74 * 75 * @since 6.4.0 76 * 77 * @return int How many node are in the stack of open elements. 78 */ 79 public function count() { 80 return count( $this->stack ); 81 } 82 83 /** 84 * Returns the node at the end of the stack of open elements, 85 * if one exists. If the stack is empty, returns null. 86 * 87 * @since 6.4.0 88 * 89 * @return WP_HTML_Token|null Last node in the stack of open elements, if one exists, otherwise null. 90 */ 91 public function current_node() { 92 $current_node = end( $this->stack ); 93 94 return $current_node ? $current_node : null; 95 } 96 97 /** 98 * Returns whether an element is in a specific scope. 99 * 100 * ## HTML Support 101 * 102 * This function skips checking for the termination list because there 103 * are no supported elements which appear in the termination list. 104 * 105 * @since 6.4.0 106 * 107 * @see https://html.spec.whatwg.org/#has-an-element-in-the-specific-scope 108 * 109 * @param string $tag_name Name of tag check. 110 * @param string[] $termination_list List of elements that terminate the search. 111 * @return bool Whether the element was found in a specific scope. 112 */ 113 public function has_element_in_specific_scope( $tag_name, $termination_list ) { 114 foreach ( $this->walk_up() as $node ) { 115 if ( $node->node_name === $tag_name ) { 116 return true; 117 } 118 119 if ( 120 '(internal: H1 through H6 - do not use)' === $tag_name && 121 in_array( $node->node_name, array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), true ) 122 ) { 123 return true; 124 } 125 126 switch ( $node->node_name ) { 127 case 'HTML': 128 return false; 129 } 130 131 if ( in_array( $node->node_name, $termination_list, true ) ) { 132 return false; 133 } 134 } 135 136 return false; 137 } 138 139 /** 140 * Returns whether a particular element is in scope. 141 * 142 * @since 6.4.0 143 * 144 * @see https://html.spec.whatwg.org/#has-an-element-in-scope 145 * 146 * @param string $tag_name Name of tag to check. 147 * @return bool Whether given element is in scope. 148 */ 149 public function has_element_in_scope( $tag_name ) { 150 return $this->has_element_in_specific_scope( 151 $tag_name, 152 array( 153 154 /* 155 * Because it's not currently possible to encounter 156 * one of the termination elements, they don't need 157 * to be listed here. If they were, they would be 158 * unreachable and only waste CPU cycles while 159 * scanning through HTML. 160 */ 161 ) 162 ); 163 } 164 165 /** 166 * Returns whether a particular element is in list item scope. 167 * 168 * @since 6.4.0 169 * @since 6.5.0 Implemented: no longer throws on every invocation. 170 * 171 * @see https://html.spec.whatwg.org/#has-an-element-in-list-item-scope 172 * 173 * @param string $tag_name Name of tag to check. 174 * @return bool Whether given element is in scope. 175 */ 176 public function has_element_in_list_item_scope( $tag_name ) { 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 ); 185 } 186 187 /** 188 * Returns whether a particular element is in button scope. 189 * 190 * @since 6.4.0 191 * 192 * @see https://html.spec.whatwg.org/#has-an-element-in-button-scope 193 * 194 * @param string $tag_name Name of tag to check. 195 * @return bool Whether given element is in scope. 196 */ 197 public function has_element_in_button_scope( $tag_name ) { 198 return $this->has_element_in_specific_scope( $tag_name, array( 'BUTTON' ) ); 199 } 200 201 /** 202 * Returns whether a particular element is in table scope. 203 * 204 * @since 6.4.0 205 * 206 * @see https://html.spec.whatwg.org/#has-an-element-in-table-scope 207 * 208 * @throws WP_HTML_Unsupported_Exception Always until this function is implemented. 209 * 210 * @param string $tag_name Name of tag to check. 211 * @return bool Whether given element is in scope. 212 */ 213 public function has_element_in_table_scope( $tag_name ) { 214 throw new WP_HTML_Unsupported_Exception( 'Cannot process elements depending on table scope.' ); 215 216 return false; // The linter requires this unreachable code until the function is implemented and can return. 217 } 218 219 /** 220 * Returns whether a particular element is in select scope. 221 * 222 * @since 6.4.0 223 * 224 * @see https://html.spec.whatwg.org/#has-an-element-in-select-scope 225 * 226 * @throws WP_HTML_Unsupported_Exception Always until this function is implemented. 227 * 228 * @param string $tag_name Name of tag to check. 229 * @return bool Whether given element is in scope. 230 */ 231 public function has_element_in_select_scope( $tag_name ) { 232 throw new WP_HTML_Unsupported_Exception( 'Cannot process elements depending on select scope.' ); 233 234 return false; // The linter requires this unreachable code until the function is implemented and can return. 235 } 236 237 /** 238 * Returns whether a P is in BUTTON scope. 239 * 240 * @since 6.4.0 241 * 242 * @see https://html.spec.whatwg.org/#has-an-element-in-button-scope 243 * 244 * @return bool Whether a P is in BUTTON scope. 245 */ 246 public function has_p_in_button_scope() { 247 return $this->has_p_in_button_scope; 248 } 249 250 /** 251 * Pops a node off of the stack of open elements. 252 * 253 * @since 6.4.0 254 * 255 * @see https://html.spec.whatwg.org/#stack-of-open-elements 256 * 257 * @return bool Whether a node was popped off of the stack. 258 */ 259 public function pop() { 260 $item = array_pop( $this->stack ); 261 262 if ( null === $item ) { 263 return false; 264 } 265 266 $this->after_element_pop( $item ); 267 return true; 268 } 269 270 /** 271 * Pops nodes off of the stack of open elements until one with the given tag name has been popped. 272 * 273 * @since 6.4.0 274 * 275 * @see WP_HTML_Open_Elements::pop 276 * 277 * @param string $tag_name Name of tag that needs to be popped off of the stack of open elements. 278 * @return bool Whether a tag of the given name was found and popped off of the stack of open elements. 279 */ 280 public function pop_until( $tag_name ) { 281 foreach ( $this->walk_up() as $item ) { 282 $this->pop(); 283 284 if ( 285 '(internal: H1 through H6 - do not use)' === $tag_name && 286 in_array( $item->node_name, array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), true ) 287 ) { 288 return true; 289 } 290 291 if ( $tag_name === $item->node_name ) { 292 return true; 293 } 294 } 295 296 return false; 297 } 298 299 /** 300 * Pushes a node onto the stack of open elements. 301 * 302 * @since 6.4.0 303 * 304 * @see https://html.spec.whatwg.org/#stack-of-open-elements 305 * 306 * @param WP_HTML_Token $stack_item Item to add onto stack. 307 */ 308 public function push( $stack_item ) { 309 $this->stack[] = $stack_item; 310 $this->after_element_push( $stack_item ); 311 } 312 313 /** 314 * Removes a specific node from the stack of open elements. 315 * 316 * @since 6.4.0 317 * 318 * @param WP_HTML_Token $token The node to remove from the stack of open elements. 319 * @return bool Whether the node was found and removed from the stack of open elements. 320 */ 321 public function remove_node( $token ) { 322 foreach ( $this->walk_up() as $position_from_end => $item ) { 323 if ( $token->bookmark_name !== $item->bookmark_name ) { 324 continue; 325 } 326 327 $position_from_start = $this->count() - $position_from_end - 1; 328 array_splice( $this->stack, $position_from_start, 1 ); 329 $this->after_element_pop( $item ); 330 return true; 331 } 332 333 return false; 334 } 335 336 337 /** 338 * Steps through the stack of open elements, starting with the top element 339 * (added first) and walking downwards to the one added last. 340 * 341 * This generator function is designed to be used inside a "foreach" loop. 342 * 343 * Example: 344 * 345 * $html = '<em><strong><a>We are here'; 346 * foreach ( $stack->walk_down() as $node ) { 347 * echo "{$node->node_name} -> "; 348 * } 349 * > EM -> STRONG -> A -> 350 * 351 * To start with the most-recently added element and walk towards the top, 352 * see WP_HTML_Open_Elements::walk_up(). 353 * 354 * @since 6.4.0 355 */ 356 public function walk_down() { 357 $count = count( $this->stack ); 358 359 for ( $i = 0; $i < $count; $i++ ) { 360 yield $this->stack[ $i ]; 361 } 362 } 363 364 /** 365 * Steps through the stack of open elements, starting with the bottom element 366 * (added last) and walking upwards to the one added first. 367 * 368 * This generator function is designed to be used inside a "foreach" loop. 369 * 370 * Example: 371 * 372 * $html = '<em><strong><a>We are here'; 373 * foreach ( $stack->walk_up() as $node ) { 374 * echo "{$node->node_name} -> "; 375 * } 376 * > A -> STRONG -> EM -> 377 * 378 * To start with the first added element and walk towards the bottom, 379 * see WP_HTML_Open_Elements::walk_down(). 380 * 381 * @since 6.4.0 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 389 for ( $i = count( $this->stack ) - 1; $i >= 0; $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; 398 } 399 } 400 401 /* 402 * Internal helpers. 403 */ 404 405 /** 406 * Updates internal flags after adding an element. 407 * 408 * Certain conditions (such as "has_p_in_button_scope") are maintained here as 409 * flags that are only modified when adding and removing elements. This allows 410 * the HTML Processor to quickly check for these conditions instead of iterating 411 * over the open stack elements upon each new tag it encounters. These flags, 412 * however, need to be maintained as items are added and removed from the stack. 413 * 414 * @since 6.4.0 415 * 416 * @param WP_HTML_Token $item Element that was added to the stack of open elements. 417 */ 418 public function after_element_push( $item ) { 419 /* 420 * When adding support for new elements, expand this switch to trap 421 * cases where the precalculated value needs to change. 422 */ 423 switch ( $item->node_name ) { 424 case 'BUTTON': 425 $this->has_p_in_button_scope = false; 426 break; 427 428 case 'P': 429 $this->has_p_in_button_scope = true; 430 break; 431 } 432 } 433 434 /** 435 * Updates internal flags after removing an element. 436 * 437 * Certain conditions (such as "has_p_in_button_scope") are maintained here as 438 * flags that are only modified when adding and removing elements. This allows 439 * the HTML Processor to quickly check for these conditions instead of iterating 440 * over the open stack elements upon each new tag it encounters. These flags, 441 * however, need to be maintained as items are added and removed from the stack. 442 * 443 * @since 6.4.0 444 * 445 * @param WP_HTML_Token $item Element that was removed from the stack of open elements. 446 */ 447 public function after_element_pop( $item ) { 448 /* 449 * When adding support for new elements, expand this switch to trap 450 * cases where the precalculated value needs to change. 451 */ 452 switch ( $item->node_name ) { 453 case 'BUTTON': 454 $this->has_p_in_button_scope = $this->has_element_in_button_scope( 'P' ); 455 break; 456 457 case 'P': 458 $this->has_p_in_button_scope = $this->has_element_in_button_scope( 'P' ); 459 break; 460 } 461 } 462 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Sun Apr 28 08:20:02 2024 | Cross-referenced by PHPXref |