forked from WordPress/wordpress-develop
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwpHtmlProcessor.php
More file actions
295 lines (269 loc) · 10.2 KB
/
wpHtmlProcessor.php
File metadata and controls
295 lines (269 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
<?php
/**
* Unit tests covering WP_HTML_Processor functionality.
*
* @package WordPress
* @subpackage HTML-API
*
* @since 6.4.0
*
* @group html-api
*
* @coversDefaultClass WP_HTML_Processor
*/
class Tests_HtmlApi_WpHtmlProcessor extends WP_UnitTestCase {
/**
* Ensure that the HTML Processor's public constructor function warns a developer to call
* the static creator methods instead of directly instantiating a new class.
*
* The Tag Processor's constructor method is public and PHP doesn't allow changing the
* visibility for a method on a subclass, which means that the HTML Processor must
* maintain the public interface. However, constructors cannot fail to construct, so
* if there are pre-conditions (such as the context node, the encoding form, and the
* parsing mode with the HTML Processor) these must be handled through static factory
* methods on the class.
*
* The HTML Processor requires a sentinel string as an optional parameter that hints
* at using the static methods. In the absence of the optional parameter it instructs
* the callee that it should be using those static methods instead.
*
* @ticket 58517
*
* @covers WP_HTML_Processor::__construct
*/
public function test_warns_that_the_static_creator_methods_should_be_called_instead_of_the_public_constructor() {
$this->setExpectedIncorrectUsage( 'WP_HTML_Processor::__construct' );
new WP_HTML_Processor( '<p>Light roast.</p>' );
$this->assertNotNull(
$this->caught_doing_it_wrong['WP_HTML_Processor::__construct'],
"Calling the public constructor should warn to call the static creator methods instead, but didn't."
);
}
/**
* Once stepping to the end of the document, WP_HTML_Processor::get_tag
* should no longer report a tag. It should report `null` because there
* is no tag matched or open.
*
* @ticket 59167
*
* @covers WP_HTML_Processor::get_tag
*/
public function test_get_tag_is_null_once_document_is_finished() {
$p = WP_HTML_Processor::create_fragment( '<div class="test">Test</div>' );
$p->next_tag();
$this->assertSame( 'DIV', $p->get_tag() );
$this->assertFalse( $p->next_tag() );
$this->assertNull( $p->get_tag() );
}
/**
* Ensures that the HTML Processor maintains its internal state through seek calls.
*
* Because the HTML Processor must track a stack of open elements and active formatting
* elements, when it seeks to another location within its document it must adjust those
* stacks, its internal state, in such a way that they remain valid after the seek.
*
* For instance, if currently matched inside an LI element and the Processor seeks to
* an earlier location before the parent UL, then it should not report that it's still
* inside an open LI element.
*
* @ticket 58517
*
* @covers WP_HTML_Processor::next_tag
* @covers WP_HTML_Processor::seek
*/
public function test_clear_to_navigate_after_seeking() {
$p = WP_HTML_Processor::create_fragment( '<div one><strong></strong></div><p><strong two></strong></p>' );
while ( $p->next_tag() ) {
// Create a bookmark before entering a stack of elements and formatting elements.
if ( null !== $p->get_attribute( 'one' ) ) {
$this->assertTrue( $p->set_bookmark( 'one' ) );
continue;
}
// Create a bookmark inside of that stack.
if ( null !== $p->get_attribute( 'two' ) ) {
$p->set_bookmark( 'two' );
break;
}
}
// Ensure that it's possible to seek back to the outside location.
$this->assertTrue( $p->seek( 'one' ), 'Could not seek to earlier-seen location.' );
$this->assertSame( 'DIV', $p->get_tag(), "Should have jumped back to DIV but found {$p->get_tag()} instead." );
/*
* Ensure that the P element from the inner location isn't still on the stack of open elements.
* If it were, then the first STRONG element, inside the outer DIV would match the next call.
*/
$this->assertTrue( $p->next_tag( array( 'breadcrumbs' => array( 'P', 'STRONG' ) ) ), 'Failed to find given location after seeking.' );
// Only if the stack is properly managed will the processor advance to the inner STRONG element.
$this->assertTrue( $p->get_attribute( 'two' ), "Found the wrong location given the breadcrumbs, at {$p->get_tag()}." );
// Ensure that in seeking backwards the processor reports the correct full set of breadcrumbs.
$this->assertTrue( $p->seek( 'one' ), 'Failed to jump back to first bookmark.' );
$this->assertSame( array( 'HTML', 'BODY', 'DIV' ), $p->get_breadcrumbs(), 'Found wrong set of breadcrumbs navigating to node "one".' );
// Ensure that in seeking forwards the processor reports the correct full set of breadcrumbs.
$this->assertTrue( $p->seek( 'two' ), 'Failed to jump forward to second bookmark.' );
$this->assertTrue( $p->get_attribute( 'two' ), "Found the wrong location given the bookmark, at {$p->get_tag()}." );
$this->assertSame( array( 'HTML', 'BODY', 'P', 'STRONG' ), $p->get_breadcrumbs(), 'Found wrong set of bookmarks navigating to node "two".' );
}
/**
* Ensures that support is added for reconstructing active formatting elements
* before the HTML Processor handles situations with unclosed formats requiring it.
*
* @ticket 58517
*
* @covers WP_HTML_Processor::reconstruct_active_formatting_elements
*/
public function test_fails_to_reconstruct_formatting_elements() {
$p = WP_HTML_Processor::create_fragment( '<p><em>One<p><em>Two<p><em>Three<p><em>Four' );
$this->assertTrue( $p->next_tag( 'EM' ), 'Could not find first EM.' );
$this->assertFalse( $p->next_tag( 'EM' ), 'Should have aborted before finding second EM as it required reconstructing the first EM.' );
}
/**
* Ensure non-nesting tags do not nest.
*
* @ticket 60283
*
* @covers WP_HTML_Processor::step_in_body
* @covers WP_HTML_Processor::is_void
*
* @dataProvider data_void_tags
*
* @param string $tag_name Name of void tag under test.
*/
public function test_cannot_nest_void_tags( $tag_name ) {
$processor = WP_HTML_Processor::create_fragment( "<{$tag_name}><div>" );
/*
* This HTML represents the same as the following HTML,
* assuming that it were provided `<img>` as the tag:
*
* <html>
* <body>
* <img>
* <div></div>
* </body>
* </html>
*/
$found_tag = $processor->next_tag();
if ( WP_HTML_Processor::ERROR_UNSUPPORTED === $processor->get_last_error() ) {
$this->markTestSkipped( "Tag {$tag_name} is not supported." );
}
$this->assertTrue(
$found_tag,
"Could not find first {$tag_name}."
);
$this->assertSame(
array( 'HTML', 'BODY', $tag_name ),
$processor->get_breadcrumbs(),
'Found incorrect nesting of first element.'
);
$this->assertTrue(
$processor->next_tag(),
'Should have found the DIV as the second tag.'
);
$this->assertSame(
array( 'HTML', 'BODY', 'DIV' ),
$processor->get_breadcrumbs(),
"DIV should have been a sibling of the {$tag_name}."
);
}
/**
* Data provider.
*
* @return array[]
*/
public function data_void_tags() {
return array(
'AREA' => array( 'AREA' ),
'BASE' => array( 'BASE' ),
'BR' => array( 'BR' ),
'COL' => array( 'COL' ),
'EMBED' => array( 'EMBED' ),
'HR' => array( 'HR' ),
'IMG' => array( 'IMG' ),
'INPUT' => array( 'INPUT' ),
'KEYGEN' => array( 'KEYGEN' ),
'LINK' => array( 'LINK' ),
'META' => array( 'META' ),
'SOURCE' => array( 'SOURCE' ),
'TRACK' => array( 'TRACK' ),
'WBR' => array( 'WBR' ),
);
}
/**
* Ensures that special handling of unsupported tags is cleaned up
* as handling is implemented. Otherwise there's risk of leaving special
* handling (that is never reached) when tag handling is implemented.
*
* @ticket 60092
*
* @dataProvider data_unsupported_special_in_body_tags
*
* @covers WP_HTML_Processor::step_in_body
*
* @param string $tag_name Name of the tag to test.
*/
public function test_step_in_body_fails_on_unsupported_tags( $tag_name ) {
$fragment = WP_HTML_Processor::create_fragment( '<' . $tag_name . '></' . $tag_name . '>' );
$this->assertFalse( $fragment->next_tag(), 'Should fail to find tag: ' . $tag_name . '.' );
$this->assertEquals( $fragment->get_last_error(), WP_HTML_Processor::ERROR_UNSUPPORTED, 'Should have unsupported last error.' );
}
/**
* Data provider.
*
* @return array[]
*/
public function data_unsupported_special_in_body_tags() {
return array(
'APPLET' => array( 'APPLET' ),
'BASE' => array( 'BASE' ),
'BASEFONT' => array( 'BASEFONT' ),
'BGSOUND' => array( 'BGSOUND' ),
'BODY' => array( 'BODY' ),
'CAPTION' => array( 'CAPTION' ),
'COL' => array( 'COL' ),
'COLGROUP' => array( 'COLGROUP' ),
'FORM' => array( 'FORM' ),
'FRAME' => array( 'FRAME' ),
'FRAMESET' => array( 'FRAMESET' ),
'HEAD' => array( 'HEAD' ),
'HTML' => array( 'HTML' ),
'IFRAME' => array( 'IFRAME' ),
'INPUT' => array( 'INPUT' ),
'LINK' => array( 'LINK' ),
'LISTING' => array( 'LISTING' ),
'MARQUEE' => array( 'MARQUEE' ),
'MATH' => array( 'MATH' ),
'META' => array( 'META' ),
'NOBR' => array( 'NOBR' ),
'NOEMBED' => array( 'NOEMBED' ),
'NOFRAMES' => array( 'NOFRAMES' ),
'NOSCRIPT' => array( 'NOSCRIPT' ),
'OBJECT' => array( 'OBJECT' ),
'OPTGROUP' => array( 'OPTGROUP' ),
'OPTION' => array( 'OPTION' ),
'PARAM' => array( 'PARAM' ),
'PLAINTEXT' => array( 'PLAINTEXT' ),
'PRE' => array( 'PRE' ),
'RB' => array( 'RB' ),
'RP' => array( 'RP' ),
'RT' => array( 'RT' ),
'RTC' => array( 'RTC' ),
'SARCASM' => array( 'SARCASM' ),
'SCRIPT' => array( 'SCRIPT' ),
'SELECT' => array( 'SELECT' ),
'SOURCE' => array( 'SOURCE' ),
'STYLE' => array( 'STYLE' ),
'SVG' => array( 'SVG' ),
'TABLE' => array( 'TABLE' ),
'TBODY' => array( 'TBODY' ),
'TD' => array( 'TD' ),
'TEMPLATE' => array( 'TEMPLATE' ),
'TEXTAREA' => array( 'TEXTAREA' ),
'TFOOT' => array( 'TFOOT' ),
'TH' => array( 'TH' ),
'THEAD' => array( 'THEAD' ),
'TITLE' => array( 'TITLE' ),
'TR' => array( 'TR' ),
'TRACK' => array( 'TRACK' ),
'XMP' => array( 'XMP' ),
);
}
}