Changeset 56702
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/html-api/class-wp-html-processor.php
r56565 r56702 358 358 * @type string|null $class_name Tag must contain this whole class name to match. 359 359 * @type string[] $breadcrumbs DOM sub-path at which element is found, e.g. `array( 'FIGURE', 'IMG' )`. 360 * May also contain the wildcard `*` which matches a single element, e.g. `array( 'SECTION', '*' )`. 360 361 * } 361 362 * @return bool Whether a tag was matched. … … 407 408 $match_offset = isset( $query['match_offset'] ) ? (int) $query['match_offset'] : 1; 408 409 409 $crumb = end( $breadcrumbs );410 $target = strtoupper( $crumb );411 410 while ( $match_offset > 0 && $this->step() ) { 412 if ( $t arget !== $this->get_tag()) {413 continue;411 if ( $this->matches_breadcrumbs( $breadcrumbs ) && 0 === --$match_offset ) { 412 return true; 414 413 } 415 416 // Look up the stack to see if the breadcrumbs match. 417 foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) { 418 if ( strtoupper( $crumb ) !== $node->node_name ) { 419 break; 420 } 421 422 $crumb = prev( $breadcrumbs ); 423 if ( false === $crumb && 0 === --$match_offset && ! $this->is_tag_closer() ) { 424 return true; 425 } 414 } 415 416 return false; 417 } 418 419 /** 420 * Indicates if the currently-matched tag matches the given breadcrumbs. 421 * 422 * A "*" represents a single tag wildcard, where any tag matches, but not no tags. 423 * 424 * At some point this function _may_ support a `**` syntax for matching any number 425 * of unspecified tags in the breadcrumb stack. This has been intentionally left 426 * out, however, to keep this function simple and to avoid introducing backtracking, 427 * which could open up surprising performance breakdowns. 428 * 429 * Example: 430 * 431 * $processor = WP_HTML_Processor::createFragment( '<div><span><figure><img></figure></span></div>' ); 432 * $processor->next_tag( 'img' ); 433 * true === $processor->matches_breadcrumbs( array( 'figure', 'img' ) ); 434 * true === $processor->matches_breadcrumbs( array( 'span', 'figure', 'img' ) ); 435 * false === $processor->matches_breadcrumbs( array( 'span', 'img' ) ); 436 * true === $processor->matches_breadcrumbs( array( 'span', '*', 'img' ) ); 437 * 438 * @since 6.4.0 439 * 440 * @param string[] $breadcrumbs DOM sub-path at which element is found, e.g. `array( 'FIGURE', 'IMG' )`. 441 * May also contain the wildcard `*` which matches a single element, e.g. `array( 'SECTION', '*' )`. 442 * @return bool Whether the currently-matched tag is found at the given nested structure. 443 */ 444 public function matches_breadcrumbs( $breadcrumbs ) { 445 if ( ! $this->get_tag() ) { 446 return false; 447 } 448 449 // Everything matches when there are zero constraints. 450 if ( 0 === count( $breadcrumbs ) ) { 451 return true; 452 } 453 454 // Start at the last crumb. 455 $crumb = end( $breadcrumbs ); 456 457 if ( '*' !== $crumb && $this->get_tag() !== strtoupper( $crumb ) ) { 458 return false; 459 } 460 461 foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) { 462 $crumb = strtoupper( current( $breadcrumbs ) ); 463 464 if ( '*' !== $crumb && $node->node_name !== $crumb ) { 465 return false; 426 466 } 427 467 428 $crumb = end( $breadcrumbs ); 468 if ( false === prev( $breadcrumbs ) ) { 469 return true; 470 } 429 471 } 430 472 -
trunk/tests/phpunit/tests/html-api/wpHtmlProcessorBreadcrumbs.php
r56380 r56702 354 354 355 355 /** 356 * @ticket 59400 357 * 358 * @dataProvider data_html_with_breadcrumbs_of_various_specificity 359 * 360 * @param string $html_with_target_node HTML with a node containing a "target" attribute. 361 * @param string[] $breadcrumbs Breadcrumbs to test at the target node. 362 * @param bool $should_match Whether the target node should match the breadcrumbs. 363 */ 364 public function test_reports_if_tag_matches_breadcrumbs_of_various_specificity( $html_with_target_node, $breadcrumbs, $should_match ) { 365 $processor = WP_HTML_Processor::createFragment( $html_with_target_node ); 366 while ( $processor->next_tag() && null === $processor->get_attribute( 'target' ) ) { 367 continue; 368 } 369 370 $matches = $processor->matches_breadcrumbs( $breadcrumbs ); 371 $path = implode( ', ', $breadcrumbs ); 372 if ( $should_match ) { 373 $this->assertTrue( $matches, "HTML tag {$processor->get_tag()} should have matched breadcrumbs but didn't: {$path}." ); 374 } else { 375 $this->assertFalse( $matches, "HTML tag {$processor->get_tag()} should not have matched breadcrumbs but did: {$path}." ); 376 } 377 } 378 379 /** 380 * Data provider. 381 * 382 * @return array[]. 383 */ 384 public function data_html_with_breadcrumbs_of_various_specificity() { 385 return array( 386 // Test with void elements. 387 'Inner IMG' => array( '<div><span><figure><img target></figure></span></div>', array( 'span', 'figure', 'img' ), true ), 388 'Inner IMG wildcard' => array( '<div><span><figure><img target></figure></span></div>', array( 'span', '*', 'img' ), true ), 389 'Inner IMG no wildcard' => array( '<div><span><figure><img target></figure></span></div>', array( 'span', 'img' ), false ), 390 'Full specification' => array( '<div><span><figure><img target></figure></span></div>', array( 'html', 'body', 'div', 'span', 'figure', 'img' ), true ), 391 'Invalid Full specification' => array( '<div><span><figure><img target></figure></span></div>', array( 'html', 'div', 'span', 'figure', 'img' ), false ), 392 393 // Test also with non-void elements that open and close. 394 'Inner P' => array( '<div><span><figure><p target></figure></span></div>', array( 'span', 'figure', 'p' ), true ), 395 'Inner P wildcard' => array( '<div><span><figure><p target></figure></span></div>', array( 'span', '*', 'p' ), true ), 396 'Inner P no wildcard' => array( '<div><span><figure><p target></figure></span></div>', array( 'span', 'p' ), false ), 397 'Full specification (P)' => array( '<div><span><figure><p target></figure></span></div>', array( 'html', 'body', 'div', 'span', 'figure', 'p' ), true ), 398 'Invalid Full specification (P)' => array( '<div><span><figure><p target></figure></span></div>', array( 'html', 'div', 'span', 'figure', 'p' ), false ), 399 400 // Ensure that matches aren't on tag closers. 401 'Inner P' => array( '<div><span><figure></p target></figure></span></div>', array( 'span', 'figure', 'p' ), false ), 402 'Inner P wildcard' => array( '<div><span><figure></p target></figure></span></div>', array( 'span', '*', 'p' ), false ), 403 'Inner P no wildcard' => array( '<div><span><figure></p target></figure></span></div>', array( 'span', 'p' ), false ), 404 'Full specification (P)' => array( '<div><span><figure></p target></figure></span></div>', array( 'html', 'body', 'div', 'span', 'figure', 'p' ), false ), 405 'Invalid Full specification (P)' => array( '<div><span><figure></p target></figure></span></div>', array( 'html', 'div', 'span', 'figure', 'p' ), false ), 406 407 // Test wildcard behaviors. 408 'Single wildcard element' => array( '<figure><code><div><p><span><img target></span></p></div></code></figure>', array( '*' ), true ), 409 'Child of wildcard element' => array( '<figure><code><div><p><span><img target></span></p></div></code></figure>', array( 'SPAN', '*' ), true ), 410 ); 411 } 412 413 /** 356 414 * Ensures that the ability to set attributes isn't broken by the HTML Processor. 357 415 *
Note: See TracChangeset
for help on using the changeset viewer.