-
Notifications
You must be signed in to change notification settings - Fork 3.2k
WIP: HTML API: Add support for H1-H6 elements in HTML Processor #5535
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
8a2a4f6
8d2f232
f93ef13
6b45967
da446c9
c1a880f
7e9439e
e0d370c
ed98e7c
bf7a132
4b8989a
6400821
4772a7d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -102,7 +102,7 @@ | |||||||||||||||
| * - Containers: ADDRESS, BLOCKQUOTE, DETAILS, DIALOG, DIV, FOOTER, HEADER, MAIN, MENU, SPAN, SUMMARY. | ||||||||||||||||
| * - Form elements: BUTTON, FIELDSET, SEARCH. | ||||||||||||||||
| * - Formatting elements: B, BIG, CODE, EM, FONT, I, SMALL, STRIKE, STRONG, TT, U. | ||||||||||||||||
| * - Heading elements: HGROUP. | ||||||||||||||||
| * - Heading elements: H1, H2, H3, H4, H5, H6, HGROUP. | ||||||||||||||||
| * - Links: A. | ||||||||||||||||
| * - Lists: DL. | ||||||||||||||||
| * - Media elements: FIGCAPTION, FIGURE, IMG. | ||||||||||||||||
|
|
@@ -697,6 +697,60 @@ private function step_in_body() { | |||||||||||||||
| $this->state->stack_of_open_elements->pop_until( $tag_name ); | ||||||||||||||||
| return true; | ||||||||||||||||
|
|
||||||||||||||||
| /* | ||||||||||||||||
| * > A start tag whose tag name is one of: "h1", "h2", "h3", "h4", "h5", "h6" | ||||||||||||||||
| */ | ||||||||||||||||
| case '+H1': | ||||||||||||||||
| case '+H2': | ||||||||||||||||
| case '+H3': | ||||||||||||||||
| case '+H4': | ||||||||||||||||
| case '+H5': | ||||||||||||||||
| case '+H6': | ||||||||||||||||
| if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) { | ||||||||||||||||
| $this->close_a_p_element(); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| if ( | ||||||||||||||||
| in_array( | ||||||||||||||||
| $this->state->stack_of_open_elements->current_node()->node_name, | ||||||||||||||||
| array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), | ||||||||||||||||
| true | ||||||||||||||||
| ) | ||||||||||||||||
| ) { | ||||||||||||||||
| // @TODO: Indicate a parse error once it's possible. | ||||||||||||||||
| $this->state->stack_of_open_elements->pop(); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| $this->insert_html_element( $this->state->current_token ); | ||||||||||||||||
| return true; | ||||||||||||||||
|
|
||||||||||||||||
| /* | ||||||||||||||||
| * > An end tag whose tag name is one of: "h1", "h2", "h3", "h4", "h5", "h6" | ||||||||||||||||
| */ | ||||||||||||||||
| case '-H1': | ||||||||||||||||
| case '-H2': | ||||||||||||||||
| case '-H3': | ||||||||||||||||
| case '-H4': | ||||||||||||||||
| case '-H5': | ||||||||||||||||
| case '-H6': | ||||||||||||||||
| if ( ! $this->state->stack_of_open_elements->has_element_in_scope( '(internal: H1 through H6 - do not use)' ) ) { | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As an alternative to the string literal, we could spell out the whole list
Suggested change
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thank you. as we discussed, I think this is going to be very difficult to do in practice for now, specifically because of the |
||||||||||||||||
| /* | ||||||||||||||||
| * This is a parse error; ignore the token. | ||||||||||||||||
| * | ||||||||||||||||
| * @TODO: Indicate a parse error once it's possible. | ||||||||||||||||
| */ | ||||||||||||||||
| return $this->step(); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| $this->generate_implied_end_tags(); | ||||||||||||||||
|
|
||||||||||||||||
| if ( $this->state->stack_of_open_elements->current_node()->node_name !== $tag_name ) { | ||||||||||||||||
| // @TODO: Record parse error: this error doesn't impact parsing. | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| $this->state->stack_of_open_elements->pop_until( '(internal: H1 through H6 - do not use)' ); | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one's a bit trickier than the other one; we could change
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see above: if we don't need array passing I'd prefer that so we can avoid creating the array |
||||||||||||||||
| return true; | ||||||||||||||||
|
|
||||||||||||||||
| /* | ||||||||||||||||
| * > An end tag whose tag name is "p" | ||||||||||||||||
| */ | ||||||||||||||||
|
|
||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,122 @@ | ||||||||||||||
| <?php | ||||||||||||||
| /** | ||||||||||||||
| * Unit tests covering WP_HTML_Processor compliance with HTML5 semantic parsing rules | ||||||||||||||
| * for the H1 - H6 heading elements. | ||||||||||||||
| * | ||||||||||||||
| * @package WordPress | ||||||||||||||
| * @subpackage HTML-API | ||||||||||||||
| * | ||||||||||||||
| * @since 6.5.0 | ||||||||||||||
| * | ||||||||||||||
| * @group html-api | ||||||||||||||
| * | ||||||||||||||
| * @coversDefaultClass WP_HTML_Processor | ||||||||||||||
| */ | ||||||||||||||
| class Tests_HtmlApi_WpHtmlProcessorSemanticRulesHeadingElements extends WP_UnitTestCase { | ||||||||||||||
| /******************************************************************* | ||||||||||||||
| * RULES FOR "IN BODY" MODE | ||||||||||||||
| *******************************************************************/ | ||||||||||||||
|
|
||||||||||||||
| /** | ||||||||||||||
| * Verifies that H1 through H6 elements generate implied end tags. | ||||||||||||||
| * | ||||||||||||||
| * @ticket 60060 | ||||||||||||||
| * | ||||||||||||||
| * @covers WP_HTML_Processor::step | ||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't it
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. more specifically this is covering the semantic rules inside |
||||||||||||||
| * | ||||||||||||||
| * @dataProvider data_heading_elements | ||||||||||||||
| * | ||||||||||||||
| * @param string $tag_name Name of H1 - H6 element under test. | ||||||||||||||
| */ | ||||||||||||||
| public function test_in_body_heading_element_closes_open_p_tag( $tag_name ) { | ||||||||||||||
| $processor = WP_HTML_Processor::create_fragment( | ||||||||||||||
| "<p>Open<{$tag_name}>Closed P</{$tag_name}><img></p>" | ||||||||||||||
| ); | ||||||||||||||
|
|
||||||||||||||
| $processor->next_tag( $tag_name ); | ||||||||||||||
| $this->assertSame( | ||||||||||||||
| array( 'HTML', 'BODY', $tag_name ), | ||||||||||||||
| $processor->get_breadcrumbs(), | ||||||||||||||
| "Expected {$tag_name} to be a direct child of the BODY, having closed the open P element." | ||||||||||||||
| ); | ||||||||||||||
|
|
||||||||||||||
| $processor->next_tag( 'IMG' ); | ||||||||||||||
| $this->assertSame( | ||||||||||||||
| array( 'HTML', 'BODY', 'IMG' ), | ||||||||||||||
| $processor->get_breadcrumbs(), | ||||||||||||||
| 'Expected IMG to be a direct child of BODY, having closed the open P element.' | ||||||||||||||
| ); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /** | ||||||||||||||
| * Data provider. | ||||||||||||||
| * | ||||||||||||||
| * @return array[]. | ||||||||||||||
| */ | ||||||||||||||
| public function data_heading_elements() { | ||||||||||||||
| return array( | ||||||||||||||
| 'H1' => array( 'H1' ), | ||||||||||||||
| 'H2' => array( 'H2' ), | ||||||||||||||
| 'H3' => array( 'H3' ), | ||||||||||||||
| 'H4' => array( 'H4' ), | ||||||||||||||
| 'H5' => array( 'H5' ), | ||||||||||||||
| 'H6' => array( 'H5' ), | ||||||||||||||
| ); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /** | ||||||||||||||
| * Verifies that H1 through H6 elements close an open H1 through H6 element. | ||||||||||||||
| * | ||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing
Suggested change
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added in 4772a7d |
||||||||||||||
| * @dataProvider data_heading_combinations | ||||||||||||||
| * | ||||||||||||||
| * @param string $first_heading H1 - H6 element appearing (unclosed) before the second. | ||||||||||||||
| * @param string $second_heading H1 - H6 element appearing after the first. | ||||||||||||||
| */ | ||||||||||||||
| public function test_in_body_heading_element_closes_other_heading_elements( $first_heading, $second_heading ) { | ||||||||||||||
| $processor = WP_HTML_Processor::create_fragment( | ||||||||||||||
| "<div><{$first_heading} first> then <{$second_heading} second> and end </{$second_heading}><img></{$first_heading}></div>" | ||||||||||||||
| ); | ||||||||||||||
|
|
||||||||||||||
| while ( $processor->next_tag() && null === $processor->get_attribute( 'second' ) ) { | ||||||||||||||
| continue; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| $this->assertTrue( | ||||||||||||||
| $processor->get_attribute( 'second' ), | ||||||||||||||
| "Failed to find expected {$second_heading} tag." | ||||||||||||||
| ); | ||||||||||||||
|
|
||||||||||||||
| $this->assertSame( | ||||||||||||||
| array( 'HTML', 'BODY', 'DIV', $second_heading ), | ||||||||||||||
| $processor->get_breadcrumbs(), | ||||||||||||||
| "Expected {$second_heading} to be a direct child of the DIV, having closed the open {$first_heading} element." | ||||||||||||||
| ); | ||||||||||||||
|
|
||||||||||||||
| $processor->next_tag( 'IMG' ); | ||||||||||||||
| $this->assertSame( | ||||||||||||||
| array( 'HTML', 'BODY', 'DIV', 'IMG' ), | ||||||||||||||
| $processor->get_breadcrumbs(), | ||||||||||||||
| "Expected IMG to be a direct child of DIV, having closed the open {$first_heading} element." | ||||||||||||||
| ); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /** | ||||||||||||||
| * Data provider. | ||||||||||||||
| * | ||||||||||||||
| * @return array[] | ||||||||||||||
| */ | ||||||||||||||
| public function data_heading_combinations() { | ||||||||||||||
| $headings = array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ); | ||||||||||||||
|
|
||||||||||||||
| $combinations = array(); | ||||||||||||||
|
|
||||||||||||||
| // Create all unique pairs of H1 - H6 elements. | ||||||||||||||
| foreach ( $headings as $first_tag ) { | ||||||||||||||
| foreach ( $headings as $second_tag ) { | ||||||||||||||
| $combinations[ "{$first_tag} then {$second_tag}" ] = array( $first_tag, $second_tag ); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| return $combinations; | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I missed this; this looks accidental, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, thanks. Not sure what happened here.
added back in 6400821