/[suikacvs]/markup/html/whatpm/Whatpm/HTML.pm.src
Suika

Contents of /markup/html/whatpm/Whatpm/HTML.pm.src

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.204 - (show annotations) (download) (as text)
Sun Oct 5 05:59:35 2008 UTC (18 years ago) by wakaba
Branch: MAIN
Changes since 1.203: +15 -14 lines
File MIME type: application/x-wais-source
++ whatpm/t/ChangeLog	5 Oct 2008 05:38:58 -0000
	* content-model-1.dat, content-model-2.dat: Test results on
	|label| contents fixed.

	* ContentChecker.t: |dom-conformance/html-form-label.dat| added.

	* dom-conformance/: New directory.

2008-10-05  Wakaba  <wakaba@suika.fam.cx>

++ whatpm/t/dom-conformance/ChangeLog	5 Oct 2008 05:35:48 -0000
2008-10-05  Wakaba  <wakaba@suika.fam.cx>

	* html-form-label.dat: New test data file.

	* ChangeLog: New file.

++ whatpm/Whatpm/ChangeLog	5 Oct 2008 05:58:50 -0000
	* ContentChecker.pod: Note on internal flags is added.

2008-10-05  Wakaba  <wakaba@suika.fam.cx>

++ whatpm/Whatpm/ContentChecker/ChangeLog	5 Oct 2008 05:36:36 -0000
2008-10-05  Wakaba  <wakaba@suika.fam.cx>

	* HTML.pm: <label> content conformance checking implemented.

1 package Whatpm::HTML;
2 use strict;
3 our $VERSION=do{my @r=(q$Revision: 1.203 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
4 use Error qw(:try);
5
6 ## NOTE: This module don't check all HTML5 parse errors; character
7 ## encoding related parse errors are expected to be handled by relevant
8 ## modules.
9 ## Parse errors for control characters that are not allowed in HTML5
10 ## documents, for surrogate code points, and for noncharacter code
11 ## points, as well as U+FFFD substitions for characters whose code points
12 ## is higher than U+10FFFF may be detected by combining the parser with
13 ## the checker implemented by Whatpm::Charset::UnicodeChecker (for its
14 ## usage example, see |t/HTML-tree.t| in the Whatpm package or the
15 ## WebHACC::Language::HTML module in the WebHACC package).
16
17 ## ISSUE:
18 ## var doc = implementation.createDocument (null, null, null);
19 ## doc.write ('');
20 ## alert (doc.compatMode);
21
22 require IO::Handle;
23
24 my $HTML_NS = q<http://www.w3.org/1999/xhtml>;
25 my $MML_NS = q<http://www.w3.org/1998/Math/MathML>;
26 my $SVG_NS = q<http://www.w3.org/2000/svg>;
27 my $XLINK_NS = q<http://www.w3.org/1999/xlink>;
28 my $XML_NS = q<http://www.w3.org/XML/1998/namespace>;
29 my $XMLNS_NS = q<http://www.w3.org/2000/xmlns/>;
30
31 sub A_EL () { 0b1 }
32 sub ADDRESS_EL () { 0b10 }
33 sub BODY_EL () { 0b100 }
34 sub BUTTON_EL () { 0b1000 }
35 sub CAPTION_EL () { 0b10000 }
36 sub DD_EL () { 0b100000 }
37 sub DIV_EL () { 0b1000000 }
38 sub DT_EL () { 0b10000000 }
39 sub FORM_EL () { 0b100000000 }
40 sub FORMATTING_EL () { 0b1000000000 }
41 sub FRAMESET_EL () { 0b10000000000 }
42 sub HEADING_EL () { 0b100000000000 }
43 sub HTML_EL () { 0b1000000000000 }
44 sub LI_EL () { 0b10000000000000 }
45 sub NOBR_EL () { 0b100000000000000 }
46 sub OPTION_EL () { 0b1000000000000000 }
47 sub OPTGROUP_EL () { 0b10000000000000000 }
48 sub P_EL () { 0b100000000000000000 }
49 sub SELECT_EL () { 0b1000000000000000000 }
50 sub TABLE_EL () { 0b10000000000000000000 }
51 sub TABLE_CELL_EL () { 0b100000000000000000000 }
52 sub TABLE_ROW_EL () { 0b1000000000000000000000 }
53 sub TABLE_ROW_GROUP_EL () { 0b10000000000000000000000 }
54 sub MISC_SCOPING_EL () { 0b100000000000000000000000 }
55 sub MISC_SPECIAL_EL () { 0b1000000000000000000000000 }
56 sub FOREIGN_EL () { 0b10000000000000000000000000 }
57 sub FOREIGN_FLOW_CONTENT_EL () { 0b100000000000000000000000000 }
58 sub MML_AXML_EL () { 0b1000000000000000000000000000 }
59 sub RUBY_EL () { 0b10000000000000000000000000000 }
60 sub RUBY_COMPONENT_EL () { 0b100000000000000000000000000000 }
61
62 sub TABLE_ROWS_EL () {
63 TABLE_EL |
64 TABLE_ROW_EL |
65 TABLE_ROW_GROUP_EL
66 }
67
68 ## NOTE: Used in "generate implied end tags" algorithm.
69 ## NOTE: There is a code where a modified version of
70 ## END_TAG_OPTIONAL_EL is used in "generate implied end tags"
71 ## implementation (search for the algorithm name).
72 sub END_TAG_OPTIONAL_EL () {
73 DD_EL |
74 DT_EL |
75 LI_EL |
76 OPTION_EL |
77 OPTGROUP_EL |
78 P_EL |
79 RUBY_COMPONENT_EL
80 }
81
82 ## NOTE: Used in </body> and EOF algorithms.
83 sub ALL_END_TAG_OPTIONAL_EL () {
84 DD_EL |
85 DT_EL |
86 LI_EL |
87 P_EL |
88
89 ## ISSUE: option, optgroup, rt, rp?
90
91 BODY_EL |
92 HTML_EL |
93 TABLE_CELL_EL |
94 TABLE_ROW_EL |
95 TABLE_ROW_GROUP_EL
96 }
97
98 sub SCOPING_EL () {
99 BUTTON_EL |
100 CAPTION_EL |
101 HTML_EL |
102 TABLE_EL |
103 TABLE_CELL_EL |
104 MISC_SCOPING_EL
105 }
106
107 sub TABLE_SCOPING_EL () {
108 HTML_EL |
109 TABLE_EL
110 }
111
112 sub TABLE_ROWS_SCOPING_EL () {
113 HTML_EL |
114 TABLE_ROW_GROUP_EL
115 }
116
117 sub TABLE_ROW_SCOPING_EL () {
118 HTML_EL |
119 TABLE_ROW_EL
120 }
121
122 sub SPECIAL_EL () {
123 ADDRESS_EL |
124 BODY_EL |
125 DIV_EL |
126
127 DD_EL |
128 DT_EL |
129 LI_EL |
130 P_EL |
131
132 FORM_EL |
133 FRAMESET_EL |
134 HEADING_EL |
135 SELECT_EL |
136 TABLE_ROW_EL |
137 TABLE_ROW_GROUP_EL |
138 MISC_SPECIAL_EL
139 }
140
141 my $el_category = {
142 a => A_EL | FORMATTING_EL,
143 address => ADDRESS_EL,
144 applet => MISC_SCOPING_EL,
145 area => MISC_SPECIAL_EL,
146 article => MISC_SPECIAL_EL,
147 aside => MISC_SPECIAL_EL,
148 b => FORMATTING_EL,
149 base => MISC_SPECIAL_EL,
150 basefont => MISC_SPECIAL_EL,
151 bgsound => MISC_SPECIAL_EL,
152 big => FORMATTING_EL,
153 blockquote => MISC_SPECIAL_EL,
154 body => BODY_EL,
155 br => MISC_SPECIAL_EL,
156 button => BUTTON_EL,
157 caption => CAPTION_EL,
158 center => MISC_SPECIAL_EL,
159 col => MISC_SPECIAL_EL,
160 colgroup => MISC_SPECIAL_EL,
161 command => MISC_SPECIAL_EL,
162 datagrid => MISC_SPECIAL_EL,
163 dd => DD_EL,
164 details => MISC_SPECIAL_EL,
165 dialog => MISC_SPECIAL_EL,
166 dir => MISC_SPECIAL_EL,
167 div => DIV_EL,
168 dl => MISC_SPECIAL_EL,
169 dt => DT_EL,
170 em => FORMATTING_EL,
171 embed => MISC_SPECIAL_EL,
172 eventsource => MISC_SPECIAL_EL,
173 fieldset => MISC_SPECIAL_EL,
174 figure => MISC_SPECIAL_EL,
175 font => FORMATTING_EL,
176 footer => MISC_SPECIAL_EL,
177 form => FORM_EL,
178 frame => MISC_SPECIAL_EL,
179 frameset => FRAMESET_EL,
180 h1 => HEADING_EL,
181 h2 => HEADING_EL,
182 h3 => HEADING_EL,
183 h4 => HEADING_EL,
184 h5 => HEADING_EL,
185 h6 => HEADING_EL,
186 head => MISC_SPECIAL_EL,
187 header => MISC_SPECIAL_EL,
188 hr => MISC_SPECIAL_EL,
189 html => HTML_EL,
190 i => FORMATTING_EL,
191 iframe => MISC_SPECIAL_EL,
192 img => MISC_SPECIAL_EL,
193 #image => MISC_SPECIAL_EL, ## NOTE: Commented out in the spec.
194 input => MISC_SPECIAL_EL,
195 isindex => MISC_SPECIAL_EL,
196 li => LI_EL,
197 link => MISC_SPECIAL_EL,
198 listing => MISC_SPECIAL_EL,
199 marquee => MISC_SCOPING_EL,
200 menu => MISC_SPECIAL_EL,
201 meta => MISC_SPECIAL_EL,
202 nav => MISC_SPECIAL_EL,
203 nobr => NOBR_EL | FORMATTING_EL,
204 noembed => MISC_SPECIAL_EL,
205 noframes => MISC_SPECIAL_EL,
206 noscript => MISC_SPECIAL_EL,
207 object => MISC_SCOPING_EL,
208 ol => MISC_SPECIAL_EL,
209 optgroup => OPTGROUP_EL,
210 option => OPTION_EL,
211 p => P_EL,
212 param => MISC_SPECIAL_EL,
213 plaintext => MISC_SPECIAL_EL,
214 pre => MISC_SPECIAL_EL,
215 rp => RUBY_COMPONENT_EL,
216 rt => RUBY_COMPONENT_EL,
217 ruby => RUBY_EL,
218 s => FORMATTING_EL,
219 script => MISC_SPECIAL_EL,
220 select => SELECT_EL,
221 section => MISC_SPECIAL_EL,
222 small => FORMATTING_EL,
223 spacer => MISC_SPECIAL_EL,
224 strike => FORMATTING_EL,
225 strong => FORMATTING_EL,
226 style => MISC_SPECIAL_EL,
227 table => TABLE_EL,
228 tbody => TABLE_ROW_GROUP_EL,
229 td => TABLE_CELL_EL,
230 textarea => MISC_SPECIAL_EL,
231 tfoot => TABLE_ROW_GROUP_EL,
232 th => TABLE_CELL_EL,
233 thead => TABLE_ROW_GROUP_EL,
234 title => MISC_SPECIAL_EL,
235 tr => TABLE_ROW_EL,
236 tt => FORMATTING_EL,
237 u => FORMATTING_EL,
238 ul => MISC_SPECIAL_EL,
239 wbr => MISC_SPECIAL_EL,
240 };
241
242 my $el_category_f = {
243 $MML_NS => {
244 'annotation-xml' => MML_AXML_EL,
245 mi => FOREIGN_FLOW_CONTENT_EL,
246 mo => FOREIGN_FLOW_CONTENT_EL,
247 mn => FOREIGN_FLOW_CONTENT_EL,
248 ms => FOREIGN_FLOW_CONTENT_EL,
249 mtext => FOREIGN_FLOW_CONTENT_EL,
250 },
251 $SVG_NS => {
252 foreignObject => FOREIGN_FLOW_CONTENT_EL | MISC_SCOPING_EL,
253 desc => FOREIGN_FLOW_CONTENT_EL,
254 title => FOREIGN_FLOW_CONTENT_EL,
255 },
256 ## NOTE: In addition, FOREIGN_EL is set to non-HTML elements.
257 };
258
259 my $svg_attr_name = {
260 attributename => 'attributeName',
261 attributetype => 'attributeType',
262 basefrequency => 'baseFrequency',
263 baseprofile => 'baseProfile',
264 calcmode => 'calcMode',
265 clippathunits => 'clipPathUnits',
266 contentscripttype => 'contentScriptType',
267 contentstyletype => 'contentStyleType',
268 diffuseconstant => 'diffuseConstant',
269 edgemode => 'edgeMode',
270 externalresourcesrequired => 'externalResourcesRequired',
271 filterres => 'filterRes',
272 filterunits => 'filterUnits',
273 glyphref => 'glyphRef',
274 gradienttransform => 'gradientTransform',
275 gradientunits => 'gradientUnits',
276 kernelmatrix => 'kernelMatrix',
277 kernelunitlength => 'kernelUnitLength',
278 keypoints => 'keyPoints',
279 keysplines => 'keySplines',
280 keytimes => 'keyTimes',
281 lengthadjust => 'lengthAdjust',
282 limitingconeangle => 'limitingConeAngle',
283 markerheight => 'markerHeight',
284 markerunits => 'markerUnits',
285 markerwidth => 'markerWidth',
286 maskcontentunits => 'maskContentUnits',
287 maskunits => 'maskUnits',
288 numoctaves => 'numOctaves',
289 pathlength => 'pathLength',
290 patterncontentunits => 'patternContentUnits',
291 patterntransform => 'patternTransform',
292 patternunits => 'patternUnits',
293 pointsatx => 'pointsAtX',
294 pointsaty => 'pointsAtY',
295 pointsatz => 'pointsAtZ',
296 preservealpha => 'preserveAlpha',
297 preserveaspectratio => 'preserveAspectRatio',
298 primitiveunits => 'primitiveUnits',
299 refx => 'refX',
300 refy => 'refY',
301 repeatcount => 'repeatCount',
302 repeatdur => 'repeatDur',
303 requiredextensions => 'requiredExtensions',
304 requiredfeatures => 'requiredFeatures',
305 specularconstant => 'specularConstant',
306 specularexponent => 'specularExponent',
307 spreadmethod => 'spreadMethod',
308 startoffset => 'startOffset',
309 stddeviation => 'stdDeviation',
310 stitchtiles => 'stitchTiles',
311 surfacescale => 'surfaceScale',
312 systemlanguage => 'systemLanguage',
313 tablevalues => 'tableValues',
314 targetx => 'targetX',
315 targety => 'targetY',
316 textlength => 'textLength',
317 viewbox => 'viewBox',
318 viewtarget => 'viewTarget',
319 xchannelselector => 'xChannelSelector',
320 ychannelselector => 'yChannelSelector',
321 zoomandpan => 'zoomAndPan',
322 };
323
324 my $foreign_attr_xname = {
325 'xlink:actuate' => [$XLINK_NS, ['xlink', 'actuate']],
326 'xlink:arcrole' => [$XLINK_NS, ['xlink', 'arcrole']],
327 'xlink:href' => [$XLINK_NS, ['xlink', 'href']],
328 'xlink:role' => [$XLINK_NS, ['xlink', 'role']],
329 'xlink:show' => [$XLINK_NS, ['xlink', 'show']],
330 'xlink:title' => [$XLINK_NS, ['xlink', 'title']],
331 'xlink:type' => [$XLINK_NS, ['xlink', 'type']],
332 'xml:base' => [$XML_NS, ['xml', 'base']],
333 'xml:lang' => [$XML_NS, ['xml', 'lang']],
334 'xml:space' => [$XML_NS, ['xml', 'space']],
335 'xmlns' => [$XMLNS_NS, [undef, 'xmlns']],
336 'xmlns:xlink' => [$XMLNS_NS, ['xmlns', 'xlink']],
337 };
338
339 ## ISSUE: xmlns:xlink="non-xlink-ns" is not an error.
340
341 my $charref_map = {
342 0x0D => 0x000A,
343 0x80 => 0x20AC,
344 0x81 => 0xFFFD,
345 0x82 => 0x201A,
346 0x83 => 0x0192,
347 0x84 => 0x201E,
348 0x85 => 0x2026,
349 0x86 => 0x2020,
350 0x87 => 0x2021,
351 0x88 => 0x02C6,
352 0x89 => 0x2030,
353 0x8A => 0x0160,
354 0x8B => 0x2039,
355 0x8C => 0x0152,
356 0x8D => 0xFFFD,
357 0x8E => 0x017D,
358 0x8F => 0xFFFD,
359 0x90 => 0xFFFD,
360 0x91 => 0x2018,
361 0x92 => 0x2019,
362 0x93 => 0x201C,
363 0x94 => 0x201D,
364 0x95 => 0x2022,
365 0x96 => 0x2013,
366 0x97 => 0x2014,
367 0x98 => 0x02DC,
368 0x99 => 0x2122,
369 0x9A => 0x0161,
370 0x9B => 0x203A,
371 0x9C => 0x0153,
372 0x9D => 0xFFFD,
373 0x9E => 0x017E,
374 0x9F => 0x0178,
375 }; # $charref_map
376 $charref_map->{$_} = 0xFFFD
377 for 0x0000..0x0008, 0x000B, 0x000E..0x001F, 0x007F,
378 0xD800..0xDFFF, 0xFDD0..0xFDDF, ## ISSUE: 0xFDEF
379 0xFFFE, 0xFFFF, 0x1FFFE, 0x1FFFF, 0x2FFFE, 0x2FFFF, 0x3FFFE, 0x3FFFF,
380 0x4FFFE, 0x4FFFF, 0x5FFFE, 0x5FFFF, 0x6FFFE, 0x6FFFF, 0x7FFFE,
381 0x7FFFF, 0x8FFFE, 0x8FFFF, 0x9FFFE, 0x9FFFF, 0xAFFFE, 0xAFFFF,
382 0xBFFFE, 0xBFFFF, 0xCFFFE, 0xCFFFF, 0xDFFFE, 0xDFFFF, 0xEFFFE,
383 0xEFFFF, 0xFFFFE, 0xFFFFF, 0x10FFFE, 0x10FFFF;
384
385 ## TODO: Invoke the reset algorithm when a resettable element is
386 ## created (cf. HTML5 revision 2259).
387
388 sub parse_byte_string ($$$$;$) {
389 my $self = shift;
390 my $charset_name = shift;
391 open my $input, '<', ref $_[0] ? $_[0] : \($_[0]);
392 return $self->parse_byte_stream ($charset_name, $input, @_[1..$#_]);
393 } # parse_byte_string
394
395 sub parse_byte_stream ($$$$;$$) {
396 # my ($self, $charset_name, $byte_stream, $doc, $onerror, $get_wrapper) = @_;
397 my $self = ref $_[0] ? shift : shift->new;
398 my $charset_name = shift;
399 my $byte_stream = $_[0];
400
401 my $onerror = $_[2] || sub {
402 my (%opt) = @_;
403 warn "Parse error ($opt{type})\n";
404 };
405 $self->{parse_error} = $onerror; # updated later by parse_char_string
406
407 my $get_wrapper = $_[3] || sub ($) {
408 return $_[0]; # $_[0] = byte stream handle, returned = arg to char handle
409 };
410
411 ## HTML5 encoding sniffing algorithm
412 require Message::Charset::Info;
413 my $charset;
414 my $buffer;
415 my ($char_stream, $e_status);
416
417 SNIFFING: {
418 ## NOTE: By setting |allow_fallback| option true when the
419 ## |get_decode_handle| method is invoked, we ignore what the HTML5
420 ## spec requires, i.e. unsupported encoding should be ignored.
421 ## TODO: We should not do this unless the parser is invoked
422 ## in the conformance checking mode, in which this behavior
423 ## would be useful.
424
425 ## Step 1
426 if (defined $charset_name) {
427 $charset = Message::Charset::Info->get_by_html_name ($charset_name);
428 ## TODO: Is this ok? Transfer protocol's parameter should be
429 ## interpreted in its semantics?
430
431 ($char_stream, $e_status) = $charset->get_decode_handle
432 ($byte_stream, allow_error_reporting => 1,
433 allow_fallback => 1);
434 if ($char_stream) {
435 $self->{confident} = 1;
436 last SNIFFING;
437 } else {
438 !!!parse-error (type => 'charset:not supported',
439 layer => 'encode',
440 line => 1, column => 1,
441 value => $charset_name,
442 level => $self->{level}->{uncertain});
443 }
444 }
445
446 ## Step 2
447 my $byte_buffer = '';
448 for (1..1024) {
449 my $char = $byte_stream->getc;
450 last unless defined $char;
451 $byte_buffer .= $char;
452 } ## TODO: timeout
453
454 ## Step 3
455 if ($byte_buffer =~ /^\xFE\xFF/) {
456 $charset = Message::Charset::Info->get_by_html_name ('utf-16be');
457 ($char_stream, $e_status) = $charset->get_decode_handle
458 ($byte_stream, allow_error_reporting => 1,
459 allow_fallback => 1, byte_buffer => \$byte_buffer);
460 $self->{confident} = 1;
461 last SNIFFING;
462 } elsif ($byte_buffer =~ /^\xFF\xFE/) {
463 $charset = Message::Charset::Info->get_by_html_name ('utf-16le');
464 ($char_stream, $e_status) = $charset->get_decode_handle
465 ($byte_stream, allow_error_reporting => 1,
466 allow_fallback => 1, byte_buffer => \$byte_buffer);
467 $self->{confident} = 1;
468 last SNIFFING;
469 } elsif ($byte_buffer =~ /^\xEF\xBB\xBF/) {
470 $charset = Message::Charset::Info->get_by_html_name ('utf-8');
471 ($char_stream, $e_status) = $charset->get_decode_handle
472 ($byte_stream, allow_error_reporting => 1,
473 allow_fallback => 1, byte_buffer => \$byte_buffer);
474 $self->{confident} = 1;
475 last SNIFFING;
476 }
477
478 ## Step 4
479 ## TODO: <meta charset>
480
481 ## Step 5
482 ## TODO: from history
483
484 ## Step 6
485 require Whatpm::Charset::UniversalCharDet;
486 $charset_name = Whatpm::Charset::UniversalCharDet->detect_byte_string
487 ($byte_buffer);
488 if (defined $charset_name) {
489 $charset = Message::Charset::Info->get_by_html_name ($charset_name);
490
491 require Whatpm::Charset::DecodeHandle;
492 $buffer = Whatpm::Charset::DecodeHandle::ByteBuffer->new
493 ($byte_stream);
494 ($char_stream, $e_status) = $charset->get_decode_handle
495 ($buffer, allow_error_reporting => 1,
496 allow_fallback => 1, byte_buffer => \$byte_buffer);
497 if ($char_stream) {
498 $buffer->{buffer} = $byte_buffer;
499 !!!parse-error (type => 'sniffing:chardet',
500 text => $charset_name,
501 level => $self->{level}->{info},
502 layer => 'encode',
503 line => 1, column => 1);
504 $self->{confident} = 0;
505 last SNIFFING;
506 }
507 }
508
509 ## Step 7: default
510 ## TODO: Make this configurable.
511 $charset = Message::Charset::Info->get_by_html_name ('windows-1252');
512 ## NOTE: We choose |windows-1252| here, since |utf-8| should be
513 ## detectable in the step 6.
514 require Whatpm::Charset::DecodeHandle;
515 $buffer = Whatpm::Charset::DecodeHandle::ByteBuffer->new
516 ($byte_stream);
517 ($char_stream, $e_status)
518 = $charset->get_decode_handle ($buffer,
519 allow_error_reporting => 1,
520 allow_fallback => 1,
521 byte_buffer => \$byte_buffer);
522 $buffer->{buffer} = $byte_buffer;
523 !!!parse-error (type => 'sniffing:default',
524 text => 'windows-1252',
525 level => $self->{level}->{info},
526 line => 1, column => 1,
527 layer => 'encode');
528 $self->{confident} = 0;
529 } # SNIFFING
530
531 if ($e_status & Message::Charset::Info::FALLBACK_ENCODING_IMPL ()) {
532 $self->{input_encoding} = $charset->get_iana_name; ## TODO: Should we set actual charset decoder's encoding name?
533 !!!parse-error (type => 'chardecode:fallback',
534 #text => $self->{input_encoding},
535 level => $self->{level}->{uncertain},
536 line => 1, column => 1,
537 layer => 'encode');
538 } elsif (not ($e_status &
539 Message::Charset::Info::ERROR_REPORTING_ENCODING_IMPL ())) {
540 $self->{input_encoding} = $charset->get_iana_name;
541 !!!parse-error (type => 'chardecode:no error',
542 text => $self->{input_encoding},
543 level => $self->{level}->{uncertain},
544 line => 1, column => 1,
545 layer => 'encode');
546 } else {
547 $self->{input_encoding} = $charset->get_iana_name;
548 }
549
550 $self->{change_encoding} = sub {
551 my $self = shift;
552 $charset_name = shift;
553 my $token = shift;
554
555 $charset = Message::Charset::Info->get_by_html_name ($charset_name);
556 ($char_stream, $e_status) = $charset->get_decode_handle
557 ($byte_stream, allow_error_reporting => 1, allow_fallback => 1,
558 byte_buffer => \ $buffer->{buffer});
559
560 if ($char_stream) { # if supported
561 ## "Change the encoding" algorithm:
562
563 ## Step 1
564 if ($charset->{category} &
565 Message::Charset::Info::CHARSET_CATEGORY_UTF16 ()) {
566 $charset = Message::Charset::Info->get_by_html_name ('utf-8');
567 ($char_stream, $e_status) = $charset->get_decode_handle
568 ($byte_stream,
569 byte_buffer => \ $buffer->{buffer});
570 }
571 $charset_name = $charset->get_iana_name;
572
573 ## Step 2
574 if (defined $self->{input_encoding} and
575 $self->{input_encoding} eq $charset_name) {
576 !!!parse-error (type => 'charset label:matching',
577 text => $charset_name,
578 level => $self->{level}->{info});
579 $self->{confident} = 1;
580 return;
581 }
582
583 !!!parse-error (type => 'charset label detected',
584 text => $self->{input_encoding},
585 value => $charset_name,
586 level => $self->{level}->{warn},
587 token => $token);
588
589 ## Step 3
590 # if (can) {
591 ## change the encoding on the fly.
592 #$self->{confident} = 1;
593 #return;
594 # }
595
596 ## Step 4
597 throw Whatpm::HTML::RestartParser ();
598 }
599 }; # $self->{change_encoding}
600
601 my $char_onerror = sub {
602 my (undef, $type, %opt) = @_;
603 !!!parse-error (layer => 'encode',
604 line => $self->{line}, column => $self->{column} + 1,
605 %opt, type => $type);
606 if ($opt{octets}) {
607 ${$opt{octets}} = "\x{FFFD}"; # relacement character
608 }
609 };
610
611 my $wrapped_char_stream = $get_wrapper->($char_stream);
612 $wrapped_char_stream->onerror ($char_onerror);
613
614 my @args = ($_[1], $_[2]); # $doc, $onerror - $get_wrapper = undef;
615 my $return;
616 try {
617 $return = $self->parse_char_stream ($wrapped_char_stream, @args);
618 } catch Whatpm::HTML::RestartParser with {
619 ## NOTE: Invoked after {change_encoding}.
620
621 if ($e_status & Message::Charset::Info::FALLBACK_ENCODING_IMPL ()) {
622 $self->{input_encoding} = $charset->get_iana_name; ## TODO: Should we set actual charset decoder's encoding name?
623 !!!parse-error (type => 'chardecode:fallback',
624 level => $self->{level}->{uncertain},
625 #text => $self->{input_encoding},
626 line => 1, column => 1,
627 layer => 'encode');
628 } elsif (not ($e_status &
629 Message::Charset::Info::ERROR_REPORTING_ENCODING_IMPL ())) {
630 $self->{input_encoding} = $charset->get_iana_name;
631 !!!parse-error (type => 'chardecode:no error',
632 text => $self->{input_encoding},
633 level => $self->{level}->{uncertain},
634 line => 1, column => 1,
635 layer => 'encode');
636 } else {
637 $self->{input_encoding} = $charset->get_iana_name;
638 }
639 $self->{confident} = 1;
640
641 $wrapped_char_stream = $get_wrapper->($char_stream);
642 $wrapped_char_stream->onerror ($char_onerror);
643
644 $return = $self->parse_char_stream ($wrapped_char_stream, @args);
645 };
646 return $return;
647 } # parse_byte_stream
648
649 ## NOTE: HTML5 spec says that the encoding layer MUST NOT strip BOM
650 ## and the HTML layer MUST ignore it. However, we does strip BOM in
651 ## the encoding layer and the HTML layer does not ignore any U+FEFF,
652 ## because the core part of our HTML parser expects a string of character,
653 ## not a string of bytes or code units or anything which might contain a BOM.
654 ## Therefore, any parser interface that accepts a string of bytes,
655 ## such as |parse_byte_string| in this module, must ensure that it does
656 ## strip the BOM and never strip any ZWNBSP.
657
658 sub parse_char_string ($$$;$$) {
659 #my ($self, $s, $doc, $onerror, $get_wrapper) = @_;
660 my $self = shift;
661 my $s = ref $_[0] ? $_[0] : \($_[0]);
662 require Whatpm::Charset::DecodeHandle;
663 my $input = Whatpm::Charset::DecodeHandle::CharString->new ($s);
664 return $self->parse_char_stream ($input, @_[1..$#_]);
665 } # parse_char_string
666 *parse_string = \&parse_char_string; ## NOTE: Alias for backward compatibility.
667
668 sub parse_char_stream ($$$;$$) {
669 my $self = ref $_[0] ? shift : shift->new;
670 my $input = $_[0];
671 $self->{document} = $_[1];
672 @{$self->{document}->child_nodes} = ();
673
674 ## NOTE: |set_inner_html| copies most of this method's code
675
676 $self->{confident} = 1 unless exists $self->{confident};
677 $self->{document}->input_encoding ($self->{input_encoding})
678 if defined $self->{input_encoding};
679 ## TODO: |{input_encoding}| is needless?
680
681 $self->{line_prev} = $self->{line} = 1;
682 $self->{column_prev} = -1;
683 $self->{column} = 0;
684 $self->{set_nc} = sub {
685 my $self = shift;
686
687 my $char = '';
688 if (defined $self->{next_nc}) {
689 $char = $self->{next_nc};
690 delete $self->{next_nc};
691 $self->{nc} = ord $char;
692 } else {
693 $self->{char_buffer} = '';
694 $self->{char_buffer_pos} = 0;
695
696 my $count = $input->manakai_read_until
697 ($self->{char_buffer}, qr/[^\x00\x0A\x0D]/, $self->{char_buffer_pos});
698 if ($count) {
699 $self->{line_prev} = $self->{line};
700 $self->{column_prev} = $self->{column};
701 $self->{column}++;
702 $self->{nc}
703 = ord substr ($self->{char_buffer}, $self->{char_buffer_pos}++, 1);
704 return;
705 }
706
707 if ($input->read ($char, 1)) {
708 $self->{nc} = ord $char;
709 } else {
710 $self->{nc} = -1;
711 return;
712 }
713 }
714
715 ($self->{line_prev}, $self->{column_prev})
716 = ($self->{line}, $self->{column});
717 $self->{column}++;
718
719 if ($self->{nc} == 0x000A) { # LF
720 !!!cp ('j1');
721 $self->{line}++;
722 $self->{column} = 0;
723 } elsif ($self->{nc} == 0x000D) { # CR
724 !!!cp ('j2');
725 ## TODO: support for abort/streaming
726 my $next = '';
727 if ($input->read ($next, 1) and $next ne "\x0A") {
728 $self->{next_nc} = $next;
729 }
730 $self->{nc} = 0x000A; # LF # MUST
731 $self->{line}++;
732 $self->{column} = 0;
733 } elsif ($self->{nc} == 0x0000) { # NULL
734 !!!cp ('j4');
735 !!!parse-error (type => 'NULL');
736 $self->{nc} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
737 }
738 };
739
740 $self->{read_until} = sub {
741 #my ($scalar, $specials_range, $offset) = @_;
742 return 0 if defined $self->{next_nc};
743
744 my $pattern = qr/[^$_[1]\x00\x0A\x0D]/;
745 my $offset = $_[2] || 0;
746
747 if ($self->{char_buffer_pos} < length $self->{char_buffer}) {
748 pos ($self->{char_buffer}) = $self->{char_buffer_pos};
749 if ($self->{char_buffer} =~ /\G(?>$pattern)+/) {
750 substr ($_[0], $offset)
751 = substr ($self->{char_buffer}, $-[0], $+[0] - $-[0]);
752 my $count = $+[0] - $-[0];
753 if ($count) {
754 $self->{column} += $count;
755 $self->{char_buffer_pos} += $count;
756 $self->{line_prev} = $self->{line};
757 $self->{column_prev} = $self->{column} - 1;
758 $self->{nc} = -1;
759 }
760 return $count;
761 } else {
762 return 0;
763 }
764 } else {
765 my $count = $input->manakai_read_until ($_[0], $pattern, $_[2]);
766 if ($count) {
767 $self->{column} += $count;
768 $self->{line_prev} = $self->{line};
769 $self->{column_prev} = $self->{column} - 1;
770 $self->{nc} = -1;
771 }
772 return $count;
773 }
774 }; # $self->{read_until}
775
776 my $onerror = $_[2] || sub {
777 my (%opt) = @_;
778 my $line = $opt{token} ? $opt{token}->{line} : $opt{line};
779 my $column = $opt{token} ? $opt{token}->{column} : $opt{column};
780 warn "Parse error ($opt{type}) at line $line column $column\n";
781 };
782 $self->{parse_error} = sub {
783 $onerror->(line => $self->{line}, column => $self->{column}, @_);
784 };
785
786 my $char_onerror = sub {
787 my (undef, $type, %opt) = @_;
788 !!!parse-error (layer => 'encode',
789 line => $self->{line}, column => $self->{column} + 1,
790 %opt, type => $type);
791 }; # $char_onerror
792
793 if ($_[3]) {
794 $input = $_[3]->($input);
795 $input->onerror ($char_onerror);
796 } else {
797 $input->onerror ($char_onerror) unless defined $input->onerror;
798 }
799
800 $self->_initialize_tokenizer;
801 $self->_initialize_tree_constructor;
802 $self->_construct_tree;
803 $self->_terminate_tree_constructor;
804
805 delete $self->{parse_error}; # remove loop
806
807 return $self->{document};
808 } # parse_char_stream
809
810 sub new ($) {
811 my $class = shift;
812 my $self = bless {
813 level => {must => 'm',
814 should => 's',
815 warn => 'w',
816 info => 'i',
817 uncertain => 'u'},
818 }, $class;
819 $self->{set_nc} = sub {
820 $self->{nc} = -1;
821 };
822 $self->{parse_error} = sub {
823 #
824 };
825 $self->{change_encoding} = sub {
826 # if ($_[0] is a supported encoding) {
827 # run "change the encoding" algorithm;
828 # throw Whatpm::HTML::RestartParser (charset => $new_encoding);
829 # }
830 };
831 $self->{application_cache_selection} = sub {
832 #
833 };
834 return $self;
835 } # new
836
837 sub CM_ENTITY () { 0b001 } # & markup in data
838 sub CM_LIMITED_MARKUP () { 0b010 } # < markup in data (limited)
839 sub CM_FULL_MARKUP () { 0b100 } # < markup in data (any)
840
841 sub PLAINTEXT_CONTENT_MODEL () { 0 }
842 sub CDATA_CONTENT_MODEL () { CM_LIMITED_MARKUP }
843 sub RCDATA_CONTENT_MODEL () { CM_ENTITY | CM_LIMITED_MARKUP }
844 sub PCDATA_CONTENT_MODEL () { CM_ENTITY | CM_FULL_MARKUP }
845
846 sub DATA_STATE () { 0 }
847 #sub ENTITY_DATA_STATE () { 1 }
848 sub TAG_OPEN_STATE () { 2 }
849 sub CLOSE_TAG_OPEN_STATE () { 3 }
850 sub TAG_NAME_STATE () { 4 }
851 sub BEFORE_ATTRIBUTE_NAME_STATE () { 5 }
852 sub ATTRIBUTE_NAME_STATE () { 6 }
853 sub AFTER_ATTRIBUTE_NAME_STATE () { 7 }
854 sub BEFORE_ATTRIBUTE_VALUE_STATE () { 8 }
855 sub ATTRIBUTE_VALUE_DOUBLE_QUOTED_STATE () { 9 }
856 sub ATTRIBUTE_VALUE_SINGLE_QUOTED_STATE () { 10 }
857 sub ATTRIBUTE_VALUE_UNQUOTED_STATE () { 11 }
858 #sub ENTITY_IN_ATTRIBUTE_VALUE_STATE () { 12 }
859 sub MARKUP_DECLARATION_OPEN_STATE () { 13 }
860 sub COMMENT_START_STATE () { 14 }
861 sub COMMENT_START_DASH_STATE () { 15 }
862 sub COMMENT_STATE () { 16 }
863 sub COMMENT_END_STATE () { 17 }
864 sub COMMENT_END_DASH_STATE () { 18 }
865 sub BOGUS_COMMENT_STATE () { 19 }
866 sub DOCTYPE_STATE () { 20 }
867 sub BEFORE_DOCTYPE_NAME_STATE () { 21 }
868 sub DOCTYPE_NAME_STATE () { 22 }
869 sub AFTER_DOCTYPE_NAME_STATE () { 23 }
870 sub BEFORE_DOCTYPE_PUBLIC_IDENTIFIER_STATE () { 24 }
871 sub DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED_STATE () { 25 }
872 sub DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED_STATE () { 26 }
873 sub AFTER_DOCTYPE_PUBLIC_IDENTIFIER_STATE () { 27 }
874 sub BEFORE_DOCTYPE_SYSTEM_IDENTIFIER_STATE () { 28 }
875 sub DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED_STATE () { 29 }
876 sub DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED_STATE () { 30 }
877 sub AFTER_DOCTYPE_SYSTEM_IDENTIFIER_STATE () { 31 }
878 sub BOGUS_DOCTYPE_STATE () { 32 }
879 sub AFTER_ATTRIBUTE_VALUE_QUOTED_STATE () { 33 }
880 sub SELF_CLOSING_START_TAG_STATE () { 34 }
881 sub CDATA_SECTION_STATE () { 35 }
882 sub MD_HYPHEN_STATE () { 36 } # "markup declaration open state" in the spec
883 sub MD_DOCTYPE_STATE () { 37 } # "markup declaration open state" in the spec
884 sub MD_CDATA_STATE () { 38 } # "markup declaration open state" in the spec
885 sub CDATA_RCDATA_CLOSE_TAG_STATE () { 39 } # "close tag open state" in the spec
886 sub CDATA_SECTION_MSE1_STATE () { 40 } # "CDATA section state" in the spec
887 sub CDATA_SECTION_MSE2_STATE () { 41 } # "CDATA section state" in the spec
888 sub PUBLIC_STATE () { 42 } # "after DOCTYPE name state" in the spec
889 sub SYSTEM_STATE () { 43 } # "after DOCTYPE name state" in the spec
890 ## NOTE: "Entity data state", "entity in attribute value state", and
891 ## "consume a character reference" algorithm are jointly implemented
892 ## using the following six states:
893 sub ENTITY_STATE () { 44 }
894 sub ENTITY_HASH_STATE () { 45 }
895 sub NCR_NUM_STATE () { 46 }
896 sub HEXREF_X_STATE () { 47 }
897 sub HEXREF_HEX_STATE () { 48 }
898 sub ENTITY_NAME_STATE () { 49 }
899 sub PCDATA_STATE () { 50 } # "data state" in the spec
900
901 sub DOCTYPE_TOKEN () { 1 }
902 sub COMMENT_TOKEN () { 2 }
903 sub START_TAG_TOKEN () { 3 }
904 sub END_TAG_TOKEN () { 4 }
905 sub END_OF_FILE_TOKEN () { 5 }
906 sub CHARACTER_TOKEN () { 6 }
907
908 sub AFTER_HTML_IMS () { 0b100 }
909 sub HEAD_IMS () { 0b1000 }
910 sub BODY_IMS () { 0b10000 }
911 sub BODY_TABLE_IMS () { 0b100000 }
912 sub TABLE_IMS () { 0b1000000 }
913 sub ROW_IMS () { 0b10000000 }
914 sub BODY_AFTER_IMS () { 0b100000000 }
915 sub FRAME_IMS () { 0b1000000000 }
916 sub SELECT_IMS () { 0b10000000000 }
917 sub IN_FOREIGN_CONTENT_IM () { 0b100000000000 }
918 ## NOTE: "in foreign content" insertion mode is special; it is combined
919 ## with the secondary insertion mode. In this parser, they are stored
920 ## together in the bit-or'ed form.
921
922 ## NOTE: "initial" and "before html" insertion modes have no constants.
923
924 ## NOTE: "after after body" insertion mode.
925 sub AFTER_HTML_BODY_IM () { AFTER_HTML_IMS | BODY_AFTER_IMS }
926
927 ## NOTE: "after after frameset" insertion mode.
928 sub AFTER_HTML_FRAMESET_IM () { AFTER_HTML_IMS | FRAME_IMS }
929
930 sub IN_HEAD_IM () { HEAD_IMS | 0b00 }
931 sub IN_HEAD_NOSCRIPT_IM () { HEAD_IMS | 0b01 }
932 sub AFTER_HEAD_IM () { HEAD_IMS | 0b10 }
933 sub BEFORE_HEAD_IM () { HEAD_IMS | 0b11 }
934 sub IN_BODY_IM () { BODY_IMS }
935 sub IN_CELL_IM () { BODY_IMS | BODY_TABLE_IMS | 0b01 }
936 sub IN_CAPTION_IM () { BODY_IMS | BODY_TABLE_IMS | 0b10 }
937 sub IN_ROW_IM () { TABLE_IMS | ROW_IMS | 0b01 }
938 sub IN_TABLE_BODY_IM () { TABLE_IMS | ROW_IMS | 0b10 }
939 sub IN_TABLE_IM () { TABLE_IMS }
940 sub AFTER_BODY_IM () { BODY_AFTER_IMS }
941 sub IN_FRAMESET_IM () { FRAME_IMS | 0b01 }
942 sub AFTER_FRAMESET_IM () { FRAME_IMS | 0b10 }
943 sub IN_SELECT_IM () { SELECT_IMS | 0b01 }
944 sub IN_SELECT_IN_TABLE_IM () { SELECT_IMS | 0b10 }
945 sub IN_COLUMN_GROUP_IM () { 0b10 }
946
947 ## Implementations MUST act as if state machine in the spec
948
949 sub _initialize_tokenizer ($) {
950 my $self = shift;
951 $self->{state} = DATA_STATE; # MUST
952 #$self->{s_kwd}; # state keyword - initialized when used
953 #$self->{entity__value}; # initialized when used
954 #$self->{entity__match}; # initialized when used
955 $self->{content_model} = PCDATA_CONTENT_MODEL; # be
956 undef $self->{ct}; # current token
957 undef $self->{ca}; # current attribute
958 undef $self->{last_stag_name}; # last emitted start tag name
959 #$self->{prev_state}; # initialized when used
960 delete $self->{self_closing};
961 $self->{char_buffer} = '';
962 $self->{char_buffer_pos} = 0;
963 $self->{nc} = -1; # next input character
964 #$self->{next_nc}
965 !!!next-input-character;
966 $self->{token} = [];
967 # $self->{escape}
968 } # _initialize_tokenizer
969
970 ## A token has:
971 ## ->{type} == DOCTYPE_TOKEN, START_TAG_TOKEN, END_TAG_TOKEN, COMMENT_TOKEN,
972 ## CHARACTER_TOKEN, or END_OF_FILE_TOKEN
973 ## ->{name} (DOCTYPE_TOKEN)
974 ## ->{tag_name} (START_TAG_TOKEN, END_TAG_TOKEN)
975 ## ->{pubid} (DOCTYPE_TOKEN)
976 ## ->{sysid} (DOCTYPE_TOKEN)
977 ## ->{quirks} == 1 or 0 (DOCTYPE_TOKEN): "force-quirks" flag
978 ## ->{attributes} isa HASH (START_TAG_TOKEN, END_TAG_TOKEN)
979 ## ->{name}
980 ## ->{value}
981 ## ->{has_reference} == 1 or 0
982 ## ->{data} (COMMENT_TOKEN, CHARACTER_TOKEN)
983 ## NOTE: The "self-closing flag" is hold as |$self->{self_closing}|.
984 ## |->{self_closing}| is used to save the value of |$self->{self_closing}|
985 ## while the token is pushed back to the stack.
986
987 ## Emitted token MUST immediately be handled by the tree construction state.
988
989 ## Before each step, UA MAY check to see if either one of the scripts in
990 ## "list of scripts that will execute as soon as possible" or the first
991 ## script in the "list of scripts that will execute asynchronously",
992 ## has completed loading. If one has, then it MUST be executed
993 ## and removed from the list.
994
995 ## TODO: Polytheistic slash SHOULD NOT be used. (Applied only to atheists.)
996 ## (This requirement was dropped from HTML5 spec, unfortunately.)
997
998 my $is_space = {
999 0x0009 => 1, # CHARACTER TABULATION (HT)
1000 0x000A => 1, # LINE FEED (LF)
1001 #0x000B => 0, # LINE TABULATION (VT)
1002 0x000C => 1, # FORM FEED (FF)
1003 #0x000D => 1, # CARRIAGE RETURN (CR)
1004 0x0020 => 1, # SPACE (SP)
1005 };
1006
1007 sub _get_next_token ($) {
1008 my $self = shift;
1009
1010 if ($self->{self_closing}) {
1011 !!!parse-error (type => 'nestc', token => $self->{ct});
1012 ## NOTE: The |self_closing| flag is only set by start tag token.
1013 ## In addition, when a start tag token is emitted, it is always set to
1014 ## |ct|.
1015 delete $self->{self_closing};
1016 }
1017
1018 if (@{$self->{token}}) {
1019 $self->{self_closing} = $self->{token}->[0]->{self_closing};
1020 return shift @{$self->{token}};
1021 }
1022
1023 A: {
1024 if ($self->{state} == PCDATA_STATE) {
1025 ## NOTE: Same as |DATA_STATE|, but only for |PCDATA| content model.
1026
1027 if ($self->{nc} == 0x0026) { # &
1028 !!!cp (0.1);
1029 ## NOTE: In the spec, the tokenizer is switched to the
1030 ## "entity data state". In this implementation, the tokenizer
1031 ## is switched to the |ENTITY_STATE|, which is an implementation
1032 ## of the "consume a character reference" algorithm.
1033 $self->{entity_add} = -1;
1034 $self->{prev_state} = DATA_STATE;
1035 $self->{state} = ENTITY_STATE;
1036 !!!next-input-character;
1037 redo A;
1038 } elsif ($self->{nc} == 0x003C) { # <
1039 !!!cp (0.2);
1040 $self->{state} = TAG_OPEN_STATE;
1041 !!!next-input-character;
1042 redo A;
1043 } elsif ($self->{nc} == -1) {
1044 !!!cp (0.3);
1045 !!!emit ({type => END_OF_FILE_TOKEN,
1046 line => $self->{line}, column => $self->{column}});
1047 last A; ## TODO: ok?
1048 } else {
1049 !!!cp (0.4);
1050 #
1051 }
1052
1053 # Anything else
1054 my $token = {type => CHARACTER_TOKEN,
1055 data => chr $self->{nc},
1056 line => $self->{line}, column => $self->{column},
1057 };
1058 $self->{read_until}->($token->{data}, q[<&], length $token->{data});
1059
1060 ## Stay in the state.
1061 !!!next-input-character;
1062 !!!emit ($token);
1063 redo A;
1064 } elsif ($self->{state} == DATA_STATE) {
1065 $self->{s_kwd} = '' unless defined $self->{s_kwd};
1066 if ($self->{nc} == 0x0026) { # &
1067 $self->{s_kwd} = '';
1068 if ($self->{content_model} & CM_ENTITY and # PCDATA | RCDATA
1069 not $self->{escape}) {
1070 !!!cp (1);
1071 ## NOTE: In the spec, the tokenizer is switched to the
1072 ## "entity data state". In this implementation, the tokenizer
1073 ## is switched to the |ENTITY_STATE|, which is an implementation
1074 ## of the "consume a character reference" algorithm.
1075 $self->{entity_add} = -1;
1076 $self->{prev_state} = DATA_STATE;
1077 $self->{state} = ENTITY_STATE;
1078 !!!next-input-character;
1079 redo A;
1080 } else {
1081 !!!cp (2);
1082 #
1083 }
1084 } elsif ($self->{nc} == 0x002D) { # -
1085 if ($self->{content_model} & CM_LIMITED_MARKUP) { # RCDATA | CDATA
1086 $self->{s_kwd} .= '-';
1087
1088 if ($self->{s_kwd} eq '<!--') {
1089 !!!cp (3);
1090 $self->{escape} = 1; # unless $self->{escape};
1091 $self->{s_kwd} = '--';
1092 #
1093 } elsif ($self->{s_kwd} eq '---') {
1094 !!!cp (4);
1095 $self->{s_kwd} = '--';
1096 #
1097 } else {
1098 !!!cp (5);
1099 #
1100 }
1101 }
1102
1103 #
1104 } elsif ($self->{nc} == 0x0021) { # !
1105 if (length $self->{s_kwd}) {
1106 !!!cp (5.1);
1107 $self->{s_kwd} .= '!';
1108 #
1109 } else {
1110 !!!cp (5.2);
1111 #$self->{s_kwd} = '';
1112 #
1113 }
1114 #
1115 } elsif ($self->{nc} == 0x003C) { # <
1116 if ($self->{content_model} & CM_FULL_MARKUP or # PCDATA
1117 (($self->{content_model} & CM_LIMITED_MARKUP) and # CDATA | RCDATA
1118 not $self->{escape})) {
1119 !!!cp (6);
1120 $self->{state} = TAG_OPEN_STATE;
1121 !!!next-input-character;
1122 redo A;
1123 } else {
1124 !!!cp (7);
1125 $self->{s_kwd} = '';
1126 #
1127 }
1128 } elsif ($self->{nc} == 0x003E) { # >
1129 if ($self->{escape} and
1130 ($self->{content_model} & CM_LIMITED_MARKUP)) { # RCDATA | CDATA
1131 if ($self->{s_kwd} eq '--') {
1132 !!!cp (8);
1133 delete $self->{escape};
1134 } else {
1135 !!!cp (9);
1136 }
1137 } else {
1138 !!!cp (10);
1139 }
1140
1141 $self->{s_kwd} = '';
1142 #
1143 } elsif ($self->{nc} == -1) {
1144 !!!cp (11);
1145 $self->{s_kwd} = '';
1146 !!!emit ({type => END_OF_FILE_TOKEN,
1147 line => $self->{line}, column => $self->{column}});
1148 last A; ## TODO: ok?
1149 } else {
1150 !!!cp (12);
1151 $self->{s_kwd} = '';
1152 #
1153 }
1154
1155 # Anything else
1156 my $token = {type => CHARACTER_TOKEN,
1157 data => chr $self->{nc},
1158 line => $self->{line}, column => $self->{column},
1159 };
1160 if ($self->{read_until}->($token->{data}, q[-!<>&],
1161 length $token->{data})) {
1162 $self->{s_kwd} = '';
1163 }
1164
1165 ## Stay in the data state.
1166 if ($self->{content_model} == PCDATA_CONTENT_MODEL) {
1167 !!!cp (13);
1168 $self->{state} = PCDATA_STATE;
1169 } else {
1170 !!!cp (14);
1171 ## Stay in the state.
1172 }
1173 !!!next-input-character;
1174 !!!emit ($token);
1175 redo A;
1176 } elsif ($self->{state} == TAG_OPEN_STATE) {
1177 if ($self->{content_model} & CM_LIMITED_MARKUP) { # RCDATA | CDATA
1178 if ($self->{nc} == 0x002F) { # /
1179 !!!cp (15);
1180 !!!next-input-character;
1181 $self->{state} = CLOSE_TAG_OPEN_STATE;
1182 redo A;
1183 } elsif ($self->{nc} == 0x0021) { # !
1184 !!!cp (15.1);
1185 $self->{s_kwd} = '<' unless $self->{escape};
1186 #
1187 } else {
1188 !!!cp (16);
1189 #
1190 }
1191
1192 ## reconsume
1193 $self->{state} = DATA_STATE;
1194 !!!emit ({type => CHARACTER_TOKEN, data => '<',
1195 line => $self->{line_prev},
1196 column => $self->{column_prev},
1197 });
1198 redo A;
1199 } elsif ($self->{content_model} & CM_FULL_MARKUP) { # PCDATA
1200 if ($self->{nc} == 0x0021) { # !
1201 !!!cp (17);
1202 $self->{state} = MARKUP_DECLARATION_OPEN_STATE;
1203 !!!next-input-character;
1204 redo A;
1205 } elsif ($self->{nc} == 0x002F) { # /
1206 !!!cp (18);
1207 $self->{state} = CLOSE_TAG_OPEN_STATE;
1208 !!!next-input-character;
1209 redo A;
1210 } elsif (0x0041 <= $self->{nc} and
1211 $self->{nc} <= 0x005A) { # A..Z
1212 !!!cp (19);
1213 $self->{ct}
1214 = {type => START_TAG_TOKEN,
1215 tag_name => chr ($self->{nc} + 0x0020),
1216 line => $self->{line_prev},
1217 column => $self->{column_prev}};
1218 $self->{state} = TAG_NAME_STATE;
1219 !!!next-input-character;
1220 redo A;
1221 } elsif (0x0061 <= $self->{nc} and
1222 $self->{nc} <= 0x007A) { # a..z
1223 !!!cp (20);
1224 $self->{ct} = {type => START_TAG_TOKEN,
1225 tag_name => chr ($self->{nc}),
1226 line => $self->{line_prev},
1227 column => $self->{column_prev}};
1228 $self->{state} = TAG_NAME_STATE;
1229 !!!next-input-character;
1230 redo A;
1231 } elsif ($self->{nc} == 0x003E) { # >
1232 !!!cp (21);
1233 !!!parse-error (type => 'empty start tag',
1234 line => $self->{line_prev},
1235 column => $self->{column_prev});
1236 $self->{state} = DATA_STATE;
1237 !!!next-input-character;
1238
1239 !!!emit ({type => CHARACTER_TOKEN, data => '<>',
1240 line => $self->{line_prev},
1241 column => $self->{column_prev},
1242 });
1243
1244 redo A;
1245 } elsif ($self->{nc} == 0x003F) { # ?
1246 !!!cp (22);
1247 !!!parse-error (type => 'pio',
1248 line => $self->{line_prev},
1249 column => $self->{column_prev});
1250 $self->{state} = BOGUS_COMMENT_STATE;
1251 $self->{ct} = {type => COMMENT_TOKEN, data => '',
1252 line => $self->{line_prev},
1253 column => $self->{column_prev},
1254 };
1255 ## $self->{nc} is intentionally left as is
1256 redo A;
1257 } else {
1258 !!!cp (23);
1259 !!!parse-error (type => 'bare stago',
1260 line => $self->{line_prev},
1261 column => $self->{column_prev});
1262 $self->{state} = DATA_STATE;
1263 ## reconsume
1264
1265 !!!emit ({type => CHARACTER_TOKEN, data => '<',
1266 line => $self->{line_prev},
1267 column => $self->{column_prev},
1268 });
1269
1270 redo A;
1271 }
1272 } else {
1273 die "$0: $self->{content_model} in tag open";
1274 }
1275 } elsif ($self->{state} == CLOSE_TAG_OPEN_STATE) {
1276 ## NOTE: The "close tag open state" in the spec is implemented as
1277 ## |CLOSE_TAG_OPEN_STATE| and |CDATA_RCDATA_CLOSE_TAG_STATE|.
1278
1279 my ($l, $c) = ($self->{line_prev}, $self->{column_prev} - 1); # "<"of"</"
1280 if ($self->{content_model} & CM_LIMITED_MARKUP) { # RCDATA | CDATA
1281 if (defined $self->{last_stag_name}) {
1282 $self->{state} = CDATA_RCDATA_CLOSE_TAG_STATE;
1283 $self->{s_kwd} = '';
1284 ## Reconsume.
1285 redo A;
1286 } else {
1287 ## No start tag token has ever been emitted
1288 ## NOTE: See <http://krijnhoetmer.nl/irc-logs/whatwg/20070626#l-564>.
1289 !!!cp (28);
1290 $self->{state} = DATA_STATE;
1291 ## Reconsume.
1292 !!!emit ({type => CHARACTER_TOKEN, data => '</',
1293 line => $l, column => $c,
1294 });
1295 redo A;
1296 }
1297 }
1298
1299 if (0x0041 <= $self->{nc} and
1300 $self->{nc} <= 0x005A) { # A..Z
1301 !!!cp (29);
1302 $self->{ct}
1303 = {type => END_TAG_TOKEN,
1304 tag_name => chr ($self->{nc} + 0x0020),
1305 line => $l, column => $c};
1306 $self->{state} = TAG_NAME_STATE;
1307 !!!next-input-character;
1308 redo A;
1309 } elsif (0x0061 <= $self->{nc} and
1310 $self->{nc} <= 0x007A) { # a..z
1311 !!!cp (30);
1312 $self->{ct} = {type => END_TAG_TOKEN,
1313 tag_name => chr ($self->{nc}),
1314 line => $l, column => $c};
1315 $self->{state} = TAG_NAME_STATE;
1316 !!!next-input-character;
1317 redo A;
1318 } elsif ($self->{nc} == 0x003E) { # >
1319 !!!cp (31);
1320 !!!parse-error (type => 'empty end tag',
1321 line => $self->{line_prev}, ## "<" in "</>"
1322 column => $self->{column_prev} - 1);
1323 $self->{state} = DATA_STATE;
1324 !!!next-input-character;
1325 redo A;
1326 } elsif ($self->{nc} == -1) {
1327 !!!cp (32);
1328 !!!parse-error (type => 'bare etago');
1329 $self->{state} = DATA_STATE;
1330 # reconsume
1331
1332 !!!emit ({type => CHARACTER_TOKEN, data => '</',
1333 line => $l, column => $c,
1334 });
1335
1336 redo A;
1337 } else {
1338 !!!cp (33);
1339 !!!parse-error (type => 'bogus end tag');
1340 $self->{state} = BOGUS_COMMENT_STATE;
1341 $self->{ct} = {type => COMMENT_TOKEN, data => '',
1342 line => $self->{line_prev}, # "<" of "</"
1343 column => $self->{column_prev} - 1,
1344 };
1345 ## NOTE: $self->{nc} is intentionally left as is.
1346 ## Although the "anything else" case of the spec not explicitly
1347 ## states that the next input character is to be reconsumed,
1348 ## it will be included to the |data| of the comment token
1349 ## generated from the bogus end tag, as defined in the
1350 ## "bogus comment state" entry.
1351 redo A;
1352 }
1353 } elsif ($self->{state} == CDATA_RCDATA_CLOSE_TAG_STATE) {
1354 my $ch = substr $self->{last_stag_name}, length $self->{s_kwd}, 1;
1355 if (length $ch) {
1356 my $CH = $ch;
1357 $ch =~ tr/a-z/A-Z/;
1358 my $nch = chr $self->{nc};
1359 if ($nch eq $ch or $nch eq $CH) {
1360 !!!cp (24);
1361 ## Stay in the state.
1362 $self->{s_kwd} .= $nch;
1363 !!!next-input-character;
1364 redo A;
1365 } else {
1366 !!!cp (25);
1367 $self->{state} = DATA_STATE;
1368 ## Reconsume.
1369 !!!emit ({type => CHARACTER_TOKEN,
1370 data => '</' . $self->{s_kwd},
1371 line => $self->{line_prev},
1372 column => $self->{column_prev} - 1 - length $self->{s_kwd},
1373 });
1374 redo A;
1375 }
1376 } else { # after "<{tag-name}"
1377 unless ($is_space->{$self->{nc}} or
1378 {
1379 0x003E => 1, # >
1380 0x002F => 1, # /
1381 -1 => 1, # EOF
1382 }->{$self->{nc}}) {
1383 !!!cp (26);
1384 ## Reconsume.
1385 $self->{state} = DATA_STATE;
1386 !!!emit ({type => CHARACTER_TOKEN,
1387 data => '</' . $self->{s_kwd},
1388 line => $self->{line_prev},
1389 column => $self->{column_prev} - 1 - length $self->{s_kwd},
1390 });
1391 redo A;
1392 } else {
1393 !!!cp (27);
1394 $self->{ct}
1395 = {type => END_TAG_TOKEN,
1396 tag_name => $self->{last_stag_name},
1397 line => $self->{line_prev},
1398 column => $self->{column_prev} - 1 - length $self->{s_kwd}};
1399 $self->{state} = TAG_NAME_STATE;
1400 ## Reconsume.
1401 redo A;
1402 }
1403 }
1404 } elsif ($self->{state} == TAG_NAME_STATE) {
1405 if ($is_space->{$self->{nc}}) {
1406 !!!cp (34);
1407 $self->{state} = BEFORE_ATTRIBUTE_NAME_STATE;
1408 !!!next-input-character;
1409 redo A;
1410 } elsif ($self->{nc} == 0x003E) { # >
1411 if ($self->{ct}->{type} == START_TAG_TOKEN) {
1412 !!!cp (35);
1413 $self->{last_stag_name} = $self->{ct}->{tag_name};
1414 } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
1415 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1416 #if ($self->{ct}->{attributes}) {
1417 # ## NOTE: This should never be reached.
1418 # !!! cp (36);
1419 # !!! parse-error (type => 'end tag attribute');
1420 #} else {
1421 !!!cp (37);
1422 #}
1423 } else {
1424 die "$0: $self->{ct}->{type}: Unknown token type";
1425 }
1426 $self->{state} = DATA_STATE;
1427 !!!next-input-character;
1428
1429 !!!emit ($self->{ct}); # start tag or end tag
1430
1431 redo A;
1432 } elsif (0x0041 <= $self->{nc} and
1433 $self->{nc} <= 0x005A) { # A..Z
1434 !!!cp (38);
1435 $self->{ct}->{tag_name} .= chr ($self->{nc} + 0x0020);
1436 # start tag or end tag
1437 ## Stay in this state
1438 !!!next-input-character;
1439 redo A;
1440 } elsif ($self->{nc} == -1) {
1441 !!!parse-error (type => 'unclosed tag');
1442 if ($self->{ct}->{type} == START_TAG_TOKEN) {
1443 !!!cp (39);
1444 $self->{last_stag_name} = $self->{ct}->{tag_name};
1445 } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
1446 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1447 #if ($self->{ct}->{attributes}) {
1448 # ## NOTE: This state should never be reached.
1449 # !!! cp (40);
1450 # !!! parse-error (type => 'end tag attribute');
1451 #} else {
1452 !!!cp (41);
1453 #}
1454 } else {
1455 die "$0: $self->{ct}->{type}: Unknown token type";
1456 }
1457 $self->{state} = DATA_STATE;
1458 # reconsume
1459
1460 !!!emit ($self->{ct}); # start tag or end tag
1461
1462 redo A;
1463 } elsif ($self->{nc} == 0x002F) { # /
1464 !!!cp (42);
1465 $self->{state} = SELF_CLOSING_START_TAG_STATE;
1466 !!!next-input-character;
1467 redo A;
1468 } else {
1469 !!!cp (44);
1470 $self->{ct}->{tag_name} .= chr $self->{nc};
1471 # start tag or end tag
1472 ## Stay in the state
1473 !!!next-input-character;
1474 redo A;
1475 }
1476 } elsif ($self->{state} == BEFORE_ATTRIBUTE_NAME_STATE) {
1477 if ($is_space->{$self->{nc}}) {
1478 !!!cp (45);
1479 ## Stay in the state
1480 !!!next-input-character;
1481 redo A;
1482 } elsif ($self->{nc} == 0x003E) { # >
1483 if ($self->{ct}->{type} == START_TAG_TOKEN) {
1484 !!!cp (46);
1485 $self->{last_stag_name} = $self->{ct}->{tag_name};
1486 } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
1487 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1488 if ($self->{ct}->{attributes}) {
1489 !!!cp (47);
1490 !!!parse-error (type => 'end tag attribute');
1491 } else {
1492 !!!cp (48);
1493 }
1494 } else {
1495 die "$0: $self->{ct}->{type}: Unknown token type";
1496 }
1497 $self->{state} = DATA_STATE;
1498 !!!next-input-character;
1499
1500 !!!emit ($self->{ct}); # start tag or end tag
1501
1502 redo A;
1503 } elsif (0x0041 <= $self->{nc} and
1504 $self->{nc} <= 0x005A) { # A..Z
1505 !!!cp (49);
1506 $self->{ca}
1507 = {name => chr ($self->{nc} + 0x0020),
1508 value => '',
1509 line => $self->{line}, column => $self->{column}};
1510 $self->{state} = ATTRIBUTE_NAME_STATE;
1511 !!!next-input-character;
1512 redo A;
1513 } elsif ($self->{nc} == 0x002F) { # /
1514 !!!cp (50);
1515 $self->{state} = SELF_CLOSING_START_TAG_STATE;
1516 !!!next-input-character;
1517 redo A;
1518 } elsif ($self->{nc} == -1) {
1519 !!!parse-error (type => 'unclosed tag');
1520 if ($self->{ct}->{type} == START_TAG_TOKEN) {
1521 !!!cp (52);
1522 $self->{last_stag_name} = $self->{ct}->{tag_name};
1523 } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
1524 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1525 if ($self->{ct}->{attributes}) {
1526 !!!cp (53);
1527 !!!parse-error (type => 'end tag attribute');
1528 } else {
1529 !!!cp (54);
1530 }
1531 } else {
1532 die "$0: $self->{ct}->{type}: Unknown token type";
1533 }
1534 $self->{state} = DATA_STATE;
1535 # reconsume
1536
1537 !!!emit ($self->{ct}); # start tag or end tag
1538
1539 redo A;
1540 } else {
1541 if ({
1542 0x0022 => 1, # "
1543 0x0027 => 1, # '
1544 0x003D => 1, # =
1545 }->{$self->{nc}}) {
1546 !!!cp (55);
1547 !!!parse-error (type => 'bad attribute name');
1548 } else {
1549 !!!cp (56);
1550 }
1551 $self->{ca}
1552 = {name => chr ($self->{nc}),
1553 value => '',
1554 line => $self->{line}, column => $self->{column}};
1555 $self->{state} = ATTRIBUTE_NAME_STATE;
1556 !!!next-input-character;
1557 redo A;
1558 }
1559 } elsif ($self->{state} == ATTRIBUTE_NAME_STATE) {
1560 my $before_leave = sub {
1561 if (exists $self->{ct}->{attributes} # start tag or end tag
1562 ->{$self->{ca}->{name}}) { # MUST
1563 !!!cp (57);
1564 !!!parse-error (type => 'duplicate attribute', text => $self->{ca}->{name}, line => $self->{ca}->{line}, column => $self->{ca}->{column});
1565 ## Discard $self->{ca} # MUST
1566 } else {
1567 !!!cp (58);
1568 $self->{ct}->{attributes}->{$self->{ca}->{name}}
1569 = $self->{ca};
1570 }
1571 }; # $before_leave
1572
1573 if ($is_space->{$self->{nc}}) {
1574 !!!cp (59);
1575 $before_leave->();
1576 $self->{state} = AFTER_ATTRIBUTE_NAME_STATE;
1577 !!!next-input-character;
1578 redo A;
1579 } elsif ($self->{nc} == 0x003D) { # =
1580 !!!cp (60);
1581 $before_leave->();
1582 $self->{state} = BEFORE_ATTRIBUTE_VALUE_STATE;
1583 !!!next-input-character;
1584 redo A;
1585 } elsif ($self->{nc} == 0x003E) { # >
1586 $before_leave->();
1587 if ($self->{ct}->{type} == START_TAG_TOKEN) {
1588 !!!cp (61);
1589 $self->{last_stag_name} = $self->{ct}->{tag_name};
1590 } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
1591 !!!cp (62);
1592 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1593 if ($self->{ct}->{attributes}) {
1594 !!!parse-error (type => 'end tag attribute');
1595 }
1596 } else {
1597 die "$0: $self->{ct}->{type}: Unknown token type";
1598 }
1599 $self->{state} = DATA_STATE;
1600 !!!next-input-character;
1601
1602 !!!emit ($self->{ct}); # start tag or end tag
1603
1604 redo A;
1605 } elsif (0x0041 <= $self->{nc} and
1606 $self->{nc} <= 0x005A) { # A..Z
1607 !!!cp (63);
1608 $self->{ca}->{name} .= chr ($self->{nc} + 0x0020);
1609 ## Stay in the state
1610 !!!next-input-character;
1611 redo A;
1612 } elsif ($self->{nc} == 0x002F) { # /
1613 !!!cp (64);
1614 $before_leave->();
1615 $self->{state} = SELF_CLOSING_START_TAG_STATE;
1616 !!!next-input-character;
1617 redo A;
1618 } elsif ($self->{nc} == -1) {
1619 !!!parse-error (type => 'unclosed tag');
1620 $before_leave->();
1621 if ($self->{ct}->{type} == START_TAG_TOKEN) {
1622 !!!cp (66);
1623 $self->{last_stag_name} = $self->{ct}->{tag_name};
1624 } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
1625 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1626 if ($self->{ct}->{attributes}) {
1627 !!!cp (67);
1628 !!!parse-error (type => 'end tag attribute');
1629 } else {
1630 ## NOTE: This state should never be reached.
1631 !!!cp (68);
1632 }
1633 } else {
1634 die "$0: $self->{ct}->{type}: Unknown token type";
1635 }
1636 $self->{state} = DATA_STATE;
1637 # reconsume
1638
1639 !!!emit ($self->{ct}); # start tag or end tag
1640
1641 redo A;
1642 } else {
1643 if ($self->{nc} == 0x0022 or # "
1644 $self->{nc} == 0x0027) { # '
1645 !!!cp (69);
1646 !!!parse-error (type => 'bad attribute name');
1647 } else {
1648 !!!cp (70);
1649 }
1650 $self->{ca}->{name} .= chr ($self->{nc});
1651 ## Stay in the state
1652 !!!next-input-character;
1653 redo A;
1654 }
1655 } elsif ($self->{state} == AFTER_ATTRIBUTE_NAME_STATE) {
1656 if ($is_space->{$self->{nc}}) {
1657 !!!cp (71);
1658 ## Stay in the state
1659 !!!next-input-character;
1660 redo A;
1661 } elsif ($self->{nc} == 0x003D) { # =
1662 !!!cp (72);
1663 $self->{state} = BEFORE_ATTRIBUTE_VALUE_STATE;
1664 !!!next-input-character;
1665 redo A;
1666 } elsif ($self->{nc} == 0x003E) { # >
1667 if ($self->{ct}->{type} == START_TAG_TOKEN) {
1668 !!!cp (73);
1669 $self->{last_stag_name} = $self->{ct}->{tag_name};
1670 } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
1671 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1672 if ($self->{ct}->{attributes}) {
1673 !!!cp (74);
1674 !!!parse-error (type => 'end tag attribute');
1675 } else {
1676 ## NOTE: This state should never be reached.
1677 !!!cp (75);
1678 }
1679 } else {
1680 die "$0: $self->{ct}->{type}: Unknown token type";
1681 }
1682 $self->{state} = DATA_STATE;
1683 !!!next-input-character;
1684
1685 !!!emit ($self->{ct}); # start tag or end tag
1686
1687 redo A;
1688 } elsif (0x0041 <= $self->{nc} and
1689 $self->{nc} <= 0x005A) { # A..Z
1690 !!!cp (76);
1691 $self->{ca}
1692 = {name => chr ($self->{nc} + 0x0020),
1693 value => '',
1694 line => $self->{line}, column => $self->{column}};
1695 $self->{state} = ATTRIBUTE_NAME_STATE;
1696 !!!next-input-character;
1697 redo A;
1698 } elsif ($self->{nc} == 0x002F) { # /
1699 !!!cp (77);
1700 $self->{state} = SELF_CLOSING_START_TAG_STATE;
1701 !!!next-input-character;
1702 redo A;
1703 } elsif ($self->{nc} == -1) {
1704 !!!parse-error (type => 'unclosed tag');
1705 if ($self->{ct}->{type} == START_TAG_TOKEN) {
1706 !!!cp (79);
1707 $self->{last_stag_name} = $self->{ct}->{tag_name};
1708 } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
1709 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1710 if ($self->{ct}->{attributes}) {
1711 !!!cp (80);
1712 !!!parse-error (type => 'end tag attribute');
1713 } else {
1714 ## NOTE: This state should never be reached.
1715 !!!cp (81);
1716 }
1717 } else {
1718 die "$0: $self->{ct}->{type}: Unknown token type";
1719 }
1720 $self->{state} = DATA_STATE;
1721 # reconsume
1722
1723 !!!emit ($self->{ct}); # start tag or end tag
1724
1725 redo A;
1726 } else {
1727 if ($self->{nc} == 0x0022 or # "
1728 $self->{nc} == 0x0027) { # '
1729 !!!cp (78);
1730 !!!parse-error (type => 'bad attribute name');
1731 } else {
1732 !!!cp (82);
1733 }
1734 $self->{ca}
1735 = {name => chr ($self->{nc}),
1736 value => '',
1737 line => $self->{line}, column => $self->{column}};
1738 $self->{state} = ATTRIBUTE_NAME_STATE;
1739 !!!next-input-character;
1740 redo A;
1741 }
1742 } elsif ($self->{state} == BEFORE_ATTRIBUTE_VALUE_STATE) {
1743 if ($is_space->{$self->{nc}}) {
1744 !!!cp (83);
1745 ## Stay in the state
1746 !!!next-input-character;
1747 redo A;
1748 } elsif ($self->{nc} == 0x0022) { # "
1749 !!!cp (84);
1750 $self->{state} = ATTRIBUTE_VALUE_DOUBLE_QUOTED_STATE;
1751 !!!next-input-character;
1752 redo A;
1753 } elsif ($self->{nc} == 0x0026) { # &
1754 !!!cp (85);
1755 $self->{state} = ATTRIBUTE_VALUE_UNQUOTED_STATE;
1756 ## reconsume
1757 redo A;
1758 } elsif ($self->{nc} == 0x0027) { # '
1759 !!!cp (86);
1760 $self->{state} = ATTRIBUTE_VALUE_SINGLE_QUOTED_STATE;
1761 !!!next-input-character;
1762 redo A;
1763 } elsif ($self->{nc} == 0x003E) { # >
1764 !!!parse-error (type => 'empty unquoted attribute value');
1765 if ($self->{ct}->{type} == START_TAG_TOKEN) {
1766 !!!cp (87);
1767 $self->{last_stag_name} = $self->{ct}->{tag_name};
1768 } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
1769 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1770 if ($self->{ct}->{attributes}) {
1771 !!!cp (88);
1772 !!!parse-error (type => 'end tag attribute');
1773 } else {
1774 ## NOTE: This state should never be reached.
1775 !!!cp (89);
1776 }
1777 } else {
1778 die "$0: $self->{ct}->{type}: Unknown token type";
1779 }
1780 $self->{state} = DATA_STATE;
1781 !!!next-input-character;
1782
1783 !!!emit ($self->{ct}); # start tag or end tag
1784
1785 redo A;
1786 } elsif ($self->{nc} == -1) {
1787 !!!parse-error (type => 'unclosed tag');
1788 if ($self->{ct}->{type} == START_TAG_TOKEN) {
1789 !!!cp (90);
1790 $self->{last_stag_name} = $self->{ct}->{tag_name};
1791 } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
1792 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1793 if ($self->{ct}->{attributes}) {
1794 !!!cp (91);
1795 !!!parse-error (type => 'end tag attribute');
1796 } else {
1797 ## NOTE: This state should never be reached.
1798 !!!cp (92);
1799 }
1800 } else {
1801 die "$0: $self->{ct}->{type}: Unknown token type";
1802 }
1803 $self->{state} = DATA_STATE;
1804 ## reconsume
1805
1806 !!!emit ($self->{ct}); # start tag or end tag
1807
1808 redo A;
1809 } else {
1810 if ($self->{nc} == 0x003D) { # =
1811 !!!cp (93);
1812 !!!parse-error (type => 'bad attribute value');
1813 } else {
1814 !!!cp (94);
1815 }
1816 $self->{ca}->{value} .= chr ($self->{nc});
1817 $self->{state} = ATTRIBUTE_VALUE_UNQUOTED_STATE;
1818 !!!next-input-character;
1819 redo A;
1820 }
1821 } elsif ($self->{state} == ATTRIBUTE_VALUE_DOUBLE_QUOTED_STATE) {
1822 if ($self->{nc} == 0x0022) { # "
1823 !!!cp (95);
1824 $self->{state} = AFTER_ATTRIBUTE_VALUE_QUOTED_STATE;
1825 !!!next-input-character;
1826 redo A;
1827 } elsif ($self->{nc} == 0x0026) { # &
1828 !!!cp (96);
1829 ## NOTE: In the spec, the tokenizer is switched to the
1830 ## "entity in attribute value state". In this implementation, the
1831 ## tokenizer is switched to the |ENTITY_STATE|, which is an
1832 ## implementation of the "consume a character reference" algorithm.
1833 $self->{prev_state} = $self->{state};
1834 $self->{entity_add} = 0x0022; # "
1835 $self->{state} = ENTITY_STATE;
1836 !!!next-input-character;
1837 redo A;
1838 } elsif ($self->{nc} == -1) {
1839 !!!parse-error (type => 'unclosed attribute value');
1840 if ($self->{ct}->{type} == START_TAG_TOKEN) {
1841 !!!cp (97);
1842 $self->{last_stag_name} = $self->{ct}->{tag_name};
1843 } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
1844 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1845 if ($self->{ct}->{attributes}) {
1846 !!!cp (98);
1847 !!!parse-error (type => 'end tag attribute');
1848 } else {
1849 ## NOTE: This state should never be reached.
1850 !!!cp (99);
1851 }
1852 } else {
1853 die "$0: $self->{ct}->{type}: Unknown token type";
1854 }
1855 $self->{state} = DATA_STATE;
1856 ## reconsume
1857
1858 !!!emit ($self->{ct}); # start tag or end tag
1859
1860 redo A;
1861 } else {
1862 !!!cp (100);
1863 $self->{ca}->{value} .= chr ($self->{nc});
1864 $self->{read_until}->($self->{ca}->{value},
1865 q["&],
1866 length $self->{ca}->{value});
1867
1868 ## Stay in the state
1869 !!!next-input-character;
1870 redo A;
1871 }
1872 } elsif ($self->{state} == ATTRIBUTE_VALUE_SINGLE_QUOTED_STATE) {
1873 if ($self->{nc} == 0x0027) { # '
1874 !!!cp (101);
1875 $self->{state} = AFTER_ATTRIBUTE_VALUE_QUOTED_STATE;
1876 !!!next-input-character;
1877 redo A;
1878 } elsif ($self->{nc} == 0x0026) { # &
1879 !!!cp (102);
1880 ## NOTE: In the spec, the tokenizer is switched to the
1881 ## "entity in attribute value state". In this implementation, the
1882 ## tokenizer is switched to the |ENTITY_STATE|, which is an
1883 ## implementation of the "consume a character reference" algorithm.
1884 $self->{entity_add} = 0x0027; # '
1885 $self->{prev_state} = $self->{state};
1886 $self->{state} = ENTITY_STATE;
1887 !!!next-input-character;
1888 redo A;
1889 } elsif ($self->{nc} == -1) {
1890 !!!parse-error (type => 'unclosed attribute value');
1891 if ($self->{ct}->{type} == START_TAG_TOKEN) {
1892 !!!cp (103);
1893 $self->{last_stag_name} = $self->{ct}->{tag_name};
1894 } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
1895 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1896 if ($self->{ct}->{attributes}) {
1897 !!!cp (104);
1898 !!!parse-error (type => 'end tag attribute');
1899 } else {
1900 ## NOTE: This state should never be reached.
1901 !!!cp (105);
1902 }
1903 } else {
1904 die "$0: $self->{ct}->{type}: Unknown token type";
1905 }
1906 $self->{state} = DATA_STATE;
1907 ## reconsume
1908
1909 !!!emit ($self->{ct}); # start tag or end tag
1910
1911 redo A;
1912 } else {
1913 !!!cp (106);
1914 $self->{ca}->{value} .= chr ($self->{nc});
1915 $self->{read_until}->($self->{ca}->{value},
1916 q['&],
1917 length $self->{ca}->{value});
1918
1919 ## Stay in the state
1920 !!!next-input-character;
1921 redo A;
1922 }
1923 } elsif ($self->{state} == ATTRIBUTE_VALUE_UNQUOTED_STATE) {
1924 if ($is_space->{$self->{nc}}) {
1925 !!!cp (107);
1926 $self->{state} = BEFORE_ATTRIBUTE_NAME_STATE;
1927 !!!next-input-character;
1928 redo A;
1929 } elsif ($self->{nc} == 0x0026) { # &
1930 !!!cp (108);
1931 ## NOTE: In the spec, the tokenizer is switched to the
1932 ## "entity in attribute value state". In this implementation, the
1933 ## tokenizer is switched to the |ENTITY_STATE|, which is an
1934 ## implementation of the "consume a character reference" algorithm.
1935 $self->{entity_add} = -1;
1936 $self->{prev_state} = $self->{state};
1937 $self->{state} = ENTITY_STATE;
1938 !!!next-input-character;
1939 redo A;
1940 } elsif ($self->{nc} == 0x003E) { # >
1941 if ($self->{ct}->{type} == START_TAG_TOKEN) {
1942 !!!cp (109);
1943 $self->{last_stag_name} = $self->{ct}->{tag_name};
1944 } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
1945 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1946 if ($self->{ct}->{attributes}) {
1947 !!!cp (110);
1948 !!!parse-error (type => 'end tag attribute');
1949 } else {
1950 ## NOTE: This state should never be reached.
1951 !!!cp (111);
1952 }
1953 } else {
1954 die "$0: $self->{ct}->{type}: Unknown token type";
1955 }
1956 $self->{state} = DATA_STATE;
1957 !!!next-input-character;
1958
1959 !!!emit ($self->{ct}); # start tag or end tag
1960
1961 redo A;
1962 } elsif ($self->{nc} == -1) {
1963 !!!parse-error (type => 'unclosed tag');
1964 if ($self->{ct}->{type} == START_TAG_TOKEN) {
1965 !!!cp (112);
1966 $self->{last_stag_name} = $self->{ct}->{tag_name};
1967 } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
1968 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1969 if ($self->{ct}->{attributes}) {
1970 !!!cp (113);
1971 !!!parse-error (type => 'end tag attribute');
1972 } else {
1973 ## NOTE: This state should never be reached.
1974 !!!cp (114);
1975 }
1976 } else {
1977 die "$0: $self->{ct}->{type}: Unknown token type";
1978 }
1979 $self->{state} = DATA_STATE;
1980 ## reconsume
1981
1982 !!!emit ($self->{ct}); # start tag or end tag
1983
1984 redo A;
1985 } else {
1986 if ({
1987 0x0022 => 1, # "
1988 0x0027 => 1, # '
1989 0x003D => 1, # =
1990 }->{$self->{nc}}) {
1991 !!!cp (115);
1992 !!!parse-error (type => 'bad attribute value');
1993 } else {
1994 !!!cp (116);
1995 }
1996 $self->{ca}->{value} .= chr ($self->{nc});
1997 $self->{read_until}->($self->{ca}->{value},
1998 q["'=& >],
1999 length $self->{ca}->{value});
2000
2001 ## Stay in the state
2002 !!!next-input-character;
2003 redo A;
2004 }
2005 } elsif ($self->{state} == AFTER_ATTRIBUTE_VALUE_QUOTED_STATE) {
2006 if ($is_space->{$self->{nc}}) {
2007 !!!cp (118);
2008 $self->{state} = BEFORE_ATTRIBUTE_NAME_STATE;
2009 !!!next-input-character;
2010 redo A;
2011 } elsif ($self->{nc} == 0x003E) { # >
2012 if ($self->{ct}->{type} == START_TAG_TOKEN) {
2013 !!!cp (119);
2014 $self->{last_stag_name} = $self->{ct}->{tag_name};
2015 } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
2016 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
2017 if ($self->{ct}->{attributes}) {
2018 !!!cp (120);
2019 !!!parse-error (type => 'end tag attribute');
2020 } else {
2021 ## NOTE: This state should never be reached.
2022 !!!cp (121);
2023 }
2024 } else {
2025 die "$0: $self->{ct}->{type}: Unknown token type";
2026 }
2027 $self->{state} = DATA_STATE;
2028 !!!next-input-character;
2029
2030 !!!emit ($self->{ct}); # start tag or end tag
2031
2032 redo A;
2033 } elsif ($self->{nc} == 0x002F) { # /
2034 !!!cp (122);
2035 $self->{state} = SELF_CLOSING_START_TAG_STATE;
2036 !!!next-input-character;
2037 redo A;
2038 } elsif ($self->{nc} == -1) {
2039 !!!parse-error (type => 'unclosed tag');
2040 if ($self->{ct}->{type} == START_TAG_TOKEN) {
2041 !!!cp (122.3);
2042 $self->{last_stag_name} = $self->{ct}->{tag_name};
2043 } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
2044 if ($self->{ct}->{attributes}) {
2045 !!!cp (122.1);
2046 !!!parse-error (type => 'end tag attribute');
2047 } else {
2048 ## NOTE: This state should never be reached.
2049 !!!cp (122.2);
2050 }
2051 } else {
2052 die "$0: $self->{ct}->{type}: Unknown token type";
2053 }
2054 $self->{state} = DATA_STATE;
2055 ## Reconsume.
2056 !!!emit ($self->{ct}); # start tag or end tag
2057 redo A;
2058 } else {
2059 !!!cp ('124.1');
2060 !!!parse-error (type => 'no space between attributes');
2061 $self->{state} = BEFORE_ATTRIBUTE_NAME_STATE;
2062 ## reconsume
2063 redo A;
2064 }
2065 } elsif ($self->{state} == SELF_CLOSING_START_TAG_STATE) {
2066 if ($self->{nc} == 0x003E) { # >
2067 if ($self->{ct}->{type} == END_TAG_TOKEN) {
2068 !!!cp ('124.2');
2069 !!!parse-error (type => 'nestc', token => $self->{ct});
2070 ## TODO: Different type than slash in start tag
2071 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
2072 if ($self->{ct}->{attributes}) {
2073 !!!cp ('124.4');
2074 !!!parse-error (type => 'end tag attribute');
2075 } else {
2076 !!!cp ('124.5');
2077 }
2078 ## TODO: Test |<title></title/>|
2079 } else {
2080 !!!cp ('124.3');
2081 $self->{self_closing} = 1;
2082 }
2083
2084 $self->{state} = DATA_STATE;
2085 !!!next-input-character;
2086
2087 !!!emit ($self->{ct}); # start tag or end tag
2088
2089 redo A;
2090 } elsif ($self->{nc} == -1) {
2091 !!!parse-error (type => 'unclosed tag');
2092 if ($self->{ct}->{type} == START_TAG_TOKEN) {
2093 !!!cp (124.7);
2094 $self->{last_stag_name} = $self->{ct}->{tag_name};
2095 } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
2096 if ($self->{ct}->{attributes}) {
2097 !!!cp (124.5);
2098 !!!parse-error (type => 'end tag attribute');
2099 } else {
2100 ## NOTE: This state should never be reached.
2101 !!!cp (124.6);
2102 }
2103 } else {
2104 die "$0: $self->{ct}->{type}: Unknown token type";
2105 }
2106 $self->{state} = DATA_STATE;
2107 ## Reconsume.
2108 !!!emit ($self->{ct}); # start tag or end tag
2109 redo A;
2110 } else {
2111 !!!cp ('124.4');
2112 !!!parse-error (type => 'nestc');
2113 ## TODO: This error type is wrong.
2114 $self->{state} = BEFORE_ATTRIBUTE_NAME_STATE;
2115 ## Reconsume.
2116 redo A;
2117 }
2118 } elsif ($self->{state} == BOGUS_COMMENT_STATE) {
2119 ## (only happen if PCDATA state)
2120
2121 ## NOTE: Unlike spec's "bogus comment state", this implementation
2122 ## consumes characters one-by-one basis.
2123
2124 if ($self->{nc} == 0x003E) { # >
2125 !!!cp (124);
2126 $self->{state} = DATA_STATE;
2127 !!!next-input-character;
2128
2129 !!!emit ($self->{ct}); # comment
2130 redo A;
2131 } elsif ($self->{nc} == -1) {
2132 !!!cp (125);
2133 $self->{state} = DATA_STATE;
2134 ## reconsume
2135
2136 !!!emit ($self->{ct}); # comment
2137 redo A;
2138 } else {
2139 !!!cp (126);
2140 $self->{ct}->{data} .= chr ($self->{nc}); # comment
2141 $self->{read_until}->($self->{ct}->{data},
2142 q[>],
2143 length $self->{ct}->{data});
2144
2145 ## Stay in the state.
2146 !!!next-input-character;
2147 redo A;
2148 }
2149 } elsif ($self->{state} == MARKUP_DECLARATION_OPEN_STATE) {
2150 ## (only happen if PCDATA state)
2151
2152 if ($self->{nc} == 0x002D) { # -
2153 !!!cp (133);
2154 $self->{state} = MD_HYPHEN_STATE;
2155 !!!next-input-character;
2156 redo A;
2157 } elsif ($self->{nc} == 0x0044 or # D
2158 $self->{nc} == 0x0064) { # d
2159 ## ASCII case-insensitive.
2160 !!!cp (130);
2161 $self->{state} = MD_DOCTYPE_STATE;
2162 $self->{s_kwd} = chr $self->{nc};
2163 !!!next-input-character;
2164 redo A;
2165 } elsif ($self->{insertion_mode} & IN_FOREIGN_CONTENT_IM and
2166 $self->{open_elements}->[-1]->[1] & FOREIGN_EL and
2167 $self->{nc} == 0x005B) { # [
2168 !!!cp (135.4);
2169 $self->{state} = MD_CDATA_STATE;
2170 $self->{s_kwd} = '[';
2171 !!!next-input-character;
2172 redo A;
2173 } else {
2174 !!!cp (136);
2175 }
2176
2177 !!!parse-error (type => 'bogus comment',
2178 line => $self->{line_prev},
2179 column => $self->{column_prev} - 1);
2180 ## Reconsume.
2181 $self->{state} = BOGUS_COMMENT_STATE;
2182 $self->{ct} = {type => COMMENT_TOKEN, data => '',
2183 line => $self->{line_prev},
2184 column => $self->{column_prev} - 1,
2185 };
2186 redo A;
2187 } elsif ($self->{state} == MD_HYPHEN_STATE) {
2188 if ($self->{nc} == 0x002D) { # -
2189 !!!cp (127);
2190 $self->{ct} = {type => COMMENT_TOKEN, data => '',
2191 line => $self->{line_prev},
2192 column => $self->{column_prev} - 2,
2193 };
2194 $self->{state} = COMMENT_START_STATE;
2195 !!!next-input-character;
2196 redo A;
2197 } else {
2198 !!!cp (128);
2199 !!!parse-error (type => 'bogus comment',
2200 line => $self->{line_prev},
2201 column => $self->{column_prev} - 2);
2202 $self->{state} = BOGUS_COMMENT_STATE;
2203 ## Reconsume.
2204 $self->{ct} = {type => COMMENT_TOKEN,
2205 data => '-',
2206 line => $self->{line_prev},
2207 column => $self->{column_prev} - 2,
2208 };
2209 redo A;
2210 }
2211 } elsif ($self->{state} == MD_DOCTYPE_STATE) {
2212 ## ASCII case-insensitive.
2213 if ($self->{nc} == [
2214 undef,
2215 0x004F, # O
2216 0x0043, # C
2217 0x0054, # T
2218 0x0059, # Y
2219 0x0050, # P
2220 ]->[length $self->{s_kwd}] or
2221 $self->{nc} == [
2222 undef,
2223 0x006F, # o
2224 0x0063, # c
2225 0x0074, # t
2226 0x0079, # y
2227 0x0070, # p
2228 ]->[length $self->{s_kwd}]) {
2229 !!!cp (131);
2230 ## Stay in the state.
2231 $self->{s_kwd} .= chr $self->{nc};
2232 !!!next-input-character;
2233 redo A;
2234 } elsif ((length $self->{s_kwd}) == 6 and
2235 ($self->{nc} == 0x0045 or # E
2236 $self->{nc} == 0x0065)) { # e
2237 !!!cp (129);
2238 $self->{state} = DOCTYPE_STATE;
2239 $self->{ct} = {type => DOCTYPE_TOKEN,
2240 quirks => 1,
2241 line => $self->{line_prev},
2242 column => $self->{column_prev} - 7,
2243 };
2244 !!!next-input-character;
2245 redo A;
2246 } else {
2247 !!!cp (132);
2248 !!!parse-error (type => 'bogus comment',
2249 line => $self->{line_prev},
2250 column => $self->{column_prev} - 1 - length $self->{s_kwd});
2251 $self->{state} = BOGUS_COMMENT_STATE;
2252 ## Reconsume.
2253 $self->{ct} = {type => COMMENT_TOKEN,
2254 data => $self->{s_kwd},
2255 line => $self->{line_prev},
2256 column => $self->{column_prev} - 1 - length $self->{s_kwd},
2257 };
2258 redo A;
2259 }
2260 } elsif ($self->{state} == MD_CDATA_STATE) {
2261 if ($self->{nc} == {
2262 '[' => 0x0043, # C
2263 '[C' => 0x0044, # D
2264 '[CD' => 0x0041, # A
2265 '[CDA' => 0x0054, # T
2266 '[CDAT' => 0x0041, # A
2267 }->{$self->{s_kwd}}) {
2268 !!!cp (135.1);
2269 ## Stay in the state.
2270 $self->{s_kwd} .= chr $self->{nc};
2271 !!!next-input-character;
2272 redo A;
2273 } elsif ($self->{s_kwd} eq '[CDATA' and
2274 $self->{nc} == 0x005B) { # [
2275 !!!cp (135.2);
2276 $self->{ct} = {type => CHARACTER_TOKEN,
2277 data => '',
2278 line => $self->{line_prev},
2279 column => $self->{column_prev} - 7};
2280 $self->{state} = CDATA_SECTION_STATE;
2281 !!!next-input-character;
2282 redo A;
2283 } else {
2284 !!!cp (135.3);
2285 !!!parse-error (type => 'bogus comment',
2286 line => $self->{line_prev},
2287 column => $self->{column_prev} - 1 - length $self->{s_kwd});
2288 $self->{state} = BOGUS_COMMENT_STATE;
2289 ## Reconsume.
2290 $self->{ct} = {type => COMMENT_TOKEN,
2291 data => $self->{s_kwd},
2292 line => $self->{line_prev},
2293 column => $self->{column_prev} - 1 - length $self->{s_kwd},
2294 };
2295 redo A;
2296 }
2297 } elsif ($self->{state} == COMMENT_START_STATE) {
2298 if ($self->{nc} == 0x002D) { # -
2299 !!!cp (137);
2300 $self->{state} = COMMENT_START_DASH_STATE;
2301 !!!next-input-character;
2302 redo A;
2303 } elsif ($self->{nc} == 0x003E) { # >
2304 !!!cp (138);
2305 !!!parse-error (type => 'bogus comment');
2306 $self->{state} = DATA_STATE;
2307 !!!next-input-character;
2308
2309 !!!emit ($self->{ct}); # comment
2310
2311 redo A;
2312 } elsif ($self->{nc} == -1) {
2313 !!!cp (139);
2314 !!!parse-error (type => 'unclosed comment');
2315 $self->{state} = DATA_STATE;
2316 ## reconsume
2317
2318 !!!emit ($self->{ct}); # comment
2319
2320 redo A;
2321 } else {
2322 !!!cp (140);
2323 $self->{ct}->{data} # comment
2324 .= chr ($self->{nc});
2325 $self->{state} = COMMENT_STATE;
2326 !!!next-input-character;
2327 redo A;
2328 }
2329 } elsif ($self->{state} == COMMENT_START_DASH_STATE) {
2330 if ($self->{nc} == 0x002D) { # -
2331 !!!cp (141);
2332 $self->{state} = COMMENT_END_STATE;
2333 !!!next-input-character;
2334 redo A;
2335 } elsif ($self->{nc} == 0x003E) { # >
2336 !!!cp (142);
2337 !!!parse-error (type => 'bogus comment');
2338 $self->{state} = DATA_STATE;
2339 !!!next-input-character;
2340
2341 !!!emit ($self->{ct}); # comment
2342
2343 redo A;
2344 } elsif ($self->{nc} == -1) {
2345 !!!cp (143);
2346 !!!parse-error (type => 'unclosed comment');
2347 $self->{state} = DATA_STATE;
2348 ## reconsume
2349
2350 !!!emit ($self->{ct}); # comment
2351
2352 redo A;
2353 } else {
2354 !!!cp (144);
2355 $self->{ct}->{data} # comment
2356 .= '-' . chr ($self->{nc});
2357 $self->{state} = COMMENT_STATE;
2358 !!!next-input-character;
2359 redo A;
2360 }
2361 } elsif ($self->{state} == COMMENT_STATE) {
2362 if ($self->{nc} == 0x002D) { # -
2363 !!!cp (145);
2364 $self->{state} = COMMENT_END_DASH_STATE;
2365 !!!next-input-character;
2366 redo A;
2367 } elsif ($self->{nc} == -1) {
2368 !!!cp (146);
2369 !!!parse-error (type => 'unclosed comment');
2370 $self->{state} = DATA_STATE;
2371 ## reconsume
2372
2373 !!!emit ($self->{ct}); # comment
2374
2375 redo A;
2376 } else {
2377 !!!cp (147);
2378 $self->{ct}->{data} .= chr ($self->{nc}); # comment
2379 $self->{read_until}->($self->{ct}->{data},
2380 q[-],
2381 length $self->{ct}->{data});
2382
2383 ## Stay in the state
2384 !!!next-input-character;
2385 redo A;
2386 }
2387 } elsif ($self->{state} == COMMENT_END_DASH_STATE) {
2388 if ($self->{nc} == 0x002D) { # -
2389 !!!cp (148);
2390 $self->{state} = COMMENT_END_STATE;
2391 !!!next-input-character;
2392 redo A;
2393 } elsif ($self->{nc} == -1) {
2394 !!!cp (149);
2395 !!!parse-error (type => 'unclosed comment');
2396 $self->{state} = DATA_STATE;
2397 ## reconsume
2398
2399 !!!emit ($self->{ct}); # comment
2400
2401 redo A;
2402 } else {
2403 !!!cp (150);
2404 $self->{ct}->{data} .= '-' . chr ($self->{nc}); # comment
2405 $self->{state} = COMMENT_STATE;
2406 !!!next-input-character;
2407 redo A;
2408 }
2409 } elsif ($self->{state} == COMMENT_END_STATE) {
2410 if ($self->{nc} == 0x003E) { # >
2411 !!!cp (151);
2412 $self->{state} = DATA_STATE;
2413 !!!next-input-character;
2414
2415 !!!emit ($self->{ct}); # comment
2416
2417 redo A;
2418 } elsif ($self->{nc} == 0x002D) { # -
2419 !!!cp (152);
2420 !!!parse-error (type => 'dash in comment',
2421 line => $self->{line_prev},
2422 column => $self->{column_prev});
2423 $self->{ct}->{data} .= '-'; # comment
2424 ## Stay in the state
2425 !!!next-input-character;
2426 redo A;
2427 } elsif ($self->{nc} == -1) {
2428 !!!cp (153);
2429 !!!parse-error (type => 'unclosed comment');
2430 $self->{state} = DATA_STATE;
2431 ## reconsume
2432
2433 !!!emit ($self->{ct}); # comment
2434
2435 redo A;
2436 } else {
2437 !!!cp (154);
2438 !!!parse-error (type => 'dash in comment',
2439 line => $self->{line_prev},
2440 column => $self->{column_prev});
2441 $self->{ct}->{data} .= '--' . chr ($self->{nc}); # comment
2442 $self->{state} = COMMENT_STATE;
2443 !!!next-input-character;
2444 redo A;
2445 }
2446 } elsif ($self->{state} == DOCTYPE_STATE) {
2447 if ($is_space->{$self->{nc}}) {
2448 !!!cp (155);
2449 $self->{state} = BEFORE_DOCTYPE_NAME_STATE;
2450 !!!next-input-character;
2451 redo A;
2452 } else {
2453 !!!cp (156);
2454 !!!parse-error (type => 'no space before DOCTYPE name');
2455 $self->{state} = BEFORE_DOCTYPE_NAME_STATE;
2456 ## reconsume
2457 redo A;
2458 }
2459 } elsif ($self->{state} == BEFORE_DOCTYPE_NAME_STATE) {
2460 if ($is_space->{$self->{nc}}) {
2461 !!!cp (157);
2462 ## Stay in the state
2463 !!!next-input-character;
2464 redo A;
2465 } elsif ($self->{nc} == 0x003E) { # >
2466 !!!cp (158);
2467 !!!parse-error (type => 'no DOCTYPE name');
2468 $self->{state} = DATA_STATE;
2469 !!!next-input-character;
2470
2471 !!!emit ($self->{ct}); # DOCTYPE (quirks)
2472
2473 redo A;
2474 } elsif ($self->{nc} == -1) {
2475 !!!cp (159);
2476 !!!parse-error (type => 'no DOCTYPE name');
2477 $self->{state} = DATA_STATE;
2478 ## reconsume
2479
2480 !!!emit ($self->{ct}); # DOCTYPE (quirks)
2481
2482 redo A;
2483 } else {
2484 !!!cp (160);
2485 $self->{ct}->{name} = chr $self->{nc};
2486 delete $self->{ct}->{quirks};
2487 $self->{state} = DOCTYPE_NAME_STATE;
2488 !!!next-input-character;
2489 redo A;
2490 }
2491 } elsif ($self->{state} == DOCTYPE_NAME_STATE) {
2492 ## ISSUE: Redundant "First," in the spec.
2493 if ($is_space->{$self->{nc}}) {
2494 !!!cp (161);
2495 $self->{state} = AFTER_DOCTYPE_NAME_STATE;
2496 !!!next-input-character;
2497 redo A;
2498 } elsif ($self->{nc} == 0x003E) { # >
2499 !!!cp (162);
2500 $self->{state} = DATA_STATE;
2501 !!!next-input-character;
2502
2503 !!!emit ($self->{ct}); # DOCTYPE
2504
2505 redo A;
2506 } elsif ($self->{nc} == -1) {
2507 !!!cp (163);
2508 !!!parse-error (type => 'unclosed DOCTYPE');
2509 $self->{state} = DATA_STATE;
2510 ## reconsume
2511
2512 $self->{ct}->{quirks} = 1;
2513 !!!emit ($self->{ct}); # DOCTYPE
2514
2515 redo A;
2516 } else {
2517 !!!cp (164);
2518 $self->{ct}->{name}
2519 .= chr ($self->{nc}); # DOCTYPE
2520 ## Stay in the state
2521 !!!next-input-character;
2522 redo A;
2523 }
2524 } elsif ($self->{state} == AFTER_DOCTYPE_NAME_STATE) {
2525 if ($is_space->{$self->{nc}}) {
2526 !!!cp (165);
2527 ## Stay in the state
2528 !!!next-input-character;
2529 redo A;
2530 } elsif ($self->{nc} == 0x003E) { # >
2531 !!!cp (166);
2532 $self->{state} = DATA_STATE;
2533 !!!next-input-character;
2534
2535 !!!emit ($self->{ct}); # DOCTYPE
2536
2537 redo A;
2538 } elsif ($self->{nc} == -1) {
2539 !!!cp (167);
2540 !!!parse-error (type => 'unclosed DOCTYPE');
2541 $self->{state} = DATA_STATE;
2542 ## reconsume
2543
2544 $self->{ct}->{quirks} = 1;
2545 !!!emit ($self->{ct}); # DOCTYPE
2546
2547 redo A;
2548 } elsif ($self->{nc} == 0x0050 or # P
2549 $self->{nc} == 0x0070) { # p
2550 $self->{state} = PUBLIC_STATE;
2551 $self->{s_kwd} = chr $self->{nc};
2552 !!!next-input-character;
2553 redo A;
2554 } elsif ($self->{nc} == 0x0053 or # S
2555 $self->{nc} == 0x0073) { # s
2556 $self->{state} = SYSTEM_STATE;
2557 $self->{s_kwd} = chr $self->{nc};
2558 !!!next-input-character;
2559 redo A;
2560 } else {
2561 !!!cp (180);
2562 !!!parse-error (type => 'string after DOCTYPE name');
2563 $self->{ct}->{quirks} = 1;
2564
2565 $self->{state} = BOGUS_DOCTYPE_STATE;
2566 !!!next-input-character;
2567 redo A;
2568 }
2569 } elsif ($self->{state} == PUBLIC_STATE) {
2570 ## ASCII case-insensitive
2571 if ($self->{nc} == [
2572 undef,
2573 0x0055, # U
2574 0x0042, # B
2575 0x004C, # L
2576 0x0049, # I
2577 ]->[length $self->{s_kwd}] or
2578 $self->{nc} == [
2579 undef,
2580 0x0075, # u
2581 0x0062, # b
2582 0x006C, # l
2583 0x0069, # i
2584 ]->[length $self->{s_kwd}]) {
2585 !!!cp (175);
2586 ## Stay in the state.
2587 $self->{s_kwd} .= chr $self->{nc};
2588 !!!next-input-character;
2589 redo A;
2590 } elsif ((length $self->{s_kwd}) == 5 and
2591 ($self->{nc} == 0x0043 or # C
2592 $self->{nc} == 0x0063)) { # c
2593 !!!cp (168);
2594 $self->{state} = BEFORE_DOCTYPE_PUBLIC_IDENTIFIER_STATE;
2595 !!!next-input-character;
2596 redo A;
2597 } else {
2598 !!!cp (169);
2599 !!!parse-error (type => 'string after DOCTYPE name',
2600 line => $self->{line_prev},
2601 column => $self->{column_prev} + 1 - length $self->{s_kwd});
2602 $self->{ct}->{quirks} = 1;
2603
2604 $self->{state} = BOGUS_DOCTYPE_STATE;
2605 ## Reconsume.
2606 redo A;
2607 }
2608 } elsif ($self->{state} == SYSTEM_STATE) {
2609 ## ASCII case-insensitive
2610 if ($self->{nc} == [
2611 undef,
2612 0x0059, # Y
2613 0x0053, # S
2614 0x0054, # T
2615 0x0045, # E
2616 ]->[length $self->{s_kwd}] or
2617 $self->{nc} == [
2618 undef,
2619 0x0079, # y
2620 0x0073, # s
2621 0x0074, # t
2622 0x0065, # e
2623 ]->[length $self->{s_kwd}]) {
2624 !!!cp (170);
2625 ## Stay in the state.
2626 $self->{s_kwd} .= chr $self->{nc};
2627 !!!next-input-character;
2628 redo A;
2629 } elsif ((length $self->{s_kwd}) == 5 and
2630 ($self->{nc} == 0x004D or # M
2631 $self->{nc} == 0x006D)) { # m
2632 !!!cp (171);
2633 $self->{state} = BEFORE_DOCTYPE_SYSTEM_IDENTIFIER_STATE;
2634 !!!next-input-character;
2635 redo A;
2636 } else {
2637 !!!cp (172);
2638 !!!parse-error (type => 'string after DOCTYPE name',
2639 line => $self->{line_prev},
2640 column => $self->{column_prev} + 1 - length $self->{s_kwd});
2641 $self->{ct}->{quirks} = 1;
2642
2643 $self->{state} = BOGUS_DOCTYPE_STATE;
2644 ## Reconsume.
2645 redo A;
2646 }
2647 } elsif ($self->{state} == BEFORE_DOCTYPE_PUBLIC_IDENTIFIER_STATE) {
2648 if ($is_space->{$self->{nc}}) {
2649 !!!cp (181);
2650 ## Stay in the state
2651 !!!next-input-character;
2652 redo A;
2653 } elsif ($self->{nc} eq 0x0022) { # "
2654 !!!cp (182);
2655 $self->{ct}->{pubid} = ''; # DOCTYPE
2656 $self->{state} = DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED_STATE;
2657 !!!next-input-character;
2658 redo A;
2659 } elsif ($self->{nc} eq 0x0027) { # '
2660 !!!cp (183);
2661 $self->{ct}->{pubid} = ''; # DOCTYPE
2662 $self->{state} = DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED_STATE;
2663 !!!next-input-character;
2664 redo A;
2665 } elsif ($self->{nc} eq 0x003E) { # >
2666 !!!cp (184);
2667 !!!parse-error (type => 'no PUBLIC literal');
2668
2669 $self->{state} = DATA_STATE;
2670 !!!next-input-character;
2671
2672 $self->{ct}->{quirks} = 1;
2673 !!!emit ($self->{ct}); # DOCTYPE
2674
2675 redo A;
2676 } elsif ($self->{nc} == -1) {
2677 !!!cp (185);
2678 !!!parse-error (type => 'unclosed DOCTYPE');
2679
2680 $self->{state} = DATA_STATE;
2681 ## reconsume
2682
2683 $self->{ct}->{quirks} = 1;
2684 !!!emit ($self->{ct}); # DOCTYPE
2685
2686 redo A;
2687 } else {
2688 !!!cp (186);
2689 !!!parse-error (type => 'string after PUBLIC');
2690 $self->{ct}->{quirks} = 1;
2691
2692 $self->{state} = BOGUS_DOCTYPE_STATE;
2693 !!!next-input-character;
2694 redo A;
2695 }
2696 } elsif ($self->{state} == DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED_STATE) {
2697 if ($self->{nc} == 0x0022) { # "
2698 !!!cp (187);
2699 $self->{state} = AFTER_DOCTYPE_PUBLIC_IDENTIFIER_STATE;
2700 !!!next-input-character;
2701 redo A;
2702 } elsif ($self->{nc} == 0x003E) { # >
2703 !!!cp (188);
2704 !!!parse-error (type => 'unclosed PUBLIC literal');
2705
2706 $self->{state} = DATA_STATE;
2707 !!!next-input-character;
2708
2709 $self->{ct}->{quirks} = 1;
2710 !!!emit ($self->{ct}); # DOCTYPE
2711
2712 redo A;
2713 } elsif ($self->{nc} == -1) {
2714 !!!cp (189);
2715 !!!parse-error (type => 'unclosed PUBLIC literal');
2716
2717 $self->{state} = DATA_STATE;
2718 ## reconsume
2719
2720 $self->{ct}->{quirks} = 1;
2721 !!!emit ($self->{ct}); # DOCTYPE
2722
2723 redo A;
2724 } else {
2725 !!!cp (190);
2726 $self->{ct}->{pubid} # DOCTYPE
2727 .= chr $self->{nc};
2728 $self->{read_until}->($self->{ct}->{pubid}, q[">],
2729 length $self->{ct}->{pubid});
2730
2731 ## Stay in the state
2732 !!!next-input-character;
2733 redo A;
2734 }
2735 } elsif ($self->{state} == DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED_STATE) {
2736 if ($self->{nc} == 0x0027) { # '
2737 !!!cp (191);
2738 $self->{state} = AFTER_DOCTYPE_PUBLIC_IDENTIFIER_STATE;
2739 !!!next-input-character;
2740 redo A;
2741 } elsif ($self->{nc} == 0x003E) { # >
2742 !!!cp (192);
2743 !!!parse-error (type => 'unclosed PUBLIC literal');
2744
2745 $self->{state} = DATA_STATE;
2746 !!!next-input-character;
2747
2748 $self->{ct}->{quirks} = 1;
2749 !!!emit ($self->{ct}); # DOCTYPE
2750
2751 redo A;
2752 } elsif ($self->{nc} == -1) {
2753 !!!cp (193);
2754 !!!parse-error (type => 'unclosed PUBLIC literal');
2755
2756 $self->{state} = DATA_STATE;
2757 ## reconsume
2758
2759 $self->{ct}->{quirks} = 1;
2760 !!!emit ($self->{ct}); # DOCTYPE
2761
2762 redo A;
2763 } else {
2764 !!!cp (194);
2765 $self->{ct}->{pubid} # DOCTYPE
2766 .= chr $self->{nc};
2767 $self->{read_until}->($self->{ct}->{pubid}, q['>],
2768 length $self->{ct}->{pubid});
2769
2770 ## Stay in the state
2771 !!!next-input-character;
2772 redo A;
2773 }
2774 } elsif ($self->{state} == AFTER_DOCTYPE_PUBLIC_IDENTIFIER_STATE) {
2775 if ($is_space->{$self->{nc}}) {
2776 !!!cp (195);
2777 ## Stay in the state
2778 !!!next-input-character;
2779 redo A;
2780 } elsif ($self->{nc} == 0x0022) { # "
2781 !!!cp (196);
2782 $self->{ct}->{sysid} = ''; # DOCTYPE
2783 $self->{state} = DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED_STATE;
2784 !!!next-input-character;
2785 redo A;
2786 } elsif ($self->{nc} == 0x0027) { # '
2787 !!!cp (197);
2788 $self->{ct}->{sysid} = ''; # DOCTYPE
2789 $self->{state} = DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED_STATE;
2790 !!!next-input-character;
2791 redo A;
2792 } elsif ($self->{nc} == 0x003E) { # >
2793 !!!cp (198);
2794 $self->{state} = DATA_STATE;
2795 !!!next-input-character;
2796
2797 !!!emit ($self->{ct}); # DOCTYPE
2798
2799 redo A;
2800 } elsif ($self->{nc} == -1) {
2801 !!!cp (199);
2802 !!!parse-error (type => 'unclosed DOCTYPE');
2803
2804 $self->{state} = DATA_STATE;
2805 ## reconsume
2806
2807 $self->{ct}->{quirks} = 1;
2808 !!!emit ($self->{ct}); # DOCTYPE
2809
2810 redo A;
2811 } else {
2812 !!!cp (200);
2813 !!!parse-error (type => 'string after PUBLIC literal');
2814 $self->{ct}->{quirks} = 1;
2815
2816 $self->{state} = BOGUS_DOCTYPE_STATE;
2817 !!!next-input-character;
2818 redo A;
2819 }
2820 } elsif ($self->{state} == BEFORE_DOCTYPE_SYSTEM_IDENTIFIER_STATE) {
2821 if ($is_space->{$self->{nc}}) {
2822 !!!cp (201);
2823 ## Stay in the state
2824 !!!next-input-character;
2825 redo A;
2826 } elsif ($self->{nc} == 0x0022) { # "
2827 !!!cp (202);
2828 $self->{ct}->{sysid} = ''; # DOCTYPE
2829 $self->{state} = DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED_STATE;
2830 !!!next-input-character;
2831 redo A;
2832 } elsif ($self->{nc} == 0x0027) { # '
2833 !!!cp (203);
2834 $self->{ct}->{sysid} = ''; # DOCTYPE
2835 $self->{state} = DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED_STATE;
2836 !!!next-input-character;
2837 redo A;
2838 } elsif ($self->{nc} == 0x003E) { # >
2839 !!!cp (204);
2840 !!!parse-error (type => 'no SYSTEM literal');
2841 $self->{state} = DATA_STATE;
2842 !!!next-input-character;
2843
2844 $self->{ct}->{quirks} = 1;
2845 !!!emit ($self->{ct}); # DOCTYPE
2846
2847 redo A;
2848 } elsif ($self->{nc} == -1) {
2849 !!!cp (205);
2850 !!!parse-error (type => 'unclosed DOCTYPE');
2851
2852 $self->{state} = DATA_STATE;
2853 ## reconsume
2854
2855 $self->{ct}->{quirks} = 1;
2856 !!!emit ($self->{ct}); # DOCTYPE
2857
2858 redo A;
2859 } else {
2860 !!!cp (206);
2861 !!!parse-error (type => 'string after SYSTEM');
2862 $self->{ct}->{quirks} = 1;
2863
2864 $self->{state} = BOGUS_DOCTYPE_STATE;
2865 !!!next-input-character;
2866 redo A;
2867 }
2868 } elsif ($self->{state} == DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED_STATE) {
2869 if ($self->{nc} == 0x0022) { # "
2870 !!!cp (207);
2871 $self->{state} = AFTER_DOCTYPE_SYSTEM_IDENTIFIER_STATE;
2872 !!!next-input-character;
2873 redo A;
2874 } elsif ($self->{nc} == 0x003E) { # >
2875 !!!cp (208);
2876 !!!parse-error (type => 'unclosed SYSTEM literal');
2877
2878 $self->{state} = DATA_STATE;
2879 !!!next-input-character;
2880
2881 $self->{ct}->{quirks} = 1;
2882 !!!emit ($self->{ct}); # DOCTYPE
2883
2884 redo A;
2885 } elsif ($self->{nc} == -1) {
2886 !!!cp (209);
2887 !!!parse-error (type => 'unclosed SYSTEM literal');
2888
2889 $self->{state} = DATA_STATE;
2890 ## reconsume
2891
2892 $self->{ct}->{quirks} = 1;
2893 !!!emit ($self->{ct}); # DOCTYPE
2894
2895 redo A;
2896 } else {
2897 !!!cp (210);
2898 $self->{ct}->{sysid} # DOCTYPE
2899 .= chr $self->{nc};
2900 $self->{read_until}->($self->{ct}->{sysid}, q[">],
2901 length $self->{ct}->{sysid});
2902
2903 ## Stay in the state
2904 !!!next-input-character;
2905 redo A;
2906 }
2907 } elsif ($self->{state} == DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED_STATE) {
2908 if ($self->{nc} == 0x0027) { # '
2909 !!!cp (211);
2910 $self->{state} = AFTER_DOCTYPE_SYSTEM_IDENTIFIER_STATE;
2911 !!!next-input-character;
2912 redo A;
2913 } elsif ($self->{nc} == 0x003E) { # >
2914 !!!cp (212);
2915 !!!parse-error (type => 'unclosed SYSTEM literal');
2916
2917 $self->{state} = DATA_STATE;
2918 !!!next-input-character;
2919
2920 $self->{ct}->{quirks} = 1;
2921 !!!emit ($self->{ct}); # DOCTYPE
2922
2923 redo A;
2924 } elsif ($self->{nc} == -1) {
2925 !!!cp (213);
2926 !!!parse-error (type => 'unclosed SYSTEM literal');
2927
2928 $self->{state} = DATA_STATE;
2929 ## reconsume
2930
2931 $self->{ct}->{quirks} = 1;
2932 !!!emit ($self->{ct}); # DOCTYPE
2933
2934 redo A;
2935 } else {
2936 !!!cp (214);
2937 $self->{ct}->{sysid} # DOCTYPE
2938 .= chr $self->{nc};
2939 $self->{read_until}->($self->{ct}->{sysid}, q['>],
2940 length $self->{ct}->{sysid});
2941
2942 ## Stay in the state
2943 !!!next-input-character;
2944 redo A;
2945 }
2946 } elsif ($self->{state} == AFTER_DOCTYPE_SYSTEM_IDENTIFIER_STATE) {
2947 if ($is_space->{$self->{nc}}) {
2948 !!!cp (215);
2949 ## Stay in the state
2950 !!!next-input-character;
2951 redo A;
2952 } elsif ($self->{nc} == 0x003E) { # >
2953 !!!cp (216);
2954 $self->{state} = DATA_STATE;
2955 !!!next-input-character;
2956
2957 !!!emit ($self->{ct}); # DOCTYPE
2958
2959 redo A;
2960 } elsif ($self->{nc} == -1) {
2961 !!!cp (217);
2962 !!!parse-error (type => 'unclosed DOCTYPE');
2963 $self->{state} = DATA_STATE;
2964 ## reconsume
2965
2966 $self->{ct}->{quirks} = 1;
2967 !!!emit ($self->{ct}); # DOCTYPE
2968
2969 redo A;
2970 } else {
2971 !!!cp (218);
2972 !!!parse-error (type => 'string after SYSTEM literal');
2973 #$self->{ct}->{quirks} = 1;
2974
2975 $self->{state} = BOGUS_DOCTYPE_STATE;
2976 !!!next-input-character;
2977 redo A;
2978 }
2979 } elsif ($self->{state} == BOGUS_DOCTYPE_STATE) {
2980 if ($self->{nc} == 0x003E) { # >
2981 !!!cp (219);
2982 $self->{state} = DATA_STATE;
2983 !!!next-input-character;
2984
2985 !!!emit ($self->{ct}); # DOCTYPE
2986
2987 redo A;
2988 } elsif ($self->{nc} == -1) {
2989 !!!cp (220);
2990 $self->{state} = DATA_STATE;
2991 ## reconsume
2992
2993 !!!emit ($self->{ct}); # DOCTYPE
2994
2995 redo A;
2996 } else {
2997 !!!cp (221);
2998 my $s = '';
2999 $self->{read_until}->($s, q[>], 0);
3000
3001 ## Stay in the state
3002 !!!next-input-character;
3003 redo A;
3004 }
3005 } elsif ($self->{state} == CDATA_SECTION_STATE) {
3006 ## NOTE: "CDATA section state" in the state is jointly implemented
3007 ## by three states, |CDATA_SECTION_STATE|, |CDATA_SECTION_MSE1_STATE|,
3008 ## and |CDATA_SECTION_MSE2_STATE|.
3009
3010 if ($self->{nc} == 0x005D) { # ]
3011 !!!cp (221.1);
3012 $self->{state} = CDATA_SECTION_MSE1_STATE;
3013 !!!next-input-character;
3014 redo A;
3015 } elsif ($self->{nc} == -1) {
3016 $self->{state} = DATA_STATE;
3017 !!!next-input-character;
3018 if (length $self->{ct}->{data}) { # character
3019 !!!cp (221.2);
3020 !!!emit ($self->{ct}); # character
3021 } else {
3022 !!!cp (221.3);
3023 ## No token to emit. $self->{ct} is discarded.
3024 }
3025 redo A;
3026 } else {
3027 !!!cp (221.4);
3028 $self->{ct}->{data} .= chr $self->{nc};
3029 $self->{read_until}->($self->{ct}->{data},
3030 q<]>,
3031 length $self->{ct}->{data});
3032
3033 ## Stay in the state.
3034 !!!next-input-character;
3035 redo A;
3036 }
3037
3038 ## ISSUE: "text tokens" in spec.
3039 } elsif ($self->{state} == CDATA_SECTION_MSE1_STATE) {
3040 if ($self->{nc} == 0x005D) { # ]
3041 !!!cp (221.5);
3042 $self->{state} = CDATA_SECTION_MSE2_STATE;
3043 !!!next-input-character;
3044 redo A;
3045 } else {
3046 !!!cp (221.6);
3047 $self->{ct}->{data} .= ']';
3048 $self->{state} = CDATA_SECTION_STATE;
3049 ## Reconsume.
3050 redo A;
3051 }
3052 } elsif ($self->{state} == CDATA_SECTION_MSE2_STATE) {
3053 if ($self->{nc} == 0x003E) { # >
3054 $self->{state} = DATA_STATE;
3055 !!!next-input-character;
3056 if (length $self->{ct}->{data}) { # character
3057 !!!cp (221.7);
3058 !!!emit ($self->{ct}); # character
3059 } else {
3060 !!!cp (221.8);
3061 ## No token to emit. $self->{ct} is discarded.
3062 }
3063 redo A;
3064 } elsif ($self->{nc} == 0x005D) { # ]
3065 !!!cp (221.9); # character
3066 $self->{ct}->{data} .= ']'; ## Add first "]" of "]]]".
3067 ## Stay in the state.
3068 !!!next-input-character;
3069 redo A;
3070 } else {
3071 !!!cp (221.11);
3072 $self->{ct}->{data} .= ']]'; # character
3073 $self->{state} = CDATA_SECTION_STATE;
3074 ## Reconsume.
3075 redo A;
3076 }
3077 } elsif ($self->{state} == ENTITY_STATE) {
3078 if ($is_space->{$self->{nc}} or
3079 {
3080 0x003C => 1, 0x0026 => 1, -1 => 1, # <, &
3081 $self->{entity_add} => 1,
3082 }->{$self->{nc}}) {
3083 !!!cp (1001);
3084 ## Don't consume
3085 ## No error
3086 ## Return nothing.
3087 #
3088 } elsif ($self->{nc} == 0x0023) { # #
3089 !!!cp (999);
3090 $self->{state} = ENTITY_HASH_STATE;
3091 $self->{s_kwd} = '#';
3092 !!!next-input-character;
3093 redo A;
3094 } elsif ((0x0041 <= $self->{nc} and
3095 $self->{nc} <= 0x005A) or # A..Z
3096 (0x0061 <= $self->{nc} and
3097 $self->{nc} <= 0x007A)) { # a..z
3098 !!!cp (998);
3099 require Whatpm::_NamedEntityList;
3100 $self->{state} = ENTITY_NAME_STATE;
3101 $self->{s_kwd} = chr $self->{nc};
3102 $self->{entity__value} = $self->{s_kwd};
3103 $self->{entity__match} = 0;
3104 !!!next-input-character;
3105 redo A;
3106 } else {
3107 !!!cp (1027);
3108 !!!parse-error (type => 'bare ero');
3109 ## Return nothing.
3110 #
3111 }
3112
3113 ## NOTE: No character is consumed by the "consume a character
3114 ## reference" algorithm. In other word, there is an "&" character
3115 ## that does not introduce a character reference, which would be
3116 ## appended to the parent element or the attribute value in later
3117 ## process of the tokenizer.
3118
3119 if ($self->{prev_state} == DATA_STATE) {
3120 !!!cp (997);
3121 $self->{state} = $self->{prev_state};
3122 ## Reconsume.
3123 !!!emit ({type => CHARACTER_TOKEN, data => '&',
3124 line => $self->{line_prev},
3125 column => $self->{column_prev},
3126 });
3127 redo A;
3128 } else {
3129 !!!cp (996);
3130 $self->{ca}->{value} .= '&';
3131 $self->{state} = $self->{prev_state};
3132 ## Reconsume.
3133 redo A;
3134 }
3135 } elsif ($self->{state} == ENTITY_HASH_STATE) {
3136 if ($self->{nc} == 0x0078 or # x
3137 $self->{nc} == 0x0058) { # X
3138 !!!cp (995);
3139 $self->{state} = HEXREF_X_STATE;
3140 $self->{s_kwd} .= chr $self->{nc};
3141 !!!next-input-character;
3142 redo A;
3143 } elsif (0x0030 <= $self->{nc} and
3144 $self->{nc} <= 0x0039) { # 0..9
3145 !!!cp (994);
3146 $self->{state} = NCR_NUM_STATE;
3147 $self->{s_kwd} = $self->{nc} - 0x0030;
3148 !!!next-input-character;
3149 redo A;
3150 } else {
3151 !!!parse-error (type => 'bare nero',
3152 line => $self->{line_prev},
3153 column => $self->{column_prev} - 1);
3154
3155 ## NOTE: According to the spec algorithm, nothing is returned,
3156 ## and then "&#" is appended to the parent element or the attribute
3157 ## value in the later processing.
3158
3159 if ($self->{prev_state} == DATA_STATE) {
3160 !!!cp (1019);
3161 $self->{state} = $self->{prev_state};
3162 ## Reconsume.
3163 !!!emit ({type => CHARACTER_TOKEN,
3164 data => '&#',
3165 line => $self->{line_prev},
3166 column => $self->{column_prev} - 1,
3167 });
3168 redo A;
3169 } else {
3170 !!!cp (993);
3171 $self->{ca}->{value} .= '&#';
3172 $self->{state} = $self->{prev_state};
3173 ## Reconsume.
3174 redo A;
3175 }
3176 }
3177 } elsif ($self->{state} == NCR_NUM_STATE) {
3178 if (0x0030 <= $self->{nc} and
3179 $self->{nc} <= 0x0039) { # 0..9
3180 !!!cp (1012);
3181 $self->{s_kwd} *= 10;
3182 $self->{s_kwd} += $self->{nc} - 0x0030;
3183
3184 ## Stay in the state.
3185 !!!next-input-character;
3186 redo A;
3187 } elsif ($self->{nc} == 0x003B) { # ;
3188 !!!cp (1013);
3189 !!!next-input-character;
3190 #
3191 } else {
3192 !!!cp (1014);
3193 !!!parse-error (type => 'no refc');
3194 ## Reconsume.
3195 #
3196 }
3197
3198 my $code = $self->{s_kwd};
3199 my $l = $self->{line_prev};
3200 my $c = $self->{column_prev};
3201 if ($charref_map->{$code}) {
3202 !!!cp (1015);
3203 !!!parse-error (type => 'invalid character reference',
3204 text => (sprintf 'U+%04X', $code),
3205 line => $l, column => $c);
3206 $code = $charref_map->{$code};
3207 } elsif ($code > 0x10FFFF) {
3208 !!!cp (1016);
3209 !!!parse-error (type => 'invalid character reference',
3210 text => (sprintf 'U-%08X', $code),
3211 line => $l, column => $c);
3212 $code = 0xFFFD;
3213 }
3214
3215 if ($self->{prev_state} == DATA_STATE) {
3216 !!!cp (992);
3217 $self->{state} = $self->{prev_state};
3218 ## Reconsume.
3219 !!!emit ({type => CHARACTER_TOKEN, data => chr $code,
3220 line => $l, column => $c,
3221 });
3222 redo A;
3223 } else {
3224 !!!cp (991);
3225 $self->{ca}->{value} .= chr $code;
3226 $self->{ca}->{has_reference} = 1;
3227 $self->{state} = $self->{prev_state};
3228 ## Reconsume.
3229 redo A;
3230 }
3231 } elsif ($self->{state} == HEXREF_X_STATE) {
3232 if ((0x0030 <= $self->{nc} and $self->{nc} <= 0x0039) or
3233 (0x0041 <= $self->{nc} and $self->{nc} <= 0x0046) or
3234 (0x0061 <= $self->{nc} and $self->{nc} <= 0x0066)) {
3235 # 0..9, A..F, a..f
3236 !!!cp (990);
3237 $self->{state} = HEXREF_HEX_STATE;
3238 $self->{s_kwd} = 0;
3239 ## Reconsume.
3240 redo A;
3241 } else {
3242 !!!parse-error (type => 'bare hcro',
3243 line => $self->{line_prev},
3244 column => $self->{column_prev} - 2);
3245
3246 ## NOTE: According to the spec algorithm, nothing is returned,
3247 ## and then "&#" followed by "X" or "x" is appended to the parent
3248 ## element or the attribute value in the later processing.
3249
3250 if ($self->{prev_state} == DATA_STATE) {
3251 !!!cp (1005);
3252 $self->{state} = $self->{prev_state};
3253 ## Reconsume.
3254 !!!emit ({type => CHARACTER_TOKEN,
3255 data => '&' . $self->{s_kwd},
3256 line => $self->{line_prev},
3257 column => $self->{column_prev} - length $self->{s_kwd},
3258 });
3259 redo A;
3260 } else {
3261 !!!cp (989);
3262 $self->{ca}->{value} .= '&' . $self->{s_kwd};
3263 $self->{state} = $self->{prev_state};
3264 ## Reconsume.
3265 redo A;
3266 }
3267 }
3268 } elsif ($self->{state} == HEXREF_HEX_STATE) {
3269 if (0x0030 <= $self->{nc} and $self->{nc} <= 0x0039) {
3270 # 0..9
3271 !!!cp (1002);
3272 $self->{s_kwd} *= 0x10;
3273 $self->{s_kwd} += $self->{nc} - 0x0030;
3274 ## Stay in the state.
3275 !!!next-input-character;
3276 redo A;
3277 } elsif (0x0061 <= $self->{nc} and
3278 $self->{nc} <= 0x0066) { # a..f
3279 !!!cp (1003);
3280 $self->{s_kwd} *= 0x10;
3281 $self->{s_kwd} += $self->{nc} - 0x0060 + 9;
3282 ## Stay in the state.
3283 !!!next-input-character;
3284 redo A;
3285 } elsif (0x0041 <= $self->{nc} and
3286 $self->{nc} <= 0x0046) { # A..F
3287 !!!cp (1004);
3288 $self->{s_kwd} *= 0x10;
3289 $self->{s_kwd} += $self->{nc} - 0x0040 + 9;
3290 ## Stay in the state.
3291 !!!next-input-character;
3292 redo A;
3293 } elsif ($self->{nc} == 0x003B) { # ;
3294 !!!cp (1006);
3295 !!!next-input-character;
3296 #
3297 } else {
3298 !!!cp (1007);
3299 !!!parse-error (type => 'no refc',
3300 line => $self->{line},
3301 column => $self->{column});
3302 ## Reconsume.
3303 #
3304 }
3305
3306 my $code = $self->{s_kwd};
3307 my $l = $self->{line_prev};
3308 my $c = $self->{column_prev};
3309 if ($charref_map->{$code}) {
3310 !!!cp (1008);
3311 !!!parse-error (type => 'invalid character reference',
3312 text => (sprintf 'U+%04X', $code),
3313 line => $l, column => $c);
3314 $code = $charref_map->{$code};
3315 } elsif ($code > 0x10FFFF) {
3316 !!!cp (1009);
3317 !!!parse-error (type => 'invalid character reference',
3318 text => (sprintf 'U-%08X', $code),
3319 line => $l, column => $c);
3320 $code = 0xFFFD;
3321 }
3322
3323 if ($self->{prev_state} == DATA_STATE) {
3324 !!!cp (988);
3325 $self->{state} = $self->{prev_state};
3326 ## Reconsume.
3327 !!!emit ({type => CHARACTER_TOKEN, data => chr $code,
3328 line => $l, column => $c,
3329 });
3330 redo A;
3331 } else {
3332 !!!cp (987);
3333 $self->{ca}->{value} .= chr $code;
3334 $self->{ca}->{has_reference} = 1;
3335 $self->{state} = $self->{prev_state};
3336 ## Reconsume.
3337 redo A;
3338 }
3339 } elsif ($self->{state} == ENTITY_NAME_STATE) {
3340 if (length $self->{s_kwd} < 30 and
3341 ## NOTE: Some number greater than the maximum length of entity name
3342 ((0x0041 <= $self->{nc} and # a
3343 $self->{nc} <= 0x005A) or # x
3344 (0x0061 <= $self->{nc} and # a
3345 $self->{nc} <= 0x007A) or # z
3346 (0x0030 <= $self->{nc} and # 0
3347 $self->{nc} <= 0x0039) or # 9
3348 $self->{nc} == 0x003B)) { # ;
3349 our $EntityChar;
3350 $self->{s_kwd} .= chr $self->{nc};
3351 if (defined $EntityChar->{$self->{s_kwd}}) {
3352 if ($self->{nc} == 0x003B) { # ;
3353 !!!cp (1020);
3354 $self->{entity__value} = $EntityChar->{$self->{s_kwd}};
3355 $self->{entity__match} = 1;
3356 !!!next-input-character;
3357 #
3358 } else {
3359 !!!cp (1021);
3360 $self->{entity__value} = $EntityChar->{$self->{s_kwd}};
3361 $self->{entity__match} = -1;
3362 ## Stay in the state.
3363 !!!next-input-character;
3364 redo A;
3365 }
3366 } else {
3367 !!!cp (1022);
3368 $self->{entity__value} .= chr $self->{nc};
3369 $self->{entity__match} *= 2;
3370 ## Stay in the state.
3371 !!!next-input-character;
3372 redo A;
3373 }
3374 }
3375
3376 my $data;
3377 my $has_ref;
3378 if ($self->{entity__match} > 0) {
3379 !!!cp (1023);
3380 $data = $self->{entity__value};
3381 $has_ref = 1;
3382 #
3383 } elsif ($self->{entity__match} < 0) {
3384 !!!parse-error (type => 'no refc');
3385 if ($self->{prev_state} != DATA_STATE and # in attribute
3386 $self->{entity__match} < -1) {
3387 !!!cp (1024);
3388 $data = '&' . $self->{s_kwd};
3389 #
3390 } else {
3391 !!!cp (1025);
3392 $data = $self->{entity__value};
3393 $has_ref = 1;
3394 #
3395 }
3396 } else {
3397 !!!cp (1026);
3398 !!!parse-error (type => 'bare ero',
3399 line => $self->{line_prev},
3400 column => $self->{column_prev} - length $self->{s_kwd});
3401 $data = '&' . $self->{s_kwd};
3402 #
3403 }
3404
3405 ## NOTE: In these cases, when a character reference is found,
3406 ## it is consumed and a character token is returned, or, otherwise,
3407 ## nothing is consumed and returned, according to the spec algorithm.
3408 ## In this implementation, anything that has been examined by the
3409 ## tokenizer is appended to the parent element or the attribute value
3410 ## as string, either literal string when no character reference or
3411 ## entity-replaced string otherwise, in this stage, since any characters
3412 ## that would not be consumed are appended in the data state or in an
3413 ## appropriate attribute value state anyway.
3414
3415 if ($self->{prev_state} == DATA_STATE) {
3416 !!!cp (986);
3417 $self->{state} = $self->{prev_state};
3418 ## Reconsume.
3419 !!!emit ({type => CHARACTER_TOKEN,
3420 data => $data,
3421 line => $self->{line_prev},
3422 column => $self->{column_prev} + 1 - length $self->{s_kwd},
3423 });
3424 redo A;
3425 } else {
3426 !!!cp (985);
3427 $self->{ca}->{value} .= $data;
3428 $self->{ca}->{has_reference} = 1 if $has_ref;
3429 $self->{state} = $self->{prev_state};
3430 ## Reconsume.
3431 redo A;
3432 }
3433 } else {
3434 die "$0: $self->{state}: Unknown state";
3435 }
3436 } # A
3437
3438 die "$0: _get_next_token: unexpected case";
3439 } # _get_next_token
3440
3441 sub _initialize_tree_constructor ($) {
3442 my $self = shift;
3443 ## NOTE: $self->{document} MUST be specified before this method is called
3444 $self->{document}->strict_error_checking (0);
3445 ## TODO: Turn mutation events off # MUST
3446 ## TODO: Turn loose Document option (manakai extension) on
3447 $self->{document}->manakai_is_html (1); # MUST
3448 $self->{document}->set_user_data (manakai_source_line => 1);
3449 $self->{document}->set_user_data (manakai_source_column => 1);
3450 } # _initialize_tree_constructor
3451
3452 sub _terminate_tree_constructor ($) {
3453 my $self = shift;
3454 $self->{document}->strict_error_checking (1);
3455 ## TODO: Turn mutation events on
3456 } # _terminate_tree_constructor
3457
3458 ## ISSUE: Should append_child (for example) in script executed in tree construction stage fire mutation events?
3459
3460 { # tree construction stage
3461 my $token;
3462
3463 sub _construct_tree ($) {
3464 my ($self) = @_;
3465
3466 ## When an interactive UA render the $self->{document} available
3467 ## to the user, or when it begin accepting user input, are
3468 ## not defined.
3469
3470 !!!next-token;
3471
3472 undef $self->{form_element};
3473 undef $self->{head_element};
3474 undef $self->{head_element_inserted};
3475 $self->{open_elements} = [];
3476 undef $self->{inner_html_node};
3477
3478 ## NOTE: The "initial" insertion mode.
3479 $self->_tree_construction_initial; # MUST
3480
3481 ## NOTE: The "before html" insertion mode.
3482 $self->_tree_construction_root_element;
3483 $self->{insertion_mode} = BEFORE_HEAD_IM;
3484
3485 ## NOTE: The "before head" insertion mode and so on.
3486 $self->_tree_construction_main;
3487 } # _construct_tree
3488
3489 sub _tree_construction_initial ($) {
3490 my $self = shift;
3491
3492 ## NOTE: "initial" insertion mode
3493
3494 INITIAL: {
3495 if ($token->{type} == DOCTYPE_TOKEN) {
3496 ## NOTE: Conformance checkers MAY, instead of reporting "not HTML5"
3497 ## error, switch to a conformance checking mode for another
3498 ## language.
3499 my $doctype_name = $token->{name};
3500 $doctype_name = '' unless defined $doctype_name;
3501 $doctype_name =~ tr/a-z/A-Z/; # ASCII case-insensitive
3502 if (not defined $token->{name} or # <!DOCTYPE>
3503 defined $token->{sysid}) {
3504 !!!cp ('t1');
3505 !!!parse-error (type => 'not HTML5', token => $token);
3506 } elsif ($doctype_name ne 'HTML') {
3507 !!!cp ('t2');
3508 !!!parse-error (type => 'not HTML5', token => $token);
3509 } elsif (defined $token->{pubid}) {
3510 if ($token->{pubid} eq 'XSLT-compat') {
3511 !!!cp ('t1.2');
3512 !!!parse-error (type => 'XSLT-compat', token => $token,
3513 level => $self->{level}->{should});
3514 } else {
3515 !!!parse-error (type => 'not HTML5', token => $token);
3516 }
3517 } else {
3518 !!!cp ('t3');
3519 #
3520 }
3521
3522 my $doctype = $self->{document}->create_document_type_definition
3523 ($token->{name}); ## ISSUE: If name is missing (e.g. <!DOCTYPE>)?
3524 ## NOTE: Default value for both |public_id| and |system_id| attributes
3525 ## are empty strings, so that we don't set any value in missing cases.
3526 $doctype->public_id ($token->{pubid}) if defined $token->{pubid};
3527 $doctype->system_id ($token->{sysid}) if defined $token->{sysid};
3528 ## NOTE: Other DocumentType attributes are null or empty lists.
3529 ## ISSUE: internalSubset = null??
3530 $self->{document}->append_child ($doctype);
3531
3532 if ($token->{quirks} or $doctype_name ne 'HTML') {
3533 !!!cp ('t4');
3534 $self->{document}->manakai_compat_mode ('quirks');
3535 } elsif (defined $token->{pubid}) {
3536 my $pubid = $token->{pubid};
3537 $pubid =~ tr/a-z/A-z/;
3538 my $prefix = [
3539 "+//SILMARIL//DTD HTML PRO V0R11 19970101//",
3540 "-//ADVASOFT LTD//DTD HTML 3.0 ASWEDIT + EXTENSIONS//",
3541 "-//AS//DTD HTML 3.0 ASWEDIT + EXTENSIONS//",
3542 "-//IETF//DTD HTML 2.0 LEVEL 1//",
3543 "-//IETF//DTD HTML 2.0 LEVEL 2//",
3544 "-//IETF//DTD HTML 2.0 STRICT LEVEL 1//",
3545 "-//IETF//DTD HTML 2.0 STRICT LEVEL 2//",
3546 "-//IETF//DTD HTML 2.0 STRICT//",
3547 "-//IETF//DTD HTML 2.0//",
3548 "-//IETF//DTD HTML 2.1E//",
3549 "-//IETF//DTD HTML 3.0//",
3550 "-//IETF//DTD HTML 3.2 FINAL//",
3551 "-//IETF//DTD HTML 3.2//",
3552 "-//IETF//DTD HTML 3//",
3553 "-//IETF//DTD HTML LEVEL 0//",
3554 "-//IETF//DTD HTML LEVEL 1//",
3555 "-//IETF//DTD HTML LEVEL 2//",
3556 "-//IETF//DTD HTML LEVEL 3//",
3557 "-//IETF//DTD HTML STRICT LEVEL 0//",
3558 "-//IETF//DTD HTML STRICT LEVEL 1//",
3559 "-//IETF//DTD HTML STRICT LEVEL 2//",
3560 "-//IETF//DTD HTML STRICT LEVEL 3//",
3561 "-//IETF//DTD HTML STRICT//",
3562 "-//IETF//DTD HTML//",
3563 "-//METRIUS//DTD METRIUS PRESENTATIONAL//",
3564 "-//MICROSOFT//DTD INTERNET EXPLORER 2.0 HTML STRICT//",
3565 "-//MICROSOFT//DTD INTERNET EXPLORER 2.0 HTML//",
3566 "-//MICROSOFT//DTD INTERNET EXPLORER 2.0 TABLES//",
3567 "-//MICROSOFT//DTD INTERNET EXPLORER 3.0 HTML STRICT//",
3568 "-//MICROSOFT//DTD INTERNET EXPLORER 3.0 HTML//",
3569 "-//MICROSOFT//DTD INTERNET EXPLORER 3.0 TABLES//",
3570 "-//NETSCAPE COMM. CORP.//DTD HTML//",
3571 "-//NETSCAPE COMM. CORP.//DTD STRICT HTML//",
3572 "-//O'REILLY AND ASSOCIATES//DTD HTML 2.0//",
3573 "-//O'REILLY AND ASSOCIATES//DTD HTML EXTENDED 1.0//",
3574 "-//O'REILLY AND ASSOCIATES//DTD HTML EXTENDED RELAXED 1.0//",
3575 "-//SOFTQUAD SOFTWARE//DTD HOTMETAL PRO 6.0::19990601::EXTENSIONS TO HTML 4.0//",
3576 "-//SOFTQUAD//DTD HOTMETAL PRO 4.0::19971010::EXTENSIONS TO HTML 4.0//",
3577 "-//SPYGLASS//DTD HTML 2.0 EXTENDED//",
3578 "-//SQ//DTD HTML 2.0 HOTMETAL + EXTENSIONS//",
3579 "-//SUN MICROSYSTEMS CORP.//DTD HOTJAVA HTML//",
3580 "-//SUN MICROSYSTEMS CORP.//DTD HOTJAVA STRICT HTML//",
3581 "-//W3C//DTD HTML 3 1995-03-24//",
3582 "-//W3C//DTD HTML 3.2 DRAFT//",
3583 "-//W3C//DTD HTML 3.2 FINAL//",
3584 "-//W3C//DTD HTML 3.2//",
3585 "-//W3C//DTD HTML 3.2S DRAFT//",
3586 "-//W3C//DTD HTML 4.0 FRAMESET//",
3587 "-//W3C//DTD HTML 4.0 TRANSITIONAL//",
3588 "-//W3C//DTD HTML EXPERIMETNAL 19960712//",
3589 "-//W3C//DTD HTML EXPERIMENTAL 970421//",
3590 "-//W3C//DTD W3 HTML//",
3591 "-//W3O//DTD W3 HTML 3.0//",
3592 "-//WEBTECHS//DTD MOZILLA HTML 2.0//",
3593 "-//WEBTECHS//DTD MOZILLA HTML//",
3594 ]; # $prefix
3595 my $match;
3596 for (@$prefix) {
3597 if (substr ($prefix, 0, length $_) eq $_) {
3598 $match = 1;
3599 last;
3600 }
3601 }
3602 if ($match or
3603 $pubid eq "-//W3O//DTD W3 HTML STRICT 3.0//EN//" or
3604 $pubid eq "-/W3C/DTD HTML 4.0 TRANSITIONAL/EN" or
3605 $pubid eq "HTML") {
3606 !!!cp ('t5');
3607 $self->{document}->manakai_compat_mode ('quirks');
3608 } elsif ($pubid =~ m[^-//W3C//DTD HTML 4.01 FRAMESET//] or
3609 $pubid =~ m[^-//W3C//DTD HTML 4.01 TRANSITIONAL//]) {
3610 if (defined $token->{sysid}) {
3611 !!!cp ('t6');
3612 $self->{document}->manakai_compat_mode ('quirks');
3613 } else {
3614 !!!cp ('t7');
3615 $self->{document}->manakai_compat_mode ('limited quirks');
3616 }
3617 } elsif ($pubid =~ m[^-//W3C//DTD XHTML 1.0 FRAMESET//] or
3618 $pubid =~ m[^-//W3C//DTD XHTML 1.0 TRANSITIONAL//]) {
3619 !!!cp ('t8');
3620 $self->{document}->manakai_compat_mode ('limited quirks');
3621 } else {
3622 !!!cp ('t9');
3623 }
3624 } else {
3625 !!!cp ('t10');
3626 }
3627 if (defined $token->{sysid}) {
3628 my $sysid = $token->{sysid};
3629 $sysid =~ tr/A-Z/a-z/;
3630 if ($sysid eq "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd") {
3631 ## NOTE: Ensure that |PUBLIC "(limited quirks)" "(quirks)"| is
3632 ## marked as quirks.
3633 $self->{document}->manakai_compat_mode ('quirks');
3634 !!!cp ('t11');
3635 } else {
3636 !!!cp ('t12');
3637 }
3638 } else {
3639 !!!cp ('t13');
3640 }
3641
3642 ## Go to the "before html" insertion mode.
3643 !!!next-token;
3644 return;
3645 } elsif ({
3646 START_TAG_TOKEN, 1,
3647 END_TAG_TOKEN, 1,
3648 END_OF_FILE_TOKEN, 1,
3649 }->{$token->{type}}) {
3650 !!!cp ('t14');
3651 !!!parse-error (type => 'no DOCTYPE', token => $token);
3652 $self->{document}->manakai_compat_mode ('quirks');
3653 ## Go to the "before html" insertion mode.
3654 ## reprocess
3655 !!!ack-later;
3656 return;
3657 } elsif ($token->{type} == CHARACTER_TOKEN) {
3658 if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
3659 ## Ignore the token
3660
3661 unless (length $token->{data}) {
3662 !!!cp ('t15');
3663 ## Stay in the insertion mode.
3664 !!!next-token;
3665 redo INITIAL;
3666 } else {
3667 !!!cp ('t16');
3668 }
3669 } else {
3670 !!!cp ('t17');
3671 }
3672
3673 !!!parse-error (type => 'no DOCTYPE', token => $token);
3674 $self->{document}->manakai_compat_mode ('quirks');
3675 ## Go to the "before html" insertion mode.
3676 ## reprocess
3677 return;
3678 } elsif ($token->{type} == COMMENT_TOKEN) {
3679 !!!cp ('t18');
3680 my $comment = $self->{document}->create_comment ($token->{data});
3681 $self->{document}->append_child ($comment);
3682
3683 ## Stay in the insertion mode.
3684 !!!next-token;
3685 redo INITIAL;
3686 } else {
3687 die "$0: $token->{type}: Unknown token type";
3688 }
3689 } # INITIAL
3690
3691 die "$0: _tree_construction_initial: This should be never reached";
3692 } # _tree_construction_initial
3693
3694 sub _tree_construction_root_element ($) {
3695 my $self = shift;
3696
3697 ## NOTE: "before html" insertion mode.
3698
3699 B: {
3700 if ($token->{type} == DOCTYPE_TOKEN) {
3701 !!!cp ('t19');
3702 !!!parse-error (type => 'in html:#DOCTYPE', token => $token);
3703 ## Ignore the token
3704 ## Stay in the insertion mode.
3705 !!!next-token;
3706 redo B;
3707 } elsif ($token->{type} == COMMENT_TOKEN) {
3708 !!!cp ('t20');
3709 my $comment = $self->{document}->create_comment ($token->{data});
3710 $self->{document}->append_child ($comment);
3711 ## Stay in the insertion mode.
3712 !!!next-token;
3713 redo B;
3714 } elsif ($token->{type} == CHARACTER_TOKEN) {
3715 if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
3716 ## Ignore the token.
3717
3718 unless (length $token->{data}) {
3719 !!!cp ('t21');
3720 ## Stay in the insertion mode.
3721 !!!next-token;
3722 redo B;
3723 } else {
3724 !!!cp ('t22');
3725 }
3726 } else {
3727 !!!cp ('t23');
3728 }
3729
3730 $self->{application_cache_selection}->(undef);
3731
3732 #
3733 } elsif ($token->{type} == START_TAG_TOKEN) {
3734 if ($token->{tag_name} eq 'html') {
3735 my $root_element;
3736 !!!create-element ($root_element, $HTML_NS, $token->{tag_name}, $token->{attributes}, $token);
3737 $self->{document}->append_child ($root_element);
3738 push @{$self->{open_elements}},
3739 [$root_element, $el_category->{html}];
3740
3741 if ($token->{attributes}->{manifest}) {
3742 !!!cp ('t24');
3743 $self->{application_cache_selection}
3744 ->($token->{attributes}->{manifest}->{value});
3745 ## ISSUE: Spec is unclear on relative references.
3746 ## According to Hixie (#whatwg 2008-03-19), it should be
3747 ## resolved against the base URI of the document in HTML
3748 ## or xml:base of the element in XHTML.
3749 } else {
3750 !!!cp ('t25');
3751 $self->{application_cache_selection}->(undef);
3752 }
3753
3754 !!!nack ('t25c');
3755
3756 !!!next-token;
3757 return; ## Go to the "before head" insertion mode.
3758 } else {
3759 !!!cp ('t25.1');
3760 #
3761 }
3762 } elsif ({
3763 END_TAG_TOKEN, 1,
3764 END_OF_FILE_TOKEN, 1,
3765 }->{$token->{type}}) {
3766 !!!cp ('t26');
3767 #
3768 } else {
3769 die "$0: $token->{type}: Unknown token type";
3770 }
3771
3772 my $root_element;
3773 !!!create-element ($root_element, $HTML_NS, 'html',, $token);
3774 $self->{document}->append_child ($root_element);
3775 push @{$self->{open_elements}}, [$root_element, $el_category->{html}];
3776
3777 $self->{application_cache_selection}->(undef);
3778
3779 ## NOTE: Reprocess the token.
3780 !!!ack-later;
3781 return; ## Go to the "before head" insertion mode.
3782 } # B
3783
3784 die "$0: _tree_construction_root_element: This should never be reached";
3785 } # _tree_construction_root_element
3786
3787 sub _reset_insertion_mode ($) {
3788 my $self = shift;
3789
3790 ## Step 1
3791 my $last;
3792
3793 ## Step 2
3794 my $i = -1;
3795 my $node = $self->{open_elements}->[$i];
3796
3797 ## Step 3
3798 S3: {
3799 if ($self->{open_elements}->[0]->[0] eq $node->[0]) {
3800 $last = 1;
3801 if (defined $self->{inner_html_node}) {
3802 !!!cp ('t28');
3803 $node = $self->{inner_html_node};
3804 } else {
3805 die "_reset_insertion_mode: t27";
3806 }
3807 }
3808
3809 ## Step 4..14
3810 my $new_mode;
3811 if ($node->[1] & FOREIGN_EL) {
3812 !!!cp ('t28.1');
3813 ## NOTE: Strictly spaking, the line below only applies to MathML and
3814 ## SVG elements. Currently the HTML syntax supports only MathML and
3815 ## SVG elements as foreigners.
3816 $new_mode = IN_BODY_IM | IN_FOREIGN_CONTENT_IM;
3817 } elsif ($node->[1] & TABLE_CELL_EL) {
3818 if ($last) {
3819 !!!cp ('t28.2');
3820 #
3821 } else {
3822 !!!cp ('t28.3');
3823 $new_mode = IN_CELL_IM;
3824 }
3825 } else {
3826 !!!cp ('t28.4');
3827 $new_mode = {
3828 select => IN_SELECT_IM,
3829 ## NOTE: |option| and |optgroup| do not set
3830 ## insertion mode to "in select" by themselves.
3831 tr => IN_ROW_IM,
3832 tbody => IN_TABLE_BODY_IM,
3833 thead => IN_TABLE_BODY_IM,
3834 tfoot => IN_TABLE_BODY_IM,
3835 caption => IN_CAPTION_IM,
3836 colgroup => IN_COLUMN_GROUP_IM,
3837 table => IN_TABLE_IM,
3838 head => IN_BODY_IM, # not in head!
3839 body => IN_BODY_IM,
3840 frameset => IN_FRAMESET_IM,
3841 }->{$node->[0]->manakai_local_name};
3842 }
3843 $self->{insertion_mode} = $new_mode and return if defined $new_mode;
3844
3845 ## Step 15
3846 if ($node->[1] & HTML_EL) {
3847 unless (defined $self->{head_element}) {
3848 !!!cp ('t29');
3849 $self->{insertion_mode} = BEFORE_HEAD_IM;
3850 } else {
3851 ## ISSUE: Can this state be reached?
3852 !!!cp ('t30');
3853 $self->{insertion_mode} = AFTER_HEAD_IM;
3854 }
3855 return;
3856 } else {
3857 !!!cp ('t31');
3858 }
3859
3860 ## Step 16
3861 $self->{insertion_mode} = IN_BODY_IM and return if $last;
3862
3863 ## Step 17
3864 $i--;
3865 $node = $self->{open_elements}->[$i];
3866
3867 ## Step 18
3868 redo S3;
3869 } # S3
3870
3871 die "$0: _reset_insertion_mode: This line should never be reached";
3872 } # _reset_insertion_mode
3873
3874 sub _tree_construction_main ($) {
3875 my $self = shift;
3876
3877 my $active_formatting_elements = [];
3878
3879 my $reconstruct_active_formatting_elements = sub { # MUST
3880 my $insert = shift;
3881
3882 ## Step 1
3883 return unless @$active_formatting_elements;
3884
3885 ## Step 3
3886 my $i = -1;
3887 my $entry = $active_formatting_elements->[$i];
3888
3889 ## Step 2
3890 return if $entry->[0] eq '#marker';
3891 for (@{$self->{open_elements}}) {
3892 if ($entry->[0] eq $_->[0]) {
3893 !!!cp ('t32');
3894 return;
3895 }
3896 }
3897
3898 S4: {
3899 ## Step 4
3900 last S4 if $active_formatting_elements->[0]->[0] eq $entry->[0];
3901
3902 ## Step 5
3903 $i--;
3904 $entry = $active_formatting_elements->[$i];
3905
3906 ## Step 6
3907 if ($entry->[0] eq '#marker') {
3908 !!!cp ('t33_1');
3909 #
3910 } else {
3911 my $in_open_elements;
3912 OE: for (@{$self->{open_elements}}) {
3913 if ($entry->[0] eq $_->[0]) {
3914 !!!cp ('t33');
3915 $in_open_elements = 1;
3916 last OE;
3917 }
3918 }
3919 if ($in_open_elements) {
3920 !!!cp ('t34');
3921 #
3922 } else {
3923 ## NOTE: <!DOCTYPE HTML><p><b><i><u></p> <p>X
3924 !!!cp ('t35');
3925 redo S4;
3926 }
3927 }
3928
3929 ## Step 7
3930 $i++;
3931 $entry = $active_formatting_elements->[$i];
3932 } # S4
3933
3934 S7: {
3935 ## Step 8
3936 my $clone = [$entry->[0]->clone_node (0), $entry->[1]];
3937
3938 ## Step 9
3939 $insert->($clone->[0]);
3940 push @{$self->{open_elements}}, $clone;
3941
3942 ## Step 10
3943 $active_formatting_elements->[$i] = $self->{open_elements}->[-1];
3944
3945 ## Step 11
3946 unless ($clone->[0] eq $active_formatting_elements->[-1]->[0]) {
3947 !!!cp ('t36');
3948 ## Step 7'
3949 $i++;
3950 $entry = $active_formatting_elements->[$i];
3951
3952 redo S7;
3953 }
3954
3955 !!!cp ('t37');
3956 } # S7
3957 }; # $reconstruct_active_formatting_elements
3958
3959 my $clear_up_to_marker = sub {
3960 for (reverse 0..$#$active_formatting_elements) {
3961 if ($active_formatting_elements->[$_]->[0] eq '#marker') {
3962 !!!cp ('t38');
3963 splice @$active_formatting_elements, $_;
3964 return;
3965 }
3966 }
3967
3968 !!!cp ('t39');
3969 }; # $clear_up_to_marker
3970
3971 my $insert;
3972
3973 my $parse_rcdata = sub ($) {
3974 my ($content_model_flag) = @_;
3975
3976 ## Step 1
3977 my $start_tag_name = $token->{tag_name};
3978 my $el;
3979 !!!create-element ($el, $HTML_NS, $start_tag_name, $token->{attributes}, $token);
3980
3981 ## Step 2
3982 $insert->($el);
3983
3984 ## Step 3
3985 $self->{content_model} = $content_model_flag; # CDATA or RCDATA
3986 delete $self->{escape}; # MUST
3987
3988 ## Step 4
3989 my $text = '';
3990 !!!nack ('t40.1');
3991 !!!next-token;
3992 while ($token->{type} == CHARACTER_TOKEN) { # or until stop tokenizing
3993 !!!cp ('t40');
3994 $text .= $token->{data};
3995 !!!next-token;
3996 }
3997
3998 ## Step 5
3999 if (length $text) {
4000 !!!cp ('t41');
4001 my $text = $self->{document}->create_text_node ($text);
4002 $el->append_child ($text);
4003 }
4004
4005 ## Step 6
4006 $self->{content_model} = PCDATA_CONTENT_MODEL;
4007
4008 ## Step 7
4009 if ($token->{type} == END_TAG_TOKEN and
4010 $token->{tag_name} eq $start_tag_name) {
4011 !!!cp ('t42');
4012 ## Ignore the token
4013 } else {
4014 ## NOTE: An end-of-file token.
4015 if ($content_model_flag == CDATA_CONTENT_MODEL) {
4016 !!!cp ('t43');
4017 !!!parse-error (type => 'in CDATA:#eof', token => $token);
4018 } elsif ($content_model_flag == RCDATA_CONTENT_MODEL) {
4019 !!!cp ('t44');
4020 !!!parse-error (type => 'in RCDATA:#eof', token => $token);
4021 } else {
4022 die "$0: $content_model_flag in parse_rcdata";
4023 }
4024 }
4025 !!!next-token;
4026 }; # $parse_rcdata
4027
4028 my $script_start_tag = sub () {
4029 my $script_el;
4030 !!!create-element ($script_el, $HTML_NS, 'script', $token->{attributes}, $token);
4031 ## TODO: mark as "parser-inserted"
4032
4033 $self->{content_model} = CDATA_CONTENT_MODEL;
4034 delete $self->{escape}; # MUST
4035
4036 my $text = '';
4037 !!!nack ('t45.1');
4038 !!!next-token;
4039 while ($token->{type} == CHARACTER_TOKEN) {
4040 !!!cp ('t45');
4041 $text .= $token->{data};
4042 !!!next-token;
4043 } # stop if non-character token or tokenizer stops tokenising
4044 if (length $text) {
4045 !!!cp ('t46');
4046 $script_el->manakai_append_text ($text);
4047 }
4048
4049 $self->{content_model} = PCDATA_CONTENT_MODEL;
4050
4051 if ($token->{type} == END_TAG_TOKEN and
4052 $token->{tag_name} eq 'script') {
4053 !!!cp ('t47');
4054 ## Ignore the token
4055 } else {
4056 !!!cp ('t48');
4057 !!!parse-error (type => 'in CDATA:#eof', token => $token);
4058 ## ISSUE: And ignore?
4059 ## TODO: mark as "already executed"
4060 }
4061
4062 if (defined $self->{inner_html_node}) {
4063 !!!cp ('t49');
4064 ## TODO: mark as "already executed"
4065 } else {
4066 !!!cp ('t50');
4067 ## TODO: $old_insertion_point = current insertion point
4068 ## TODO: insertion point = just before the next input character
4069
4070 $insert->($script_el);
4071
4072 ## TODO: insertion point = $old_insertion_point (might be "undefined")
4073
4074 ## TODO: if there is a script that will execute as soon as the parser resume, then...
4075 }
4076
4077 !!!next-token;
4078 }; # $script_start_tag
4079
4080 ## NOTE: $open_tables->[-1]->[0] is the "current table" element node.
4081 ## NOTE: $open_tables->[-1]->[1] is the "tainted" flag.
4082 ## NOTE: $open_tables->[-1]->[2] is set false when non-Text node inserted.
4083 my $open_tables = [[$self->{open_elements}->[0]->[0]]];
4084
4085 my $formatting_end_tag = sub {
4086 my $end_tag_token = shift;
4087 my $tag_name = $end_tag_token->{tag_name};
4088
4089 ## NOTE: The adoption agency algorithm (AAA).
4090
4091 FET: {
4092 ## Step 1
4093 my $formatting_element;
4094 my $formatting_element_i_in_active;
4095 AFE: for (reverse 0..$#$active_formatting_elements) {
4096 if ($active_formatting_elements->[$_]->[0] eq '#marker') {
4097 !!!cp ('t52');
4098 last AFE;
4099 } elsif ($active_formatting_elements->[$_]->[0]->manakai_local_name
4100 eq $tag_name) {
4101 !!!cp ('t51');
4102 $formatting_element = $active_formatting_elements->[$_];
4103 $formatting_element_i_in_active = $_;
4104 last AFE;
4105 }
4106 } # AFE
4107 unless (defined $formatting_element) {
4108 !!!cp ('t53');
4109 !!!parse-error (type => 'unmatched end tag', text => $tag_name, token => $end_tag_token);
4110 ## Ignore the token
4111 !!!next-token;
4112 return;
4113 }
4114 ## has an element in scope
4115 my $in_scope = 1;
4116 my $formatting_element_i_in_open;
4117 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4118 my $node = $self->{open_elements}->[$_];
4119 if ($node->[0] eq $formatting_element->[0]) {
4120 if ($in_scope) {
4121 !!!cp ('t54');
4122 $formatting_element_i_in_open = $_;
4123 last INSCOPE;
4124 } else { # in open elements but not in scope
4125 !!!cp ('t55');
4126 !!!parse-error (type => 'unmatched end tag',
4127 text => $token->{tag_name},
4128 token => $end_tag_token);
4129 ## Ignore the token
4130 !!!next-token;
4131 return;
4132 }
4133 } elsif ($node->[1] & SCOPING_EL) {
4134 !!!cp ('t56');
4135 $in_scope = 0;
4136 }
4137 } # INSCOPE
4138 unless (defined $formatting_element_i_in_open) {
4139 !!!cp ('t57');
4140 !!!parse-error (type => 'unmatched end tag',
4141 text => $token->{tag_name},
4142 token => $end_tag_token);
4143 pop @$active_formatting_elements; # $formatting_element
4144 !!!next-token; ## TODO: ok?
4145 return;
4146 }
4147 if (not $self->{open_elements}->[-1]->[0] eq $formatting_element->[0]) {
4148 !!!cp ('t58');
4149 !!!parse-error (type => 'not closed',
4150 text => $self->{open_elements}->[-1]->[0]
4151 ->manakai_local_name,
4152 token => $end_tag_token);
4153 }
4154
4155 ## Step 2
4156 my $furthest_block;
4157 my $furthest_block_i_in_open;
4158 OE: for (reverse 0..$#{$self->{open_elements}}) {
4159 my $node = $self->{open_elements}->[$_];
4160 if (not ($node->[1] & FORMATTING_EL) and
4161 #not $phrasing_category->{$node->[1]} and
4162 ($node->[1] & SPECIAL_EL or
4163 $node->[1] & SCOPING_EL)) { ## Scoping is redundant, maybe
4164 !!!cp ('t59');
4165 $furthest_block = $node;
4166 $furthest_block_i_in_open = $_;
4167 ## NOTE: The topmost (eldest) node.
4168 } elsif ($node->[0] eq $formatting_element->[0]) {
4169 !!!cp ('t60');
4170 last OE;
4171 }
4172 } # OE
4173
4174 ## Step 3
4175 unless (defined $furthest_block) { # MUST
4176 !!!cp ('t61');
4177 splice @{$self->{open_elements}}, $formatting_element_i_in_open;
4178 splice @$active_formatting_elements, $formatting_element_i_in_active, 1;
4179 !!!next-token;
4180 return;
4181 }
4182
4183 ## Step 4
4184 my $common_ancestor_node = $self->{open_elements}->[$formatting_element_i_in_open - 1];
4185
4186 ## Step 5
4187 my $furthest_block_parent = $furthest_block->[0]->parent_node;
4188 if (defined $furthest_block_parent) {
4189 !!!cp ('t62');
4190 $furthest_block_parent->remove_child ($furthest_block->[0]);
4191 }
4192
4193 ## Step 6
4194 my $bookmark_prev_el
4195 = $active_formatting_elements->[$formatting_element_i_in_active - 1]
4196 ->[0];
4197
4198 ## Step 7
4199 my $node = $furthest_block;
4200 my $node_i_in_open = $furthest_block_i_in_open;
4201 my $last_node = $furthest_block;
4202 S7: {
4203 ## Step 1
4204 $node_i_in_open--;
4205 $node = $self->{open_elements}->[$node_i_in_open];
4206
4207 ## Step 2
4208 my $node_i_in_active;
4209 S7S2: {
4210 for (reverse 0..$#$active_formatting_elements) {
4211 if ($active_formatting_elements->[$_]->[0] eq $node->[0]) {
4212 !!!cp ('t63');
4213 $node_i_in_active = $_;
4214 last S7S2;
4215 }
4216 }
4217 splice @{$self->{open_elements}}, $node_i_in_open, 1;
4218 redo S7;
4219 } # S7S2
4220
4221 ## Step 3
4222 last S7 if $node->[0] eq $formatting_element->[0];
4223
4224 ## Step 4
4225 if ($last_node->[0] eq $furthest_block->[0]) {
4226 !!!cp ('t64');
4227 $bookmark_prev_el = $node->[0];
4228 }
4229
4230 ## Step 5
4231 if ($node->[0]->has_child_nodes ()) {
4232 !!!cp ('t65');
4233 my $clone = [$node->[0]->clone_node (0), $node->[1]];
4234 $active_formatting_elements->[$node_i_in_active] = $clone;
4235 $self->{open_elements}->[$node_i_in_open] = $clone;
4236 $node = $clone;
4237 }
4238
4239 ## Step 6
4240 $node->[0]->append_child ($last_node->[0]);
4241
4242 ## Step 7
4243 $last_node = $node;
4244
4245 ## Step 8
4246 redo S7;
4247 } # S7
4248
4249 ## Step 8
4250 if ($common_ancestor_node->[1] & TABLE_ROWS_EL) {
4251 my $foster_parent_element;
4252 my $next_sibling;
4253 OE: for (reverse 0..$#{$self->{open_elements}}) {
4254 if ($self->{open_elements}->[$_]->[1] & TABLE_EL) {
4255 my $parent = $self->{open_elements}->[$_]->[0]->parent_node;
4256 if (defined $parent and $parent->node_type == 1) {
4257 !!!cp ('t65.1');
4258 $foster_parent_element = $parent;
4259 $next_sibling = $self->{open_elements}->[$_]->[0];
4260 } else {
4261 !!!cp ('t65.2');
4262 $foster_parent_element
4263 = $self->{open_elements}->[$_ - 1]->[0];
4264 }
4265 last OE;
4266 }
4267 } # OE
4268 $foster_parent_element = $self->{open_elements}->[0]->[0]
4269 unless defined $foster_parent_element;
4270 $foster_parent_element->insert_before ($last_node->[0], $next_sibling);
4271 $open_tables->[-1]->[1] = 1; # tainted
4272 } else {
4273 !!!cp ('t65.3');
4274 $common_ancestor_node->[0]->append_child ($last_node->[0]);
4275 }
4276
4277 ## Step 9
4278 my $clone = [$formatting_element->[0]->clone_node (0),
4279 $formatting_element->[1]];
4280
4281 ## Step 10
4282 my @cn = @{$furthest_block->[0]->child_nodes};
4283 $clone->[0]->append_child ($_) for @cn;
4284
4285 ## Step 11
4286 $furthest_block->[0]->append_child ($clone->[0]);
4287
4288 ## Step 12
4289 my $i;
4290 AFE: for (reverse 0..$#$active_formatting_elements) {
4291 if ($active_formatting_elements->[$_]->[0] eq $formatting_element->[0]) {
4292 !!!cp ('t66');
4293 splice @$active_formatting_elements, $_, 1;
4294 $i-- and last AFE if defined $i;
4295 } elsif ($active_formatting_elements->[$_]->[0] eq $bookmark_prev_el) {
4296 !!!cp ('t67');
4297 $i = $_;
4298 }
4299 } # AFE
4300 splice @$active_formatting_elements, $i + 1, 0, $clone;
4301
4302 ## Step 13
4303 undef $i;
4304 OE: for (reverse 0..$#{$self->{open_elements}}) {
4305 if ($self->{open_elements}->[$_]->[0] eq $formatting_element->[0]) {
4306 !!!cp ('t68');
4307 splice @{$self->{open_elements}}, $_, 1;
4308 $i-- and last OE if defined $i;
4309 } elsif ($self->{open_elements}->[$_]->[0] eq $furthest_block->[0]) {
4310 !!!cp ('t69');
4311 $i = $_;
4312 }
4313 } # OE
4314 splice @{$self->{open_elements}}, $i + 1, 0, $clone;
4315
4316 ## Step 14
4317 redo FET;
4318 } # FET
4319 }; # $formatting_end_tag
4320
4321 $insert = my $insert_to_current = sub {
4322 $self->{open_elements}->[-1]->[0]->append_child ($_[0]);
4323 }; # $insert_to_current
4324
4325 my $insert_to_foster = sub {
4326 my $child = shift;
4327 if ($self->{open_elements}->[-1]->[1] & TABLE_ROWS_EL) {
4328 # MUST
4329 my $foster_parent_element;
4330 my $next_sibling;
4331 OE: for (reverse 0..$#{$self->{open_elements}}) {
4332 if ($self->{open_elements}->[$_]->[1] & TABLE_EL) {
4333 my $parent = $self->{open_elements}->[$_]->[0]->parent_node;
4334 if (defined $parent and $parent->node_type == 1) {
4335 !!!cp ('t70');
4336 $foster_parent_element = $parent;
4337 $next_sibling = $self->{open_elements}->[$_]->[0];
4338 } else {
4339 !!!cp ('t71');
4340 $foster_parent_element
4341 = $self->{open_elements}->[$_ - 1]->[0];
4342 }
4343 last OE;
4344 }
4345 } # OE
4346 $foster_parent_element = $self->{open_elements}->[0]->[0]
4347 unless defined $foster_parent_element;
4348 $foster_parent_element->insert_before
4349 ($child, $next_sibling);
4350 $open_tables->[-1]->[1] = 1; # tainted
4351 } else {
4352 !!!cp ('t72');
4353 $self->{open_elements}->[-1]->[0]->append_child ($child);
4354 }
4355 }; # $insert_to_foster
4356
4357 ## NOTE: Insert a character (MUST): When a character is inserted, if
4358 ## the last node that was inserted by the parser is a Text node and
4359 ## the character has to be inserted after that node, then the
4360 ## character is appended to the Text node. However, if any other
4361 ## node is inserted by the parser, then a new Text node is created
4362 ## and the character is appended as that Text node. If I'm not
4363 ## wrong, for a parser with scripting disabled, there are only two
4364 ## cases where this occurs. One is the case where an element node
4365 ## is inserted to the |head| element. This is covered by using the
4366 ## |$self->{head_element_inserted}| flag. Another is the case where
4367 ## an element or comment is inserted into the |table| subtree while
4368 ## foster parenting happens. This is covered by using the [2] flag
4369 ## of the |$open_tables| structure. All other cases are handled
4370 ## simply by calling |manakai_append_text| method.
4371
4372 ## TODO: |<body><script>document.write("a<br>");
4373 ## document.body.removeChild (document.body.lastChild);
4374 ## document.write ("b")</script>|
4375
4376 B: while (1) {
4377 if ($token->{type} == DOCTYPE_TOKEN) {
4378 !!!cp ('t73');
4379 !!!parse-error (type => 'in html:#DOCTYPE', token => $token);
4380 ## Ignore the token
4381 ## Stay in the phase
4382 !!!next-token;
4383 next B;
4384 } elsif ($token->{type} == START_TAG_TOKEN and
4385 $token->{tag_name} eq 'html') {
4386 if ($self->{insertion_mode} == AFTER_HTML_BODY_IM) {
4387 !!!cp ('t79');
4388 !!!parse-error (type => 'after html', text => 'html', token => $token);
4389 $self->{insertion_mode} = AFTER_BODY_IM;
4390 } elsif ($self->{insertion_mode} == AFTER_HTML_FRAMESET_IM) {
4391 !!!cp ('t80');
4392 !!!parse-error (type => 'after html', text => 'html', token => $token);
4393 $self->{insertion_mode} = AFTER_FRAMESET_IM;
4394 } else {
4395 !!!cp ('t81');
4396 }
4397
4398 !!!cp ('t82');
4399 !!!parse-error (type => 'not first start tag', token => $token);
4400 my $top_el = $self->{open_elements}->[0]->[0];
4401 for my $attr_name (keys %{$token->{attributes}}) {
4402 unless ($top_el->has_attribute_ns (undef, $attr_name)) {
4403 !!!cp ('t84');
4404 $top_el->set_attribute_ns
4405 (undef, [undef, $attr_name],
4406 $token->{attributes}->{$attr_name}->{value});
4407 }
4408 }
4409 !!!nack ('t84.1');
4410 !!!next-token;
4411 next B;
4412 } elsif ($token->{type} == COMMENT_TOKEN) {
4413 my $comment = $self->{document}->create_comment ($token->{data});
4414 if ($self->{insertion_mode} & AFTER_HTML_IMS) {
4415 !!!cp ('t85');
4416 $self->{document}->append_child ($comment);
4417 } elsif ($self->{insertion_mode} == AFTER_BODY_IM) {
4418 !!!cp ('t86');
4419 $self->{open_elements}->[0]->[0]->append_child ($comment);
4420 } else {
4421 !!!cp ('t87');
4422 $self->{open_elements}->[-1]->[0]->append_child ($comment);
4423 $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
4424 }
4425 !!!next-token;
4426 next B;
4427 } elsif ($self->{insertion_mode} & IN_FOREIGN_CONTENT_IM) {
4428 if ($token->{type} == CHARACTER_TOKEN) {
4429 !!!cp ('t87.1');
4430 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
4431 !!!next-token;
4432 next B;
4433 } elsif ($token->{type} == START_TAG_TOKEN) {
4434 if ((not {mglyph => 1, malignmark => 1}->{$token->{tag_name}} and
4435 $self->{open_elements}->[-1]->[1] & FOREIGN_FLOW_CONTENT_EL) or
4436 not ($self->{open_elements}->[-1]->[1] & FOREIGN_EL) or
4437 ($token->{tag_name} eq 'svg' and
4438 $self->{open_elements}->[-1]->[1] & MML_AXML_EL)) {
4439 ## NOTE: "using the rules for secondary insertion mode"then"continue"
4440 !!!cp ('t87.2');
4441 #
4442 } elsif ({
4443 b => 1, big => 1, blockquote => 1, body => 1, br => 1,
4444 center => 1, code => 1, dd => 1, div => 1, dl => 1, dt => 1,
4445 em => 1, embed => 1, font => 1, h1 => 1, h2 => 1, h3 => 1,
4446 h4 => 1, h5 => 1, h6 => 1, head => 1, hr => 1, i => 1,
4447 img => 1, li => 1, listing => 1, menu => 1, meta => 1,
4448 nobr => 1, ol => 1, p => 1, pre => 1, ruby => 1, s => 1,
4449 small => 1, span => 1, strong => 1, strike => 1, sub => 1,
4450 sup => 1, table => 1, tt => 1, u => 1, ul => 1, var => 1,
4451 }->{$token->{tag_name}}) {
4452 !!!cp ('t87.2');
4453 !!!parse-error (type => 'not closed',
4454 text => $self->{open_elements}->[-1]->[0]
4455 ->manakai_local_name,
4456 token => $token);
4457
4458 pop @{$self->{open_elements}}
4459 while $self->{open_elements}->[-1]->[1] & FOREIGN_EL;
4460
4461 $self->{insertion_mode} &= ~ IN_FOREIGN_CONTENT_IM;
4462 ## Reprocess.
4463 next B;
4464 } else {
4465 my $nsuri = $self->{open_elements}->[-1]->[0]->namespace_uri;
4466 my $tag_name = $token->{tag_name};
4467 if ($nsuri eq $SVG_NS) {
4468 $tag_name = {
4469 altglyph => 'altGlyph',
4470 altglyphdef => 'altGlyphDef',
4471 altglyphitem => 'altGlyphItem',
4472 animatecolor => 'animateColor',
4473 animatemotion => 'animateMotion',
4474 animatetransform => 'animateTransform',
4475 clippath => 'clipPath',
4476 feblend => 'feBlend',
4477 fecolormatrix => 'feColorMatrix',
4478 fecomponenttransfer => 'feComponentTransfer',
4479 fecomposite => 'feComposite',
4480 feconvolvematrix => 'feConvolveMatrix',
4481 fediffuselighting => 'feDiffuseLighting',
4482 fedisplacementmap => 'feDisplacementMap',
4483 fedistantlight => 'feDistantLight',
4484 feflood => 'feFlood',
4485 fefunca => 'feFuncA',
4486 fefuncb => 'feFuncB',
4487 fefuncg => 'feFuncG',
4488 fefuncr => 'feFuncR',
4489 fegaussianblur => 'feGaussianBlur',
4490 feimage => 'feImage',
4491 femerge => 'feMerge',
4492 femergenode => 'feMergeNode',
4493 femorphology => 'feMorphology',
4494 feoffset => 'feOffset',
4495 fepointlight => 'fePointLight',
4496 fespecularlighting => 'feSpecularLighting',
4497 fespotlight => 'feSpotLight',
4498 fetile => 'feTile',
4499 feturbulence => 'feTurbulence',
4500 foreignobject => 'foreignObject',
4501 glyphref => 'glyphRef',
4502 lineargradient => 'linearGradient',
4503 radialgradient => 'radialGradient',
4504 #solidcolor => 'solidColor', ## NOTE: Commented in spec (SVG1.2)
4505 textpath => 'textPath',
4506 }->{$tag_name} || $tag_name;
4507 }
4508
4509 ## "adjust SVG attributes" (SVG only) - done in insert-element-f
4510
4511 ## "adjust foreign attributes" - done in insert-element-f
4512
4513 !!!insert-element-f ($nsuri, $tag_name, $token->{attributes}, $token);
4514
4515 if ($self->{self_closing}) {
4516 pop @{$self->{open_elements}};
4517 !!!ack ('t87.3');
4518 } else {
4519 !!!cp ('t87.4');
4520 }
4521
4522 !!!next-token;
4523 next B;
4524 }
4525 } elsif ($token->{type} == END_TAG_TOKEN) {
4526 ## NOTE: "using the rules for secondary insertion mode" then "continue"
4527 !!!cp ('t87.5');
4528 #
4529 } elsif ($token->{type} == END_OF_FILE_TOKEN) {
4530 !!!cp ('t87.6');
4531 !!!parse-error (type => 'not closed',
4532 text => $self->{open_elements}->[-1]->[0]
4533 ->manakai_local_name,
4534 token => $token);
4535
4536 pop @{$self->{open_elements}}
4537 while $self->{open_elements}->[-1]->[1] & FOREIGN_EL;
4538
4539 ## NOTE: |<span><svg>| ... two parse errors, |<svg>| ... a parse error.
4540
4541 $self->{insertion_mode} &= ~ IN_FOREIGN_CONTENT_IM;
4542 ## Reprocess.
4543 next B;
4544 } else {
4545 die "$0: $token->{type}: Unknown token type";
4546 }
4547 }
4548
4549 if ($self->{insertion_mode} & HEAD_IMS) {
4550 if ($token->{type} == CHARACTER_TOKEN) {
4551 if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
4552 unless ($self->{insertion_mode} == BEFORE_HEAD_IM) {
4553 if ($self->{head_element_inserted}) {
4554 !!!cp ('t88.3');
4555 $self->{open_elements}->[-1]->[0]->append_child
4556 ($self->{document}->create_text_node ($1));
4557 delete $self->{head_element_inserted};
4558 ## NOTE: |</head> <link> |
4559 #
4560 } else {
4561 !!!cp ('t88.2');
4562 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
4563 ## NOTE: |</head> &#x20;|
4564 #
4565 }
4566 } else {
4567 !!!cp ('t88.1');
4568 ## Ignore the token.
4569 #
4570 }
4571 unless (length $token->{data}) {
4572 !!!cp ('t88');
4573 !!!next-token;
4574 next B;
4575 }
4576 ## TODO: set $token->{column} appropriately
4577 }
4578
4579 if ($self->{insertion_mode} == BEFORE_HEAD_IM) {
4580 !!!cp ('t89');
4581 ## As if <head>
4582 !!!create-element ($self->{head_element}, $HTML_NS, 'head',, $token);
4583 $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
4584 push @{$self->{open_elements}},
4585 [$self->{head_element}, $el_category->{head}];
4586
4587 ## Reprocess in the "in head" insertion mode...
4588 pop @{$self->{open_elements}};
4589
4590 ## Reprocess in the "after head" insertion mode...
4591 } elsif ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
4592 !!!cp ('t90');
4593 ## As if </noscript>
4594 pop @{$self->{open_elements}};
4595 !!!parse-error (type => 'in noscript:#text', token => $token);
4596
4597 ## Reprocess in the "in head" insertion mode...
4598 ## As if </head>
4599 pop @{$self->{open_elements}};
4600
4601 ## Reprocess in the "after head" insertion mode...
4602 } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
4603 !!!cp ('t91');
4604 pop @{$self->{open_elements}};
4605
4606 ## Reprocess in the "after head" insertion mode...
4607 } else {
4608 !!!cp ('t92');
4609 }
4610
4611 ## "after head" insertion mode
4612 ## As if <body>
4613 !!!insert-element ('body',, $token);
4614 $self->{insertion_mode} = IN_BODY_IM;
4615 ## reprocess
4616 next B;
4617 } elsif ($token->{type} == START_TAG_TOKEN) {
4618 if ($token->{tag_name} eq 'head') {
4619 if ($self->{insertion_mode} == BEFORE_HEAD_IM) {
4620 !!!cp ('t93');
4621 !!!create-element ($self->{head_element}, $HTML_NS, $token->{tag_name}, $token->{attributes}, $token);
4622 $self->{open_elements}->[-1]->[0]->append_child
4623 ($self->{head_element});
4624 push @{$self->{open_elements}},
4625 [$self->{head_element}, $el_category->{head}];
4626 $self->{insertion_mode} = IN_HEAD_IM;
4627 !!!nack ('t93.1');
4628 !!!next-token;
4629 next B;
4630 } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
4631 !!!cp ('t93.2');
4632 !!!parse-error (type => 'after head', text => 'head',
4633 token => $token);
4634 ## Ignore the token
4635 !!!nack ('t93.3');
4636 !!!next-token;
4637 next B;
4638 } else {
4639 !!!cp ('t95');
4640 !!!parse-error (type => 'in head:head',
4641 token => $token); # or in head noscript
4642 ## Ignore the token
4643 !!!nack ('t95.1');
4644 !!!next-token;
4645 next B;
4646 }
4647 } elsif ($self->{insertion_mode} == BEFORE_HEAD_IM) {
4648 !!!cp ('t96');
4649 ## As if <head>
4650 !!!create-element ($self->{head_element}, $HTML_NS, 'head',, $token);
4651 $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
4652 push @{$self->{open_elements}},
4653 [$self->{head_element}, $el_category->{head}];
4654
4655 $self->{insertion_mode} = IN_HEAD_IM;
4656 ## Reprocess in the "in head" insertion mode...
4657 } else {
4658 !!!cp ('t97');
4659 }
4660
4661 if ($token->{tag_name} eq 'base') {
4662 if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
4663 !!!cp ('t98');
4664 ## As if </noscript>
4665 pop @{$self->{open_elements}};
4666 !!!parse-error (type => 'in noscript', text => 'base',
4667 token => $token);
4668
4669 $self->{insertion_mode} = IN_HEAD_IM;
4670 ## Reprocess in the "in head" insertion mode...
4671 } else {
4672 !!!cp ('t99');
4673 }
4674
4675 ## NOTE: There is a "as if in head" code clone.
4676 if ($self->{insertion_mode} == AFTER_HEAD_IM) {
4677 !!!cp ('t100');
4678 !!!parse-error (type => 'after head',
4679 text => $token->{tag_name}, token => $token);
4680 push @{$self->{open_elements}},
4681 [$self->{head_element}, $el_category->{head}];
4682 $self->{head_element_inserted} = 1;
4683 } else {
4684 !!!cp ('t101');
4685 }
4686 !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
4687 pop @{$self->{open_elements}};
4688 pop @{$self->{open_elements}} # <head>
4689 if $self->{insertion_mode} == AFTER_HEAD_IM;
4690 !!!nack ('t101.1');
4691 !!!next-token;
4692 next B;
4693 } elsif ($token->{tag_name} eq 'link') {
4694 ## NOTE: There is a "as if in head" code clone.
4695 if ($self->{insertion_mode} == AFTER_HEAD_IM) {
4696 !!!cp ('t102');
4697 !!!parse-error (type => 'after head',
4698 text => $token->{tag_name}, token => $token);
4699 push @{$self->{open_elements}},
4700 [$self->{head_element}, $el_category->{head}];
4701 $self->{head_element_inserted} = 1;
4702 } else {
4703 !!!cp ('t103');
4704 }
4705 !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
4706 pop @{$self->{open_elements}};
4707 pop @{$self->{open_elements}} # <head>
4708 if $self->{insertion_mode} == AFTER_HEAD_IM;
4709 !!!ack ('t103.1');
4710 !!!next-token;
4711 next B;
4712 } elsif ($token->{tag_name} eq 'command' or
4713 $token->{tag_name} eq 'eventsource') {
4714 if ($self->{insertion_mode} == IN_HEAD_IM) {
4715 ## NOTE: If the insertion mode at the time of the emission
4716 ## of the token was "before head", $self->{insertion_mode}
4717 ## is already changed to |IN_HEAD_IM|.
4718
4719 ## NOTE: There is a "as if in head" code clone.
4720 !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
4721 pop @{$self->{open_elements}};
4722 pop @{$self->{open_elements}} # <head>
4723 if $self->{insertion_mode} == AFTER_HEAD_IM;
4724 !!!ack ('t103.2');
4725 !!!next-token;
4726 next B;
4727 } else {
4728 ## NOTE: "in head noscript" or "after head" insertion mode
4729 ## - in these cases, these tags are treated as same as
4730 ## normal in-body tags.
4731 !!!cp ('t103.3');
4732 #
4733 }
4734 } elsif ($token->{tag_name} eq 'meta') {
4735 ## NOTE: There is a "as if in head" code clone.
4736 if ($self->{insertion_mode} == AFTER_HEAD_IM) {
4737 !!!cp ('t104');
4738 !!!parse-error (type => 'after head',
4739 text => $token->{tag_name}, token => $token);
4740 push @{$self->{open_elements}},
4741 [$self->{head_element}, $el_category->{head}];
4742 $self->{head_element_inserted} = 1;
4743 } else {
4744 !!!cp ('t105');
4745 }
4746 !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
4747 my $meta_el = pop @{$self->{open_elements}};
4748
4749 unless ($self->{confident}) {
4750 if ($token->{attributes}->{charset}) {
4751 !!!cp ('t106');
4752 ## NOTE: Whether the encoding is supported or not is handled
4753 ## in the {change_encoding} callback.
4754 $self->{change_encoding}
4755 ->($self, $token->{attributes}->{charset}->{value},
4756 $token);
4757
4758 $meta_el->[0]->get_attribute_node_ns (undef, 'charset')
4759 ->set_user_data (manakai_has_reference =>
4760 $token->{attributes}->{charset}
4761 ->{has_reference});
4762 } elsif ($token->{attributes}->{content}) {
4763 if ($token->{attributes}->{content}->{value}
4764 =~ /[Cc][Hh][Aa][Rr][Ss][Ee][Tt]
4765 [\x09\x0A\x0C\x0D\x20]*=
4766 [\x09\x0A\x0C\x0D\x20]*(?>"([^"]*)"|'([^']*)'|
4767 ([^"'\x09\x0A\x0C\x0D\x20]
4768 [^\x09\x0A\x0C\x0D\x20\x3B]*))/x) {
4769 !!!cp ('t107');
4770 ## NOTE: Whether the encoding is supported or not is handled
4771 ## in the {change_encoding} callback.
4772 $self->{change_encoding}
4773 ->($self, defined $1 ? $1 : defined $2 ? $2 : $3,
4774 $token);
4775 $meta_el->[0]->get_attribute_node_ns (undef, 'content')
4776 ->set_user_data (manakai_has_reference =>
4777 $token->{attributes}->{content}
4778 ->{has_reference});
4779 } else {
4780 !!!cp ('t108');
4781 }
4782 }
4783 } else {
4784 if ($token->{attributes}->{charset}) {
4785 !!!cp ('t109');
4786 $meta_el->[0]->get_attribute_node_ns (undef, 'charset')
4787 ->set_user_data (manakai_has_reference =>
4788 $token->{attributes}->{charset}
4789 ->{has_reference});
4790 }
4791 if ($token->{attributes}->{content}) {
4792 !!!cp ('t110');
4793 $meta_el->[0]->get_attribute_node_ns (undef, 'content')
4794 ->set_user_data (manakai_has_reference =>
4795 $token->{attributes}->{content}
4796 ->{has_reference});
4797 }
4798 }
4799
4800 pop @{$self->{open_elements}} # <head>
4801 if $self->{insertion_mode} == AFTER_HEAD_IM;
4802 !!!ack ('t110.1');
4803 !!!next-token;
4804 next B;
4805 } elsif ($token->{tag_name} eq 'title') {
4806 if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
4807 !!!cp ('t111');
4808 ## As if </noscript>
4809 pop @{$self->{open_elements}};
4810 !!!parse-error (type => 'in noscript', text => 'title',
4811 token => $token);
4812
4813 $self->{insertion_mode} = IN_HEAD_IM;
4814 ## Reprocess in the "in head" insertion mode...
4815 } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
4816 !!!cp ('t112');
4817 !!!parse-error (type => 'after head',
4818 text => $token->{tag_name}, token => $token);
4819 push @{$self->{open_elements}},
4820 [$self->{head_element}, $el_category->{head}];
4821 $self->{head_element_inserted} = 1;
4822 } else {
4823 !!!cp ('t113');
4824 }
4825
4826 ## NOTE: There is a "as if in head" code clone.
4827 $parse_rcdata->(RCDATA_CONTENT_MODEL);
4828 pop @{$self->{open_elements}} # <head>
4829 if $self->{insertion_mode} == AFTER_HEAD_IM;
4830 next B;
4831 } elsif ($token->{tag_name} eq 'style' or
4832 $token->{tag_name} eq 'noframes') {
4833 ## NOTE: Or (scripting is enabled and tag_name eq 'noscript' and
4834 ## insertion mode IN_HEAD_IM)
4835 ## NOTE: There is a "as if in head" code clone.
4836 if ($self->{insertion_mode} == AFTER_HEAD_IM) {
4837 !!!cp ('t114');
4838 !!!parse-error (type => 'after head',
4839 text => $token->{tag_name}, token => $token);
4840 push @{$self->{open_elements}},
4841 [$self->{head_element}, $el_category->{head}];
4842 $self->{head_element_inserted} = 1;
4843 } else {
4844 !!!cp ('t115');
4845 }
4846 $parse_rcdata->(CDATA_CONTENT_MODEL);
4847 pop @{$self->{open_elements}} # <head>
4848 if $self->{insertion_mode} == AFTER_HEAD_IM;
4849 next B;
4850 } elsif ($token->{tag_name} eq 'noscript') {
4851 if ($self->{insertion_mode} == IN_HEAD_IM) {
4852 !!!cp ('t116');
4853 ## NOTE: and scripting is disalbed
4854 !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
4855 $self->{insertion_mode} = IN_HEAD_NOSCRIPT_IM;
4856 !!!nack ('t116.1');
4857 !!!next-token;
4858 next B;
4859 } elsif ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
4860 !!!cp ('t117');
4861 !!!parse-error (type => 'in noscript', text => 'noscript',
4862 token => $token);
4863 ## Ignore the token
4864 !!!nack ('t117.1');
4865 !!!next-token;
4866 next B;
4867 } else {
4868 !!!cp ('t118');
4869 #
4870 }
4871 } elsif ($token->{tag_name} eq 'script') {
4872 if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
4873 !!!cp ('t119');
4874 ## As if </noscript>
4875 pop @{$self->{open_elements}};
4876 !!!parse-error (type => 'in noscript', text => 'script',
4877 token => $token);
4878
4879 $self->{insertion_mode} = IN_HEAD_IM;
4880 ## Reprocess in the "in head" insertion mode...
4881 } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
4882 !!!cp ('t120');
4883 !!!parse-error (type => 'after head',
4884 text => $token->{tag_name}, token => $token);
4885 push @{$self->{open_elements}},
4886 [$self->{head_element}, $el_category->{head}];
4887 $self->{head_element_inserted} = 1;
4888 } else {
4889 !!!cp ('t121');
4890 }
4891
4892 ## NOTE: There is a "as if in head" code clone.
4893 $script_start_tag->();
4894 pop @{$self->{open_elements}} # <head>
4895 if $self->{insertion_mode} == AFTER_HEAD_IM;
4896 next B;
4897 } elsif ($token->{tag_name} eq 'body' or
4898 $token->{tag_name} eq 'frameset') {
4899 if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
4900 !!!cp ('t122');
4901 ## As if </noscript>
4902 pop @{$self->{open_elements}};
4903 !!!parse-error (type => 'in noscript',
4904 text => $token->{tag_name}, token => $token);
4905
4906 ## Reprocess in the "in head" insertion mode...
4907 ## As if </head>
4908 pop @{$self->{open_elements}};
4909
4910 ## Reprocess in the "after head" insertion mode...
4911 } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
4912 !!!cp ('t124');
4913 pop @{$self->{open_elements}};
4914
4915 ## Reprocess in the "after head" insertion mode...
4916 } else {
4917 !!!cp ('t125');
4918 }
4919
4920 ## "after head" insertion mode
4921 !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
4922 if ($token->{tag_name} eq 'body') {
4923 !!!cp ('t126');
4924 $self->{insertion_mode} = IN_BODY_IM;
4925 } elsif ($token->{tag_name} eq 'frameset') {
4926 !!!cp ('t127');
4927 $self->{insertion_mode} = IN_FRAMESET_IM;
4928 } else {
4929 die "$0: tag name: $self->{tag_name}";
4930 }
4931 !!!nack ('t127.1');
4932 !!!next-token;
4933 next B;
4934 } else {
4935 !!!cp ('t128');
4936 #
4937 }
4938
4939 if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
4940 !!!cp ('t129');
4941 ## As if </noscript>
4942 pop @{$self->{open_elements}};
4943 !!!parse-error (type => 'in noscript:/',
4944 text => $token->{tag_name}, token => $token);
4945
4946 ## Reprocess in the "in head" insertion mode...
4947 ## As if </head>
4948 pop @{$self->{open_elements}};
4949
4950 ## Reprocess in the "after head" insertion mode...
4951 } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
4952 !!!cp ('t130');
4953 ## As if </head>
4954 pop @{$self->{open_elements}};
4955
4956 ## Reprocess in the "after head" insertion mode...
4957 } else {
4958 !!!cp ('t131');
4959 }
4960
4961 ## "after head" insertion mode
4962 ## As if <body>
4963 !!!insert-element ('body',, $token);
4964 $self->{insertion_mode} = IN_BODY_IM;
4965 ## reprocess
4966 !!!ack-later;
4967 next B;
4968 } elsif ($token->{type} == END_TAG_TOKEN) {
4969 if ($token->{tag_name} eq 'head') {
4970 if ($self->{insertion_mode} == BEFORE_HEAD_IM) {
4971 !!!cp ('t132');
4972 ## As if <head>
4973 !!!create-element ($self->{head_element}, $HTML_NS, 'head',, $token);
4974 $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
4975 push @{$self->{open_elements}},
4976 [$self->{head_element}, $el_category->{head}];
4977
4978 ## Reprocess in the "in head" insertion mode...
4979 pop @{$self->{open_elements}};
4980 $self->{insertion_mode} = AFTER_HEAD_IM;
4981 !!!next-token;
4982 next B;
4983 } elsif ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
4984 !!!cp ('t133');
4985 ## As if </noscript>
4986 pop @{$self->{open_elements}};
4987 !!!parse-error (type => 'in noscript:/',
4988 text => 'head', token => $token);
4989
4990 ## Reprocess in the "in head" insertion mode...
4991 pop @{$self->{open_elements}};
4992 $self->{insertion_mode} = AFTER_HEAD_IM;
4993 !!!next-token;
4994 next B;
4995 } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
4996 !!!cp ('t134');
4997 pop @{$self->{open_elements}};
4998 $self->{insertion_mode} = AFTER_HEAD_IM;
4999 !!!next-token;
5000 next B;
5001 } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
5002 !!!cp ('t134.1');
5003 !!!parse-error (type => 'unmatched end tag', text => 'head',
5004 token => $token);
5005 ## Ignore the token
5006 !!!next-token;
5007 next B;
5008 } else {
5009 die "$0: $self->{insertion_mode}: Unknown insertion mode";
5010 }
5011 } elsif ($token->{tag_name} eq 'noscript') {
5012 if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
5013 !!!cp ('t136');
5014 pop @{$self->{open_elements}};
5015 $self->{insertion_mode} = IN_HEAD_IM;
5016 !!!next-token;
5017 next B;
5018 } elsif ($self->{insertion_mode} == BEFORE_HEAD_IM or
5019 $self->{insertion_mode} == AFTER_HEAD_IM) {
5020 !!!cp ('t137');
5021 !!!parse-error (type => 'unmatched end tag',
5022 text => 'noscript', token => $token);
5023 ## Ignore the token ## ISSUE: An issue in the spec.
5024 !!!next-token;
5025 next B;
5026 } else {
5027 !!!cp ('t138');
5028 #
5029 }
5030 } elsif ({
5031 body => 1, html => 1,
5032 }->{$token->{tag_name}}) {
5033 ## TODO: This branch is entirely redundant.
5034 if ($self->{insertion_mode} == BEFORE_HEAD_IM or
5035 $self->{insertion_mode} == IN_HEAD_IM or
5036 $self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
5037 !!!cp ('t140');
5038 !!!parse-error (type => 'unmatched end tag',
5039 text => $token->{tag_name}, token => $token);
5040 ## Ignore the token
5041 !!!next-token;
5042 next B;
5043 } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
5044 !!!cp ('t140.1');
5045 !!!parse-error (type => 'unmatched end tag',
5046 text => $token->{tag_name}, token => $token);
5047 ## Ignore the token
5048 !!!next-token;
5049 next B;
5050 } else {
5051 die "$0: $self->{insertion_mode}: Unknown insertion mode";
5052 }
5053 } elsif ($token->{tag_name} eq 'p') {
5054 !!!cp ('t142');
5055 !!!parse-error (type => 'unmatched end tag',
5056 text => $token->{tag_name}, token => $token);
5057 ## Ignore the token
5058 !!!next-token;
5059 next B;
5060 } elsif ($token->{tag_name} eq 'br') {
5061 if ($self->{insertion_mode} == BEFORE_HEAD_IM) {
5062 !!!cp ('t142.2');
5063 ## (before head) as if <head>, (in head) as if </head>
5064 !!!create-element ($self->{head_element}, $HTML_NS, 'head',, $token);
5065 $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
5066 $self->{insertion_mode} = AFTER_HEAD_IM;
5067
5068 ## Reprocess in the "after head" insertion mode...
5069 } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
5070 !!!cp ('t143.2');
5071 ## As if </head>
5072 pop @{$self->{open_elements}};
5073 $self->{insertion_mode} = AFTER_HEAD_IM;
5074
5075 ## Reprocess in the "after head" insertion mode...
5076 } elsif ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
5077 !!!cp ('t143.3');
5078 ## ISSUE: Two parse errors for <head><noscript></br>
5079 !!!parse-error (type => 'unmatched end tag',
5080 text => 'br', token => $token);
5081 ## As if </noscript>
5082 pop @{$self->{open_elements}};
5083 $self->{insertion_mode} = IN_HEAD_IM;
5084
5085 ## Reprocess in the "in head" insertion mode...
5086 ## As if </head>
5087 pop @{$self->{open_elements}};
5088 $self->{insertion_mode} = AFTER_HEAD_IM;
5089
5090 ## Reprocess in the "after head" insertion mode...
5091 } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
5092 !!!cp ('t143.4');
5093 #
5094 } else {
5095 die "$0: $self->{insertion_mode}: Unknown insertion mode";
5096 }
5097
5098 ## ISSUE: does not agree with IE7 - it doesn't ignore </br>.
5099 !!!parse-error (type => 'unmatched end tag',
5100 text => 'br', token => $token);
5101 ## Ignore the token
5102 !!!next-token;
5103 next B;
5104 } else {
5105 !!!cp ('t145');
5106 !!!parse-error (type => 'unmatched end tag',
5107 text => $token->{tag_name}, token => $token);
5108 ## Ignore the token
5109 !!!next-token;
5110 next B;
5111 }
5112
5113 if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
5114 !!!cp ('t146');
5115 ## As if </noscript>
5116 pop @{$self->{open_elements}};
5117 !!!parse-error (type => 'in noscript:/',
5118 text => $token->{tag_name}, token => $token);
5119
5120 ## Reprocess in the "in head" insertion mode...
5121 ## As if </head>
5122 pop @{$self->{open_elements}};
5123
5124 ## Reprocess in the "after head" insertion mode...
5125 } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
5126 !!!cp ('t147');
5127 ## As if </head>
5128 pop @{$self->{open_elements}};
5129
5130 ## Reprocess in the "after head" insertion mode...
5131 } elsif ($self->{insertion_mode} == BEFORE_HEAD_IM) {
5132 ## ISSUE: This case cannot be reached?
5133 !!!cp ('t148');
5134 !!!parse-error (type => 'unmatched end tag',
5135 text => $token->{tag_name}, token => $token);
5136 ## Ignore the token ## ISSUE: An issue in the spec.
5137 !!!next-token;
5138 next B;
5139 } else {
5140 !!!cp ('t149');
5141 }
5142
5143 ## "after head" insertion mode
5144 ## As if <body>
5145 !!!insert-element ('body',, $token);
5146 $self->{insertion_mode} = IN_BODY_IM;
5147 ## reprocess
5148 next B;
5149 } elsif ($token->{type} == END_OF_FILE_TOKEN) {
5150 if ($self->{insertion_mode} == BEFORE_HEAD_IM) {
5151 !!!cp ('t149.1');
5152
5153 ## NOTE: As if <head>
5154 !!!create-element ($self->{head_element}, $HTML_NS, 'head',, $token);
5155 $self->{open_elements}->[-1]->[0]->append_child
5156 ($self->{head_element});
5157 #push @{$self->{open_elements}},
5158 # [$self->{head_element}, $el_category->{head}];
5159 #$self->{insertion_mode} = IN_HEAD_IM;
5160 ## NOTE: Reprocess.
5161
5162 ## NOTE: As if </head>
5163 #pop @{$self->{open_elements}};
5164 #$self->{insertion_mode} = IN_AFTER_HEAD_IM;
5165 ## NOTE: Reprocess.
5166
5167 #
5168 } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
5169 !!!cp ('t149.2');
5170
5171 ## NOTE: As if </head>
5172 pop @{$self->{open_elements}};
5173 #$self->{insertion_mode} = IN_AFTER_HEAD_IM;
5174 ## NOTE: Reprocess.
5175
5176 #
5177 } elsif ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
5178 !!!cp ('t149.3');
5179
5180 !!!parse-error (type => 'in noscript:#eof', token => $token);
5181
5182 ## As if </noscript>
5183 pop @{$self->{open_elements}};
5184 #$self->{insertion_mode} = IN_HEAD_IM;
5185 ## NOTE: Reprocess.
5186
5187 ## NOTE: As if </head>
5188 pop @{$self->{open_elements}};
5189 #$self->{insertion_mode} = IN_AFTER_HEAD_IM;
5190 ## NOTE: Reprocess.
5191
5192 #
5193 } else {
5194 !!!cp ('t149.4');
5195 #
5196 }
5197
5198 ## NOTE: As if <body>
5199 !!!insert-element ('body',, $token);
5200 $self->{insertion_mode} = IN_BODY_IM;
5201 ## NOTE: Reprocess.
5202 next B;
5203 } else {
5204 die "$0: $token->{type}: Unknown token type";
5205 }
5206 } elsif ($self->{insertion_mode} & BODY_IMS) {
5207 if ($token->{type} == CHARACTER_TOKEN) {
5208 !!!cp ('t150');
5209 ## NOTE: There is a code clone of "character in body".
5210 $reconstruct_active_formatting_elements->($insert_to_current);
5211
5212 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
5213
5214 !!!next-token;
5215 next B;
5216 } elsif ($token->{type} == START_TAG_TOKEN) {
5217 if ({
5218 caption => 1, col => 1, colgroup => 1, tbody => 1,
5219 td => 1, tfoot => 1, th => 1, thead => 1, tr => 1,
5220 }->{$token->{tag_name}}) {
5221 if ($self->{insertion_mode} == IN_CELL_IM) {
5222 ## have an element in table scope
5223 for (reverse 0..$#{$self->{open_elements}}) {
5224 my $node = $self->{open_elements}->[$_];
5225 if ($node->[1] & TABLE_CELL_EL) {
5226 !!!cp ('t151');
5227
5228 ## Close the cell
5229 !!!back-token; # <x>
5230 $token = {type => END_TAG_TOKEN,
5231 tag_name => $node->[0]->manakai_local_name,
5232 line => $token->{line},
5233 column => $token->{column}};
5234 next B;
5235 } elsif ($node->[1] & TABLE_SCOPING_EL) {
5236 !!!cp ('t152');
5237 ## ISSUE: This case can never be reached, maybe.
5238 last;
5239 }
5240 }
5241
5242 !!!cp ('t153');
5243 !!!parse-error (type => 'start tag not allowed',
5244 text => $token->{tag_name}, token => $token);
5245 ## Ignore the token
5246 !!!nack ('t153.1');
5247 !!!next-token;
5248 next B;
5249 } elsif ($self->{insertion_mode} == IN_CAPTION_IM) {
5250 !!!parse-error (type => 'not closed', text => 'caption',
5251 token => $token);
5252
5253 ## NOTE: As if </caption>.
5254 ## have a table element in table scope
5255 my $i;
5256 INSCOPE: {
5257 for (reverse 0..$#{$self->{open_elements}}) {
5258 my $node = $self->{open_elements}->[$_];
5259 if ($node->[1] & CAPTION_EL) {
5260 !!!cp ('t155');
5261 $i = $_;
5262 last INSCOPE;
5263 } elsif ($node->[1] & TABLE_SCOPING_EL) {
5264 !!!cp ('t156');
5265 last;
5266 }
5267 }
5268
5269 !!!cp ('t157');
5270 !!!parse-error (type => 'start tag not allowed',
5271 text => $token->{tag_name}, token => $token);
5272 ## Ignore the token
5273 !!!nack ('t157.1');
5274 !!!next-token;
5275 next B;
5276 } # INSCOPE
5277
5278 ## generate implied end tags
5279 while ($self->{open_elements}->[-1]->[1]
5280 & END_TAG_OPTIONAL_EL) {
5281 !!!cp ('t158');
5282 pop @{$self->{open_elements}};
5283 }
5284
5285 unless ($self->{open_elements}->[-1]->[1] & CAPTION_EL) {
5286 !!!cp ('t159');
5287 !!!parse-error (type => 'not closed',
5288 text => $self->{open_elements}->[-1]->[0]
5289 ->manakai_local_name,
5290 token => $token);
5291 } else {
5292 !!!cp ('t160');
5293 }
5294
5295 splice @{$self->{open_elements}}, $i;
5296
5297 $clear_up_to_marker->();
5298
5299 $self->{insertion_mode} = IN_TABLE_IM;
5300
5301 ## reprocess
5302 !!!ack-later;
5303 next B;
5304 } else {
5305 !!!cp ('t161');
5306 #
5307 }
5308 } else {
5309 !!!cp ('t162');
5310 #
5311 }
5312 } elsif ($token->{type} == END_TAG_TOKEN) {
5313 if ($token->{tag_name} eq 'td' or $token->{tag_name} eq 'th') {
5314 if ($self->{insertion_mode} == IN_CELL_IM) {
5315 ## have an element in table scope
5316 my $i;
5317 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5318 my $node = $self->{open_elements}->[$_];
5319 if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
5320 !!!cp ('t163');
5321 $i = $_;
5322 last INSCOPE;
5323 } elsif ($node->[1] & TABLE_SCOPING_EL) {
5324 !!!cp ('t164');
5325 last INSCOPE;
5326 }
5327 } # INSCOPE
5328 unless (defined $i) {
5329 !!!cp ('t165');
5330 !!!parse-error (type => 'unmatched end tag',
5331 text => $token->{tag_name},
5332 token => $token);
5333 ## Ignore the token
5334 !!!next-token;
5335 next B;
5336 }
5337
5338 ## generate implied end tags
5339 while ($self->{open_elements}->[-1]->[1]
5340 & END_TAG_OPTIONAL_EL) {
5341 !!!cp ('t166');
5342 pop @{$self->{open_elements}};
5343 }
5344
5345 if ($self->{open_elements}->[-1]->[0]->manakai_local_name
5346 ne $token->{tag_name}) {
5347 !!!cp ('t167');
5348 !!!parse-error (type => 'not closed',
5349 text => $self->{open_elements}->[-1]->[0]
5350 ->manakai_local_name,
5351 token => $token);
5352 } else {
5353 !!!cp ('t168');
5354 }
5355
5356 splice @{$self->{open_elements}}, $i;
5357
5358 $clear_up_to_marker->();
5359
5360 $self->{insertion_mode} = IN_ROW_IM;
5361
5362 !!!next-token;
5363 next B;
5364 } elsif ($self->{insertion_mode} == IN_CAPTION_IM) {
5365 !!!cp ('t169');
5366 !!!parse-error (type => 'unmatched end tag',
5367 text => $token->{tag_name}, token => $token);
5368 ## Ignore the token
5369 !!!next-token;
5370 next B;
5371 } else {
5372 !!!cp ('t170');
5373 #
5374 }
5375 } elsif ($token->{tag_name} eq 'caption') {
5376 if ($self->{insertion_mode} == IN_CAPTION_IM) {
5377 ## have a table element in table scope
5378 my $i;
5379 INSCOPE: {
5380 for (reverse 0..$#{$self->{open_elements}}) {
5381 my $node = $self->{open_elements}->[$_];
5382 if ($node->[1] & CAPTION_EL) {
5383 !!!cp ('t171');
5384 $i = $_;
5385 last INSCOPE;
5386 } elsif ($node->[1] & TABLE_SCOPING_EL) {
5387 !!!cp ('t172');
5388 last;
5389 }
5390 }
5391
5392 !!!cp ('t173');
5393 !!!parse-error (type => 'unmatched end tag',
5394 text => $token->{tag_name}, token => $token);
5395 ## Ignore the token
5396 !!!next-token;
5397 next B;
5398 } # INSCOPE
5399
5400 ## generate implied end tags
5401 while ($self->{open_elements}->[-1]->[1]
5402 & END_TAG_OPTIONAL_EL) {
5403 !!!cp ('t174');
5404 pop @{$self->{open_elements}};
5405 }
5406
5407 unless ($self->{open_elements}->[-1]->[1] & CAPTION_EL) {
5408 !!!cp ('t175');
5409 !!!parse-error (type => 'not closed',
5410 text => $self->{open_elements}->[-1]->[0]
5411 ->manakai_local_name,
5412 token => $token);
5413 } else {
5414 !!!cp ('t176');
5415 }
5416
5417 splice @{$self->{open_elements}}, $i;
5418
5419 $clear_up_to_marker->();
5420
5421 $self->{insertion_mode} = IN_TABLE_IM;
5422
5423 !!!next-token;
5424 next B;
5425 } elsif ($self->{insertion_mode} == IN_CELL_IM) {
5426 !!!cp ('t177');
5427 !!!parse-error (type => 'unmatched end tag',
5428 text => $token->{tag_name}, token => $token);
5429 ## Ignore the token
5430 !!!next-token;
5431 next B;
5432 } else {
5433 !!!cp ('t178');
5434 #
5435 }
5436 } elsif ({
5437 table => 1, tbody => 1, tfoot => 1,
5438 thead => 1, tr => 1,
5439 }->{$token->{tag_name}} and
5440 $self->{insertion_mode} == IN_CELL_IM) {
5441 ## have an element in table scope
5442 my $i;
5443 my $tn;
5444 INSCOPE: {
5445 for (reverse 0..$#{$self->{open_elements}}) {
5446 my $node = $self->{open_elements}->[$_];
5447 if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
5448 !!!cp ('t179');
5449 $i = $_;
5450
5451 ## Close the cell
5452 !!!back-token; # </x>
5453 $token = {type => END_TAG_TOKEN, tag_name => $tn,
5454 line => $token->{line},
5455 column => $token->{column}};
5456 next B;
5457 } elsif ($node->[1] & TABLE_CELL_EL) {
5458 !!!cp ('t180');
5459 $tn = $node->[0]->manakai_local_name;
5460 ## NOTE: There is exactly one |td| or |th| element
5461 ## in scope in the stack of open elements by definition.
5462 } elsif ($node->[1] & TABLE_SCOPING_EL) {
5463 ## ISSUE: Can this be reached?
5464 !!!cp ('t181');
5465 last;
5466 }
5467 }
5468
5469 !!!cp ('t182');
5470 !!!parse-error (type => 'unmatched end tag',
5471 text => $token->{tag_name}, token => $token);
5472 ## Ignore the token
5473 !!!next-token;
5474 next B;
5475 } # INSCOPE
5476 } elsif ($token->{tag_name} eq 'table' and
5477 $self->{insertion_mode} == IN_CAPTION_IM) {
5478 !!!parse-error (type => 'not closed', text => 'caption',
5479 token => $token);
5480
5481 ## As if </caption>
5482 ## have a table element in table scope
5483 my $i;
5484 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5485 my $node = $self->{open_elements}->[$_];
5486 if ($node->[1] & CAPTION_EL) {
5487 !!!cp ('t184');
5488 $i = $_;
5489 last INSCOPE;
5490 } elsif ($node->[1] & TABLE_SCOPING_EL) {
5491 !!!cp ('t185');
5492 last INSCOPE;
5493 }
5494 } # INSCOPE
5495 unless (defined $i) {
5496 !!!cp ('t186');
5497 !!!parse-error (type => 'unmatched end tag',
5498 text => 'caption', token => $token);
5499 ## Ignore the token
5500 !!!next-token;
5501 next B;
5502 }
5503
5504 ## generate implied end tags
5505 while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
5506 !!!cp ('t187');
5507 pop @{$self->{open_elements}};
5508 }
5509
5510 unless ($self->{open_elements}->[-1]->[1] & CAPTION_EL) {
5511 !!!cp ('t188');
5512 !!!parse-error (type => 'not closed',
5513 text => $self->{open_elements}->[-1]->[0]
5514 ->manakai_local_name,
5515 token => $token);
5516 } else {
5517 !!!cp ('t189');
5518 }
5519
5520 splice @{$self->{open_elements}}, $i;
5521
5522 $clear_up_to_marker->();
5523
5524 $self->{insertion_mode} = IN_TABLE_IM;
5525
5526 ## reprocess
5527 next B;
5528 } elsif ({
5529 body => 1, col => 1, colgroup => 1, html => 1,
5530 }->{$token->{tag_name}}) {
5531 if ($self->{insertion_mode} & BODY_TABLE_IMS) {
5532 !!!cp ('t190');
5533 !!!parse-error (type => 'unmatched end tag',
5534 text => $token->{tag_name}, token => $token);
5535 ## Ignore the token
5536 !!!next-token;
5537 next B;
5538 } else {
5539 !!!cp ('t191');
5540 #
5541 }
5542 } elsif ({
5543 tbody => 1, tfoot => 1,
5544 thead => 1, tr => 1,
5545 }->{$token->{tag_name}} and
5546 $self->{insertion_mode} == IN_CAPTION_IM) {
5547 !!!cp ('t192');
5548 !!!parse-error (type => 'unmatched end tag',
5549 text => $token->{tag_name}, token => $token);
5550 ## Ignore the token
5551 !!!next-token;
5552 next B;
5553 } else {
5554 !!!cp ('t193');
5555 #
5556 }
5557 } elsif ($token->{type} == END_OF_FILE_TOKEN) {
5558 for my $entry (@{$self->{open_elements}}) {
5559 unless ($entry->[1] & ALL_END_TAG_OPTIONAL_EL) {
5560 !!!cp ('t75');
5561 !!!parse-error (type => 'in body:#eof', token => $token);
5562 last;
5563 }
5564 }
5565
5566 ## Stop parsing.
5567 last B;
5568 } else {
5569 die "$0: $token->{type}: Unknown token type";
5570 }
5571
5572 $insert = $insert_to_current;
5573 #
5574 } elsif ($self->{insertion_mode} & TABLE_IMS) {
5575 if ($token->{type} == CHARACTER_TOKEN) {
5576 if (not $open_tables->[-1]->[1] and # tainted
5577 $token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
5578 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
5579
5580 unless (length $token->{data}) {
5581 !!!cp ('t194');
5582 !!!next-token;
5583 next B;
5584 } else {
5585 !!!cp ('t195');
5586 }
5587 }
5588
5589 !!!parse-error (type => 'in table:#text', token => $token);
5590
5591 ## NOTE: As if in body, but insert into the foster parent element.
5592 $reconstruct_active_formatting_elements->($insert_to_foster);
5593
5594 if ($self->{open_elements}->[-1]->[1] & TABLE_ROWS_EL) {
5595 # MUST
5596 my $foster_parent_element;
5597 my $next_sibling;
5598 my $prev_sibling;
5599 OE: for (reverse 0..$#{$self->{open_elements}}) {
5600 if ($self->{open_elements}->[$_]->[1] & TABLE_EL) {
5601 my $parent = $self->{open_elements}->[$_]->[0]->parent_node;
5602 if (defined $parent and $parent->node_type == 1) {
5603 $foster_parent_element = $parent;
5604 !!!cp ('t196');
5605 $next_sibling = $self->{open_elements}->[$_]->[0];
5606 $prev_sibling = $next_sibling->previous_sibling;
5607 #
5608 } else {
5609 !!!cp ('t197');
5610 $foster_parent_element = $self->{open_elements}->[$_ - 1]->[0];
5611 $prev_sibling = $foster_parent_element->last_child;
5612 #
5613 }
5614 last OE;
5615 }
5616 } # OE
5617 $foster_parent_element = $self->{open_elements}->[0]->[0] and
5618 $prev_sibling = $foster_parent_element->last_child
5619 unless defined $foster_parent_element;
5620 undef $prev_sibling unless $open_tables->[-1]->[2]; # ~node inserted
5621 if (defined $prev_sibling and
5622 $prev_sibling->node_type == 3) {
5623 !!!cp ('t198');
5624 $prev_sibling->manakai_append_text ($token->{data});
5625 } else {
5626 !!!cp ('t199');
5627 $foster_parent_element->insert_before
5628 ($self->{document}->create_text_node ($token->{data}),
5629 $next_sibling);
5630 }
5631 $open_tables->[-1]->[1] = 1; # tainted
5632 $open_tables->[-1]->[2] = 1; # ~node inserted
5633 } else {
5634 ## NOTE: Fragment case or in a foster parent'ed element
5635 ## (e.g. |<table><span>a|). In fragment case, whether the
5636 ## character is appended to existing node or a new node is
5637 ## created is irrelevant, since the foster parent'ed nodes
5638 ## are discarded and fragment parsing does not invoke any
5639 ## script.
5640 !!!cp ('t200');
5641 $self->{open_elements}->[-1]->[0]->manakai_append_text
5642 ($token->{data});
5643 }
5644
5645 !!!next-token;
5646 next B;
5647 } elsif ($token->{type} == START_TAG_TOKEN) {
5648 if ({
5649 tr => ($self->{insertion_mode} != IN_ROW_IM),
5650 th => 1, td => 1,
5651 }->{$token->{tag_name}}) {
5652 if ($self->{insertion_mode} == IN_TABLE_IM) {
5653 ## Clear back to table context
5654 while (not ($self->{open_elements}->[-1]->[1]
5655 & TABLE_SCOPING_EL)) {
5656 !!!cp ('t201');
5657 pop @{$self->{open_elements}};
5658 }
5659
5660 !!!insert-element ('tbody',, $token);
5661 $self->{insertion_mode} = IN_TABLE_BODY_IM;
5662 ## reprocess in the "in table body" insertion mode...
5663 }
5664
5665 if ($self->{insertion_mode} == IN_TABLE_BODY_IM) {
5666 unless ($token->{tag_name} eq 'tr') {
5667 !!!cp ('t202');
5668 !!!parse-error (type => 'missing start tag:tr', token => $token);
5669 }
5670
5671 ## Clear back to table body context
5672 while (not ($self->{open_elements}->[-1]->[1]
5673 & TABLE_ROWS_SCOPING_EL)) {
5674 !!!cp ('t203');
5675 ## ISSUE: Can this case be reached?
5676 pop @{$self->{open_elements}};
5677 }
5678
5679 $self->{insertion_mode} = IN_ROW_IM;
5680 if ($token->{tag_name} eq 'tr') {
5681 !!!cp ('t204');
5682 !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
5683 $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
5684 !!!nack ('t204');
5685 !!!next-token;
5686 next B;
5687 } else {
5688 !!!cp ('t205');
5689 !!!insert-element ('tr',, $token);
5690 ## reprocess in the "in row" insertion mode
5691 }
5692 } else {
5693 !!!cp ('t206');
5694 }
5695
5696 ## Clear back to table row context
5697 while (not ($self->{open_elements}->[-1]->[1]
5698 & TABLE_ROW_SCOPING_EL)) {
5699 !!!cp ('t207');
5700 pop @{$self->{open_elements}};
5701 }
5702
5703 !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
5704 $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
5705 $self->{insertion_mode} = IN_CELL_IM;
5706
5707 push @$active_formatting_elements, ['#marker', ''];
5708
5709 !!!nack ('t207.1');
5710 !!!next-token;
5711 next B;
5712 } elsif ({
5713 caption => 1, col => 1, colgroup => 1,
5714 tbody => 1, tfoot => 1, thead => 1,
5715 tr => 1, # $self->{insertion_mode} == IN_ROW_IM
5716 }->{$token->{tag_name}}) {
5717 if ($self->{insertion_mode} == IN_ROW_IM) {
5718 ## As if </tr>
5719 ## have an element in table scope
5720 my $i;
5721 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5722 my $node = $self->{open_elements}->[$_];
5723 if ($node->[1] & TABLE_ROW_EL) {
5724 !!!cp ('t208');
5725 $i = $_;
5726 last INSCOPE;
5727 } elsif ($node->[1] & TABLE_SCOPING_EL) {
5728 !!!cp ('t209');
5729 last INSCOPE;
5730 }
5731 } # INSCOPE
5732 unless (defined $i) {
5733 !!!cp ('t210');
5734 ## TODO: This type is wrong.
5735 !!!parse-error (type => 'unmacthed end tag',
5736 text => $token->{tag_name}, token => $token);
5737 ## Ignore the token
5738 !!!nack ('t210.1');
5739 !!!next-token;
5740 next B;
5741 }
5742
5743 ## Clear back to table row context
5744 while (not ($self->{open_elements}->[-1]->[1]
5745 & TABLE_ROW_SCOPING_EL)) {
5746 !!!cp ('t211');
5747 ## ISSUE: Can this case be reached?
5748 pop @{$self->{open_elements}};
5749 }
5750
5751 pop @{$self->{open_elements}}; # tr
5752 $self->{insertion_mode} = IN_TABLE_BODY_IM;
5753 if ($token->{tag_name} eq 'tr') {
5754 !!!cp ('t212');
5755 ## reprocess
5756 !!!ack-later;
5757 next B;
5758 } else {
5759 !!!cp ('t213');
5760 ## reprocess in the "in table body" insertion mode...
5761 }
5762 }
5763
5764 if ($self->{insertion_mode} == IN_TABLE_BODY_IM) {
5765 ## have an element in table scope
5766 my $i;
5767 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5768 my $node = $self->{open_elements}->[$_];
5769 if ($node->[1] & TABLE_ROW_GROUP_EL) {
5770 !!!cp ('t214');
5771 $i = $_;
5772 last INSCOPE;
5773 } elsif ($node->[1] & TABLE_SCOPING_EL) {
5774 !!!cp ('t215');
5775 last INSCOPE;
5776 }
5777 } # INSCOPE
5778 unless (defined $i) {
5779 !!!cp ('t216');
5780 ## TODO: This erorr type is wrong.
5781 !!!parse-error (type => 'unmatched end tag',
5782 text => $token->{tag_name}, token => $token);
5783 ## Ignore the token
5784 !!!nack ('t216.1');
5785 !!!next-token;
5786 next B;
5787 }
5788
5789 ## Clear back to table body context
5790 while (not ($self->{open_elements}->[-1]->[1]
5791 & TABLE_ROWS_SCOPING_EL)) {
5792 !!!cp ('t217');
5793 ## ISSUE: Can this state be reached?
5794 pop @{$self->{open_elements}};
5795 }
5796
5797 ## As if <{current node}>
5798 ## have an element in table scope
5799 ## true by definition
5800
5801 ## Clear back to table body context
5802 ## nop by definition
5803
5804 pop @{$self->{open_elements}};
5805 $self->{insertion_mode} = IN_TABLE_IM;
5806 ## reprocess in "in table" insertion mode...
5807 } else {
5808 !!!cp ('t218');
5809 }
5810
5811 if ($token->{tag_name} eq 'col') {
5812 ## Clear back to table context
5813 while (not ($self->{open_elements}->[-1]->[1]
5814 & TABLE_SCOPING_EL)) {
5815 !!!cp ('t219');
5816 ## ISSUE: Can this state be reached?
5817 pop @{$self->{open_elements}};
5818 }
5819
5820 !!!insert-element ('colgroup',, $token);
5821 $self->{insertion_mode} = IN_COLUMN_GROUP_IM;
5822 ## reprocess
5823 $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
5824 !!!ack-later;
5825 next B;
5826 } elsif ({
5827 caption => 1,
5828 colgroup => 1,
5829 tbody => 1, tfoot => 1, thead => 1,
5830 }->{$token->{tag_name}}) {
5831 ## Clear back to table context
5832 while (not ($self->{open_elements}->[-1]->[1]
5833 & TABLE_SCOPING_EL)) {
5834 !!!cp ('t220');
5835 ## ISSUE: Can this state be reached?
5836 pop @{$self->{open_elements}};
5837 }
5838
5839 push @$active_formatting_elements, ['#marker', '']
5840 if $token->{tag_name} eq 'caption';
5841
5842 !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
5843 $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
5844 $self->{insertion_mode} = {
5845 caption => IN_CAPTION_IM,
5846 colgroup => IN_COLUMN_GROUP_IM,
5847 tbody => IN_TABLE_BODY_IM,
5848 tfoot => IN_TABLE_BODY_IM,
5849 thead => IN_TABLE_BODY_IM,
5850 }->{$token->{tag_name}};
5851 !!!next-token;
5852 !!!nack ('t220.1');
5853 next B;
5854 } else {
5855 die "$0: in table: <>: $token->{tag_name}";
5856 }
5857 } elsif ($token->{tag_name} eq 'table') {
5858 !!!parse-error (type => 'not closed',
5859 text => $self->{open_elements}->[-1]->[0]
5860 ->manakai_local_name,
5861 token => $token);
5862
5863 ## As if </table>
5864 ## have a table element in table scope
5865 my $i;
5866 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5867 my $node = $self->{open_elements}->[$_];
5868 if ($node->[1] & TABLE_EL) {
5869 !!!cp ('t221');
5870 $i = $_;
5871 last INSCOPE;
5872 } elsif ($node->[1] & TABLE_SCOPING_EL) {
5873 !!!cp ('t222');
5874 last INSCOPE;
5875 }
5876 } # INSCOPE
5877 unless (defined $i) {
5878 !!!cp ('t223');
5879 ## TODO: The following is wrong, maybe.
5880 !!!parse-error (type => 'unmatched end tag', text => 'table',
5881 token => $token);
5882 ## Ignore tokens </table><table>
5883 !!!nack ('t223.1');
5884 !!!next-token;
5885 next B;
5886 }
5887
5888 ## TODO: Followings are removed from the latest spec.
5889 ## generate implied end tags
5890 while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
5891 !!!cp ('t224');
5892 pop @{$self->{open_elements}};
5893 }
5894
5895 unless ($self->{open_elements}->[-1]->[1] & TABLE_EL) {
5896 !!!cp ('t225');
5897 ## NOTE: |<table><tr><table>|
5898 !!!parse-error (type => 'not closed',
5899 text => $self->{open_elements}->[-1]->[0]
5900 ->manakai_local_name,
5901 token => $token);
5902 } else {
5903 !!!cp ('t226');
5904 }
5905
5906 splice @{$self->{open_elements}}, $i;
5907 pop @{$open_tables};
5908
5909 $self->_reset_insertion_mode;
5910
5911 ## reprocess
5912 !!!ack-later;
5913 next B;
5914 } elsif ($token->{tag_name} eq 'style') {
5915 if (not $open_tables->[-1]->[1]) { # tainted
5916 !!!cp ('t227.8');
5917 ## NOTE: This is a "as if in head" code clone.
5918 $parse_rcdata->(CDATA_CONTENT_MODEL);
5919 $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
5920 next B;
5921 } else {
5922 !!!cp ('t227.7');
5923 #
5924 }
5925 } elsif ($token->{tag_name} eq 'script') {
5926 if (not $open_tables->[-1]->[1]) { # tainted
5927 !!!cp ('t227.6');
5928 ## NOTE: This is a "as if in head" code clone.
5929 $script_start_tag->();
5930 $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
5931 next B;
5932 } else {
5933 !!!cp ('t227.5');
5934 #
5935 }
5936 } elsif ($token->{tag_name} eq 'input') {
5937 if (not $open_tables->[-1]->[1]) { # tainted
5938 if ($token->{attributes}->{type}) { ## TODO: case
5939 my $type = lc $token->{attributes}->{type}->{value};
5940 if ($type eq 'hidden') {
5941 !!!cp ('t227.3');
5942 !!!parse-error (type => 'in table',
5943 text => $token->{tag_name}, token => $token);
5944
5945 !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
5946 $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
5947
5948 ## TODO: form element pointer
5949
5950 pop @{$self->{open_elements}};
5951
5952 !!!next-token;
5953 !!!ack ('t227.2.1');
5954 next B;
5955 } else {
5956 !!!cp ('t227.2');
5957 #
5958 }
5959 } else {
5960 !!!cp ('t227.1');
5961 #
5962 }
5963 } else {
5964 !!!cp ('t227.4');
5965 #
5966 }
5967 } else {
5968 !!!cp ('t227');
5969 #
5970 }
5971
5972 !!!parse-error (type => 'in table', text => $token->{tag_name},
5973 token => $token);
5974
5975 $insert = $insert_to_foster;
5976 #
5977 } elsif ($token->{type} == END_TAG_TOKEN) {
5978 if ($token->{tag_name} eq 'tr' and
5979 $self->{insertion_mode} == IN_ROW_IM) {
5980 ## have an element in table scope
5981 my $i;
5982 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5983 my $node = $self->{open_elements}->[$_];
5984 if ($node->[1] & TABLE_ROW_EL) {
5985 !!!cp ('t228');
5986 $i = $_;
5987 last INSCOPE;
5988 } elsif ($node->[1] & TABLE_SCOPING_EL) {
5989 !!!cp ('t229');
5990 last INSCOPE;
5991 }
5992 } # INSCOPE
5993 unless (defined $i) {
5994 !!!cp ('t230');
5995 !!!parse-error (type => 'unmatched end tag',
5996 text => $token->{tag_name}, token => $token);
5997 ## Ignore the token
5998 !!!nack ('t230.1');
5999 !!!next-token;
6000 next B;
6001 } else {
6002 !!!cp ('t232');
6003 }
6004
6005 ## Clear back to table row context
6006 while (not ($self->{open_elements}->[-1]->[1]
6007 & TABLE_ROW_SCOPING_EL)) {
6008 !!!cp ('t231');
6009 ## ISSUE: Can this state be reached?
6010 pop @{$self->{open_elements}};
6011 }
6012
6013 pop @{$self->{open_elements}}; # tr
6014 $self->{insertion_mode} = IN_TABLE_BODY_IM;
6015 !!!next-token;
6016 !!!nack ('t231.1');
6017 next B;
6018 } elsif ($token->{tag_name} eq 'table') {
6019 if ($self->{insertion_mode} == IN_ROW_IM) {
6020 ## As if </tr>
6021 ## have an element in table scope
6022 my $i;
6023 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6024 my $node = $self->{open_elements}->[$_];
6025 if ($node->[1] & TABLE_ROW_EL) {
6026 !!!cp ('t233');
6027 $i = $_;
6028 last INSCOPE;
6029 } elsif ($node->[1] & TABLE_SCOPING_EL) {
6030 !!!cp ('t234');
6031 last INSCOPE;
6032 }
6033 } # INSCOPE
6034 unless (defined $i) {
6035 !!!cp ('t235');
6036 ## TODO: The following is wrong.
6037 !!!parse-error (type => 'unmatched end tag',
6038 text => $token->{type}, token => $token);
6039 ## Ignore the token
6040 !!!nack ('t236.1');
6041 !!!next-token;
6042 next B;
6043 }
6044
6045 ## Clear back to table row context
6046 while (not ($self->{open_elements}->[-1]->[1]
6047 & TABLE_ROW_SCOPING_EL)) {
6048 !!!cp ('t236');
6049 ## ISSUE: Can this state be reached?
6050 pop @{$self->{open_elements}};
6051 }
6052
6053 pop @{$self->{open_elements}}; # tr
6054 $self->{insertion_mode} = IN_TABLE_BODY_IM;
6055 ## reprocess in the "in table body" insertion mode...
6056 }
6057
6058 if ($self->{insertion_mode} == IN_TABLE_BODY_IM) {
6059 ## have an element in table scope
6060 my $i;
6061 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6062 my $node = $self->{open_elements}->[$_];
6063 if ($node->[1] & TABLE_ROW_GROUP_EL) {
6064 !!!cp ('t237');
6065 $i = $_;
6066 last INSCOPE;
6067 } elsif ($node->[1] & TABLE_SCOPING_EL) {
6068 !!!cp ('t238');
6069 last INSCOPE;
6070 }
6071 } # INSCOPE
6072 unless (defined $i) {
6073 !!!cp ('t239');
6074 !!!parse-error (type => 'unmatched end tag',
6075 text => $token->{tag_name}, token => $token);
6076 ## Ignore the token
6077 !!!nack ('t239.1');
6078 !!!next-token;
6079 next B;
6080 }
6081
6082 ## Clear back to table body context
6083 while (not ($self->{open_elements}->[-1]->[1]
6084 & TABLE_ROWS_SCOPING_EL)) {
6085 !!!cp ('t240');
6086 pop @{$self->{open_elements}};
6087 }
6088
6089 ## As if <{current node}>
6090 ## have an element in table scope
6091 ## true by definition
6092
6093 ## Clear back to table body context
6094 ## nop by definition
6095
6096 pop @{$self->{open_elements}};
6097 $self->{insertion_mode} = IN_TABLE_IM;
6098 ## reprocess in the "in table" insertion mode...
6099 }
6100
6101 ## NOTE: </table> in the "in table" insertion mode.
6102 ## When you edit the code fragment below, please ensure that
6103 ## the code for <table> in the "in table" insertion mode
6104 ## is synced with it.
6105
6106 ## have a table element in table scope
6107 my $i;
6108 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6109 my $node = $self->{open_elements}->[$_];
6110 if ($node->[1] & TABLE_EL) {
6111 !!!cp ('t241');
6112 $i = $_;
6113 last INSCOPE;
6114 } elsif ($node->[1] & TABLE_SCOPING_EL) {
6115 !!!cp ('t242');
6116 last INSCOPE;
6117 }
6118 } # INSCOPE
6119 unless (defined $i) {
6120 !!!cp ('t243');
6121 !!!parse-error (type => 'unmatched end tag',
6122 text => $token->{tag_name}, token => $token);
6123 ## Ignore the token
6124 !!!nack ('t243.1');
6125 !!!next-token;
6126 next B;
6127 }
6128
6129 splice @{$self->{open_elements}}, $i;
6130 pop @{$open_tables};
6131
6132 $self->_reset_insertion_mode;
6133
6134 !!!next-token;
6135 next B;
6136 } elsif ({
6137 tbody => 1, tfoot => 1, thead => 1,
6138 }->{$token->{tag_name}} and
6139 $self->{insertion_mode} & ROW_IMS) {
6140 if ($self->{insertion_mode} == IN_ROW_IM) {
6141 ## have an element in table scope
6142 my $i;
6143 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6144 my $node = $self->{open_elements}->[$_];
6145 if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
6146 !!!cp ('t247');
6147 $i = $_;
6148 last INSCOPE;
6149 } elsif ($node->[1] & TABLE_SCOPING_EL) {
6150 !!!cp ('t248');
6151 last INSCOPE;
6152 }
6153 } # INSCOPE
6154 unless (defined $i) {
6155 !!!cp ('t249');
6156 !!!parse-error (type => 'unmatched end tag',
6157 text => $token->{tag_name}, token => $token);
6158 ## Ignore the token
6159 !!!nack ('t249.1');
6160 !!!next-token;
6161 next B;
6162 }
6163
6164 ## As if </tr>
6165 ## have an element in table scope
6166 my $i;
6167 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6168 my $node = $self->{open_elements}->[$_];
6169 if ($node->[1] & TABLE_ROW_EL) {
6170 !!!cp ('t250');
6171 $i = $_;
6172 last INSCOPE;
6173 } elsif ($node->[1] & TABLE_SCOPING_EL) {
6174 !!!cp ('t251');
6175 last INSCOPE;
6176 }
6177 } # INSCOPE
6178 unless (defined $i) {
6179 !!!cp ('t252');
6180 !!!parse-error (type => 'unmatched end tag',
6181 text => 'tr', token => $token);
6182 ## Ignore the token
6183 !!!nack ('t252.1');
6184 !!!next-token;
6185 next B;
6186 }
6187
6188 ## Clear back to table row context
6189 while (not ($self->{open_elements}->[-1]->[1]
6190 & TABLE_ROW_SCOPING_EL)) {
6191 !!!cp ('t253');
6192 ## ISSUE: Can this case be reached?
6193 pop @{$self->{open_elements}};
6194 }
6195
6196 pop @{$self->{open_elements}}; # tr
6197 $self->{insertion_mode} = IN_TABLE_BODY_IM;
6198 ## reprocess in the "in table body" insertion mode...
6199 }
6200
6201 ## have an element in table scope
6202 my $i;
6203 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6204 my $node = $self->{open_elements}->[$_];
6205 if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
6206 !!!cp ('t254');
6207 $i = $_;
6208 last INSCOPE;
6209 } elsif ($node->[1] & TABLE_SCOPING_EL) {
6210 !!!cp ('t255');
6211 last INSCOPE;
6212 }
6213 } # INSCOPE
6214 unless (defined $i) {
6215 !!!cp ('t256');
6216 !!!parse-error (type => 'unmatched end tag',
6217 text => $token->{tag_name}, token => $token);
6218 ## Ignore the token
6219 !!!nack ('t256.1');
6220 !!!next-token;
6221 next B;
6222 }
6223
6224 ## Clear back to table body context
6225 while (not ($self->{open_elements}->[-1]->[1]
6226 & TABLE_ROWS_SCOPING_EL)) {
6227 !!!cp ('t257');
6228 ## ISSUE: Can this case be reached?
6229 pop @{$self->{open_elements}};
6230 }
6231
6232 pop @{$self->{open_elements}};
6233 $self->{insertion_mode} = IN_TABLE_IM;
6234 !!!nack ('t257.1');
6235 !!!next-token;
6236 next B;
6237 } elsif ({
6238 body => 1, caption => 1, col => 1, colgroup => 1,
6239 html => 1, td => 1, th => 1,
6240 tr => 1, # $self->{insertion_mode} == IN_ROW_IM
6241 tbody => 1, tfoot => 1, thead => 1, # $self->{insertion_mode} == IN_TABLE_IM
6242 }->{$token->{tag_name}}) {
6243 !!!cp ('t258');
6244 !!!parse-error (type => 'unmatched end tag',
6245 text => $token->{tag_name}, token => $token);
6246 ## Ignore the token
6247 !!!nack ('t258.1');
6248 !!!next-token;
6249 next B;
6250 } else {
6251 !!!cp ('t259');
6252 !!!parse-error (type => 'in table:/',
6253 text => $token->{tag_name}, token => $token);
6254
6255 $insert = $insert_to_foster;
6256 #
6257 }
6258 } elsif ($token->{type} == END_OF_FILE_TOKEN) {
6259 unless ($self->{open_elements}->[-1]->[1] & HTML_EL and
6260 @{$self->{open_elements}} == 1) { # redundant, maybe
6261 !!!parse-error (type => 'in body:#eof', token => $token);
6262 !!!cp ('t259.1');
6263 #
6264 } else {
6265 !!!cp ('t259.2');
6266 #
6267 }
6268
6269 ## Stop parsing
6270 last B;
6271 } else {
6272 die "$0: $token->{type}: Unknown token type";
6273 }
6274 } elsif ($self->{insertion_mode} == IN_COLUMN_GROUP_IM) {
6275 if ($token->{type} == CHARACTER_TOKEN) {
6276 if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
6277 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
6278 unless (length $token->{data}) {
6279 !!!cp ('t260');
6280 !!!next-token;
6281 next B;
6282 }
6283 }
6284
6285 !!!cp ('t261');
6286 #
6287 } elsif ($token->{type} == START_TAG_TOKEN) {
6288 if ($token->{tag_name} eq 'col') {
6289 !!!cp ('t262');
6290 !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
6291 pop @{$self->{open_elements}};
6292 !!!ack ('t262.1');
6293 !!!next-token;
6294 next B;
6295 } else {
6296 !!!cp ('t263');
6297 #
6298 }
6299 } elsif ($token->{type} == END_TAG_TOKEN) {
6300 if ($token->{tag_name} eq 'colgroup') {
6301 if ($self->{open_elements}->[-1]->[1] & HTML_EL) {
6302 !!!cp ('t264');
6303 !!!parse-error (type => 'unmatched end tag',
6304 text => 'colgroup', token => $token);
6305 ## Ignore the token
6306 !!!next-token;
6307 next B;
6308 } else {
6309 !!!cp ('t265');
6310 pop @{$self->{open_elements}}; # colgroup
6311 $self->{insertion_mode} = IN_TABLE_IM;
6312 !!!next-token;
6313 next B;
6314 }
6315 } elsif ($token->{tag_name} eq 'col') {
6316 !!!cp ('t266');
6317 !!!parse-error (type => 'unmatched end tag',
6318 text => 'col', token => $token);
6319 ## Ignore the token
6320 !!!next-token;
6321 next B;
6322 } else {
6323 !!!cp ('t267');
6324 #
6325 }
6326 } elsif ($token->{type} == END_OF_FILE_TOKEN) {
6327 if ($self->{open_elements}->[-1]->[1] & HTML_EL and
6328 @{$self->{open_elements}} == 1) { # redundant, maybe
6329 !!!cp ('t270.2');
6330 ## Stop parsing.
6331 last B;
6332 } else {
6333 ## NOTE: As if </colgroup>.
6334 !!!cp ('t270.1');
6335 pop @{$self->{open_elements}}; # colgroup
6336 $self->{insertion_mode} = IN_TABLE_IM;
6337 ## Reprocess.
6338 next B;
6339 }
6340 } else {
6341 die "$0: $token->{type}: Unknown token type";
6342 }
6343
6344 ## As if </colgroup>
6345 if ($self->{open_elements}->[-1]->[1] & HTML_EL) {
6346 !!!cp ('t269');
6347 ## TODO: Wrong error type?
6348 !!!parse-error (type => 'unmatched end tag',
6349 text => 'colgroup', token => $token);
6350 ## Ignore the token
6351 !!!nack ('t269.1');
6352 !!!next-token;
6353 next B;
6354 } else {
6355 !!!cp ('t270');
6356 pop @{$self->{open_elements}}; # colgroup
6357 $self->{insertion_mode} = IN_TABLE_IM;
6358 !!!ack-later;
6359 ## reprocess
6360 next B;
6361 }
6362 } elsif ($self->{insertion_mode} & SELECT_IMS) {
6363 if ($token->{type} == CHARACTER_TOKEN) {
6364 !!!cp ('t271');
6365 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
6366 !!!next-token;
6367 next B;
6368 } elsif ($token->{type} == START_TAG_TOKEN) {
6369 if ($token->{tag_name} eq 'option') {
6370 if ($self->{open_elements}->[-1]->[1] & OPTION_EL) {
6371 !!!cp ('t272');
6372 ## As if </option>
6373 pop @{$self->{open_elements}};
6374 } else {
6375 !!!cp ('t273');
6376 }
6377
6378 !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
6379 !!!nack ('t273.1');
6380 !!!next-token;
6381 next B;
6382 } elsif ($token->{tag_name} eq 'optgroup') {
6383 if ($self->{open_elements}->[-1]->[1] & OPTION_EL) {
6384 !!!cp ('t274');
6385 ## As if </option>
6386 pop @{$self->{open_elements}};
6387 } else {
6388 !!!cp ('t275');
6389 }
6390
6391 if ($self->{open_elements}->[-1]->[1] & OPTGROUP_EL) {
6392 !!!cp ('t276');
6393 ## As if </optgroup>
6394 pop @{$self->{open_elements}};
6395 } else {
6396 !!!cp ('t277');
6397 }
6398
6399 !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
6400 !!!nack ('t277.1');
6401 !!!next-token;
6402 next B;
6403 } elsif ({
6404 select => 1, input => 1, textarea => 1,
6405 }->{$token->{tag_name}} or
6406 ($self->{insertion_mode} == IN_SELECT_IN_TABLE_IM and
6407 {
6408 caption => 1, table => 1,
6409 tbody => 1, tfoot => 1, thead => 1,
6410 tr => 1, td => 1, th => 1,
6411 }->{$token->{tag_name}})) {
6412 ## TODO: The type below is not good - <select> is replaced by </select>
6413 !!!parse-error (type => 'not closed', text => 'select',
6414 token => $token);
6415 ## NOTE: As if the token were </select> (<select> case) or
6416 ## as if there were </select> (otherwise).
6417 ## have an element in table scope
6418 my $i;
6419 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6420 my $node = $self->{open_elements}->[$_];
6421 if ($node->[1] & SELECT_EL) {
6422 !!!cp ('t278');
6423 $i = $_;
6424 last INSCOPE;
6425 } elsif ($node->[1] & TABLE_SCOPING_EL) {
6426 !!!cp ('t279');
6427 last INSCOPE;
6428 }
6429 } # INSCOPE
6430 unless (defined $i) {
6431 !!!cp ('t280');
6432 !!!parse-error (type => 'unmatched end tag',
6433 text => 'select', token => $token);
6434 ## Ignore the token
6435 !!!nack ('t280.1');
6436 !!!next-token;
6437 next B;
6438 }
6439
6440 !!!cp ('t281');
6441 splice @{$self->{open_elements}}, $i;
6442
6443 $self->_reset_insertion_mode;
6444
6445 if ($token->{tag_name} eq 'select') {
6446 !!!nack ('t281.2');
6447 !!!next-token;
6448 next B;
6449 } else {
6450 !!!cp ('t281.1');
6451 !!!ack-later;
6452 ## Reprocess the token.
6453 next B;
6454 }
6455 } else {
6456 !!!cp ('t282');
6457 !!!parse-error (type => 'in select',
6458 text => $token->{tag_name}, token => $token);
6459 ## Ignore the token
6460 !!!nack ('t282.1');
6461 !!!next-token;
6462 next B;
6463 }
6464 } elsif ($token->{type} == END_TAG_TOKEN) {
6465 if ($token->{tag_name} eq 'optgroup') {
6466 if ($self->{open_elements}->[-1]->[1] & OPTION_EL and
6467 $self->{open_elements}->[-2]->[1] & OPTGROUP_EL) {
6468 !!!cp ('t283');
6469 ## As if </option>
6470 splice @{$self->{open_elements}}, -2;
6471 } elsif ($self->{open_elements}->[-1]->[1] & OPTGROUP_EL) {
6472 !!!cp ('t284');
6473 pop @{$self->{open_elements}};
6474 } else {
6475 !!!cp ('t285');
6476 !!!parse-error (type => 'unmatched end tag',
6477 text => $token->{tag_name}, token => $token);
6478 ## Ignore the token
6479 }
6480 !!!nack ('t285.1');
6481 !!!next-token;
6482 next B;
6483 } elsif ($token->{tag_name} eq 'option') {
6484 if ($self->{open_elements}->[-1]->[1] & OPTION_EL) {
6485 !!!cp ('t286');
6486 pop @{$self->{open_elements}};
6487 } else {
6488 !!!cp ('t287');
6489 !!!parse-error (type => 'unmatched end tag',
6490 text => $token->{tag_name}, token => $token);
6491 ## Ignore the token
6492 }
6493 !!!nack ('t287.1');
6494 !!!next-token;
6495 next B;
6496 } elsif ($token->{tag_name} eq 'select') {
6497 ## have an element in table scope
6498 my $i;
6499 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6500 my $node = $self->{open_elements}->[$_];
6501 if ($node->[1] & SELECT_EL) {
6502 !!!cp ('t288');
6503 $i = $_;
6504 last INSCOPE;
6505 } elsif ($node->[1] & TABLE_SCOPING_EL) {
6506 !!!cp ('t289');
6507 last INSCOPE;
6508 }
6509 } # INSCOPE
6510 unless (defined $i) {
6511 !!!cp ('t290');
6512 !!!parse-error (type => 'unmatched end tag',
6513 text => $token->{tag_name}, token => $token);
6514 ## Ignore the token
6515 !!!nack ('t290.1');
6516 !!!next-token;
6517 next B;
6518 }
6519
6520 !!!cp ('t291');
6521 splice @{$self->{open_elements}}, $i;
6522
6523 $self->_reset_insertion_mode;
6524
6525 !!!nack ('t291.1');
6526 !!!next-token;
6527 next B;
6528 } elsif ($self->{insertion_mode} == IN_SELECT_IN_TABLE_IM and
6529 {
6530 caption => 1, table => 1, tbody => 1,
6531 tfoot => 1, thead => 1, tr => 1, td => 1, th => 1,
6532 }->{$token->{tag_name}}) {
6533 ## TODO: The following is wrong?
6534 !!!parse-error (type => 'unmatched end tag',
6535 text => $token->{tag_name}, token => $token);
6536
6537 ## have an element in table scope
6538 my $i;
6539 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6540 my $node = $self->{open_elements}->[$_];
6541 if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
6542 !!!cp ('t292');
6543 $i = $_;
6544 last INSCOPE;
6545 } elsif ($node->[1] & TABLE_SCOPING_EL) {
6546 !!!cp ('t293');
6547 last INSCOPE;
6548 }
6549 } # INSCOPE
6550 unless (defined $i) {
6551 !!!cp ('t294');
6552 ## Ignore the token
6553 !!!nack ('t294.1');
6554 !!!next-token;
6555 next B;
6556 }
6557
6558 ## As if </select>
6559 ## have an element in table scope
6560 undef $i;
6561 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6562 my $node = $self->{open_elements}->[$_];
6563 if ($node->[1] & SELECT_EL) {
6564 !!!cp ('t295');
6565 $i = $_;
6566 last INSCOPE;
6567 } elsif ($node->[1] & TABLE_SCOPING_EL) {
6568 ## ISSUE: Can this state be reached?
6569 !!!cp ('t296');
6570 last INSCOPE;
6571 }
6572 } # INSCOPE
6573 unless (defined $i) {
6574 !!!cp ('t297');
6575 ## TODO: The following error type is correct?
6576 !!!parse-error (type => 'unmatched end tag',
6577 text => 'select', token => $token);
6578 ## Ignore the </select> token
6579 !!!nack ('t297.1');
6580 !!!next-token; ## TODO: ok?
6581 next B;
6582 }
6583
6584 !!!cp ('t298');
6585 splice @{$self->{open_elements}}, $i;
6586
6587 $self->_reset_insertion_mode;
6588
6589 !!!ack-later;
6590 ## reprocess
6591 next B;
6592 } else {
6593 !!!cp ('t299');
6594 !!!parse-error (type => 'in select:/',
6595 text => $token->{tag_name}, token => $token);
6596 ## Ignore the token
6597 !!!nack ('t299.3');
6598 !!!next-token;
6599 next B;
6600 }
6601 } elsif ($token->{type} == END_OF_FILE_TOKEN) {
6602 unless ($self->{open_elements}->[-1]->[1] & HTML_EL and
6603 @{$self->{open_elements}} == 1) { # redundant, maybe
6604 !!!cp ('t299.1');
6605 !!!parse-error (type => 'in body:#eof', token => $token);
6606 } else {
6607 !!!cp ('t299.2');
6608 }
6609
6610 ## Stop parsing.
6611 last B;
6612 } else {
6613 die "$0: $token->{type}: Unknown token type";
6614 }
6615 } elsif ($self->{insertion_mode} & BODY_AFTER_IMS) {
6616 if ($token->{type} == CHARACTER_TOKEN) {
6617 if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
6618 my $data = $1;
6619 ## As if in body
6620 $reconstruct_active_formatting_elements->($insert_to_current);
6621
6622 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
6623
6624 unless (length $token->{data}) {
6625 !!!cp ('t300');
6626 !!!next-token;
6627 next B;
6628 }
6629 }
6630
6631 if ($self->{insertion_mode} == AFTER_HTML_BODY_IM) {
6632 !!!cp ('t301');
6633 !!!parse-error (type => 'after html:#text', token => $token);
6634 #
6635 } else {
6636 !!!cp ('t302');
6637 ## "after body" insertion mode
6638 !!!parse-error (type => 'after body:#text', token => $token);
6639 #
6640 }
6641
6642 $self->{insertion_mode} = IN_BODY_IM;
6643 ## reprocess
6644 next B;
6645 } elsif ($token->{type} == START_TAG_TOKEN) {
6646 if ($self->{insertion_mode} == AFTER_HTML_BODY_IM) {
6647 !!!cp ('t303');
6648 !!!parse-error (type => 'after html',
6649 text => $token->{tag_name}, token => $token);
6650 #
6651 } else {
6652 !!!cp ('t304');
6653 ## "after body" insertion mode
6654 !!!parse-error (type => 'after body',
6655 text => $token->{tag_name}, token => $token);
6656 #
6657 }
6658
6659 $self->{insertion_mode} = IN_BODY_IM;
6660 !!!ack-later;
6661 ## reprocess
6662 next B;
6663 } elsif ($token->{type} == END_TAG_TOKEN) {
6664 if ($self->{insertion_mode} == AFTER_HTML_BODY_IM) {
6665 !!!cp ('t305');
6666 !!!parse-error (type => 'after html:/',
6667 text => $token->{tag_name}, token => $token);
6668
6669 $self->{insertion_mode} = IN_BODY_IM;
6670 ## Reprocess.
6671 next B;
6672 } else {
6673 !!!cp ('t306');
6674 }
6675
6676 ## "after body" insertion mode
6677 if ($token->{tag_name} eq 'html') {
6678 if (defined $self->{inner_html_node}) {
6679 !!!cp ('t307');
6680 !!!parse-error (type => 'unmatched end tag',
6681 text => 'html', token => $token);
6682 ## Ignore the token
6683 !!!next-token;
6684 next B;
6685 } else {
6686 !!!cp ('t308');
6687 $self->{insertion_mode} = AFTER_HTML_BODY_IM;
6688 !!!next-token;
6689 next B;
6690 }
6691 } else {
6692 !!!cp ('t309');
6693 !!!parse-error (type => 'after body:/',
6694 text => $token->{tag_name}, token => $token);
6695
6696 $self->{insertion_mode} = IN_BODY_IM;
6697 ## reprocess
6698 next B;
6699 }
6700 } elsif ($token->{type} == END_OF_FILE_TOKEN) {
6701 !!!cp ('t309.2');
6702 ## Stop parsing
6703 last B;
6704 } else {
6705 die "$0: $token->{type}: Unknown token type";
6706 }
6707 } elsif ($self->{insertion_mode} & FRAME_IMS) {
6708 if ($token->{type} == CHARACTER_TOKEN) {
6709 if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
6710 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
6711
6712 unless (length $token->{data}) {
6713 !!!cp ('t310');
6714 !!!next-token;
6715 next B;
6716 }
6717 }
6718
6719 if ($token->{data} =~ s/^[^\x09\x0A\x0C\x20]+//) {
6720 if ($self->{insertion_mode} == IN_FRAMESET_IM) {
6721 !!!cp ('t311');
6722 !!!parse-error (type => 'in frameset:#text', token => $token);
6723 } elsif ($self->{insertion_mode} == AFTER_FRAMESET_IM) {
6724 !!!cp ('t312');
6725 !!!parse-error (type => 'after frameset:#text', token => $token);
6726 } else { # "after after frameset"
6727 !!!cp ('t313');
6728 !!!parse-error (type => 'after html:#text', token => $token);
6729 }
6730
6731 ## Ignore the token.
6732 if (length $token->{data}) {
6733 !!!cp ('t314');
6734 ## reprocess the rest of characters
6735 } else {
6736 !!!cp ('t315');
6737 !!!next-token;
6738 }
6739 next B;
6740 }
6741
6742 die qq[$0: Character "$token->{data}"];
6743 } elsif ($token->{type} == START_TAG_TOKEN) {
6744 if ($token->{tag_name} eq 'frameset' and
6745 $self->{insertion_mode} == IN_FRAMESET_IM) {
6746 !!!cp ('t318');
6747 !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
6748 !!!nack ('t318.1');
6749 !!!next-token;
6750 next B;
6751 } elsif ($token->{tag_name} eq 'frame' and
6752 $self->{insertion_mode} == IN_FRAMESET_IM) {
6753 !!!cp ('t319');
6754 !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
6755 pop @{$self->{open_elements}};
6756 !!!ack ('t319.1');
6757 !!!next-token;
6758 next B;
6759 } elsif ($token->{tag_name} eq 'noframes') {
6760 !!!cp ('t320');
6761 ## NOTE: As if in head.
6762 $parse_rcdata->(CDATA_CONTENT_MODEL);
6763 next B;
6764
6765 ## NOTE: |<!DOCTYPE HTML><frameset></frameset></html><noframes></noframes>|
6766 ## has no parse error.
6767 } else {
6768 if ($self->{insertion_mode} == IN_FRAMESET_IM) {
6769 !!!cp ('t321');
6770 !!!parse-error (type => 'in frameset',
6771 text => $token->{tag_name}, token => $token);
6772 } elsif ($self->{insertion_mode} == AFTER_FRAMESET_IM) {
6773 !!!cp ('t322');
6774 !!!parse-error (type => 'after frameset',
6775 text => $token->{tag_name}, token => $token);
6776 } else { # "after after frameset"
6777 !!!cp ('t322.2');
6778 !!!parse-error (type => 'after after frameset',
6779 text => $token->{tag_name}, token => $token);
6780 }
6781 ## Ignore the token
6782 !!!nack ('t322.1');
6783 !!!next-token;
6784 next B;
6785 }
6786 } elsif ($token->{type} == END_TAG_TOKEN) {
6787 if ($token->{tag_name} eq 'frameset' and
6788 $self->{insertion_mode} == IN_FRAMESET_IM) {
6789 if ($self->{open_elements}->[-1]->[1] & HTML_EL and
6790 @{$self->{open_elements}} == 1) {
6791 !!!cp ('t325');
6792 !!!parse-error (type => 'unmatched end tag',
6793 text => $token->{tag_name}, token => $token);
6794 ## Ignore the token
6795 !!!next-token;
6796 } else {
6797 !!!cp ('t326');
6798 pop @{$self->{open_elements}};
6799 !!!next-token;
6800 }
6801
6802 if (not defined $self->{inner_html_node} and
6803 not ($self->{open_elements}->[-1]->[1] & FRAMESET_EL)) {
6804 !!!cp ('t327');
6805 $self->{insertion_mode} = AFTER_FRAMESET_IM;
6806 } else {
6807 !!!cp ('t328');
6808 }
6809 next B;
6810 } elsif ($token->{tag_name} eq 'html' and
6811 $self->{insertion_mode} == AFTER_FRAMESET_IM) {
6812 !!!cp ('t329');
6813 $self->{insertion_mode} = AFTER_HTML_FRAMESET_IM;
6814 !!!next-token;
6815 next B;
6816 } else {
6817 if ($self->{insertion_mode} == IN_FRAMESET_IM) {
6818 !!!cp ('t330');
6819 !!!parse-error (type => 'in frameset:/',
6820 text => $token->{tag_name}, token => $token);
6821 } elsif ($self->{insertion_mode} == AFTER_FRAMESET_IM) {
6822 !!!cp ('t330.1');
6823 !!!parse-error (type => 'after frameset:/',
6824 text => $token->{tag_name}, token => $token);
6825 } else { # "after after html"
6826 !!!cp ('t331');
6827 !!!parse-error (type => 'after after frameset:/',
6828 text => $token->{tag_name}, token => $token);
6829 }
6830 ## Ignore the token
6831 !!!next-token;
6832 next B;
6833 }
6834 } elsif ($token->{type} == END_OF_FILE_TOKEN) {
6835 unless ($self->{open_elements}->[-1]->[1] & HTML_EL and
6836 @{$self->{open_elements}} == 1) { # redundant, maybe
6837 !!!cp ('t331.1');
6838 !!!parse-error (type => 'in body:#eof', token => $token);
6839 } else {
6840 !!!cp ('t331.2');
6841 }
6842
6843 ## Stop parsing
6844 last B;
6845 } else {
6846 die "$0: $token->{type}: Unknown token type";
6847 }
6848 } else {
6849 die "$0: $self->{insertion_mode}: Unknown insertion mode";
6850 }
6851
6852 ## "in body" insertion mode
6853 if ($token->{type} == START_TAG_TOKEN) {
6854 if ($token->{tag_name} eq 'script') {
6855 !!!cp ('t332');
6856 ## NOTE: This is an "as if in head" code clone
6857 $script_start_tag->();
6858 next B;
6859 } elsif ($token->{tag_name} eq 'style') {
6860 !!!cp ('t333');
6861 ## NOTE: This is an "as if in head" code clone
6862 $parse_rcdata->(CDATA_CONTENT_MODEL);
6863 next B;
6864 } elsif ({
6865 base => 1, command => 1, eventsource => 1, link => 1,
6866 }->{$token->{tag_name}}) {
6867 !!!cp ('t334');
6868 ## NOTE: This is an "as if in head" code clone, only "-t" differs
6869 !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
6870 pop @{$self->{open_elements}};
6871 !!!ack ('t334.1');
6872 !!!next-token;
6873 next B;
6874 } elsif ($token->{tag_name} eq 'meta') {
6875 ## NOTE: This is an "as if in head" code clone, only "-t" differs
6876 !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
6877 my $meta_el = pop @{$self->{open_elements}};
6878
6879 unless ($self->{confident}) {
6880 if ($token->{attributes}->{charset}) {
6881 !!!cp ('t335');
6882 ## NOTE: Whether the encoding is supported or not is handled
6883 ## in the {change_encoding} callback.
6884 $self->{change_encoding}
6885 ->($self, $token->{attributes}->{charset}->{value}, $token);
6886
6887 $meta_el->[0]->get_attribute_node_ns (undef, 'charset')
6888 ->set_user_data (manakai_has_reference =>
6889 $token->{attributes}->{charset}
6890 ->{has_reference});
6891 } elsif ($token->{attributes}->{content}) {
6892 if ($token->{attributes}->{content}->{value}
6893 =~ /[Cc][Hh][Aa][Rr][Ss][Ee][Tt]
6894 [\x09\x0A\x0C\x0D\x20]*=
6895 [\x09\x0A\x0C\x0D\x20]*(?>"([^"]*)"|'([^']*)'|
6896 ([^"'\x09\x0A\x0C\x0D\x20][^\x09\x0A\x0C\x0D\x20\x3B]*))
6897 /x) {
6898 !!!cp ('t336');
6899 ## NOTE: Whether the encoding is supported or not is handled
6900 ## in the {change_encoding} callback.
6901 $self->{change_encoding}
6902 ->($self, defined $1 ? $1 : defined $2 ? $2 : $3, $token);
6903 $meta_el->[0]->get_attribute_node_ns (undef, 'content')
6904 ->set_user_data (manakai_has_reference =>
6905 $token->{attributes}->{content}
6906 ->{has_reference});
6907 }
6908 }
6909 } else {
6910 if ($token->{attributes}->{charset}) {
6911 !!!cp ('t337');
6912 $meta_el->[0]->get_attribute_node_ns (undef, 'charset')
6913 ->set_user_data (manakai_has_reference =>
6914 $token->{attributes}->{charset}
6915 ->{has_reference});
6916 }
6917 if ($token->{attributes}->{content}) {
6918 !!!cp ('t338');
6919 $meta_el->[0]->get_attribute_node_ns (undef, 'content')
6920 ->set_user_data (manakai_has_reference =>
6921 $token->{attributes}->{content}
6922 ->{has_reference});
6923 }
6924 }
6925
6926 !!!ack ('t338.1');
6927 !!!next-token;
6928 next B;
6929 } elsif ($token->{tag_name} eq 'title') {
6930 !!!cp ('t341');
6931 ## NOTE: This is an "as if in head" code clone
6932 $parse_rcdata->(RCDATA_CONTENT_MODEL);
6933 next B;
6934 } elsif ($token->{tag_name} eq 'body') {
6935 !!!parse-error (type => 'in body', text => 'body', token => $token);
6936
6937 if (@{$self->{open_elements}} == 1 or
6938 not ($self->{open_elements}->[1]->[1] & BODY_EL)) {
6939 !!!cp ('t342');
6940 ## Ignore the token
6941 } else {
6942 my $body_el = $self->{open_elements}->[1]->[0];
6943 for my $attr_name (keys %{$token->{attributes}}) {
6944 unless ($body_el->has_attribute_ns (undef, $attr_name)) {
6945 !!!cp ('t343');
6946 $body_el->set_attribute_ns
6947 (undef, [undef, $attr_name],
6948 $token->{attributes}->{$attr_name}->{value});
6949 }
6950 }
6951 }
6952 !!!nack ('t343.1');
6953 !!!next-token;
6954 next B;
6955 } elsif ({
6956 ## NOTE: Start tags for non-phrasing flow content elements
6957
6958 ## NOTE: The normal one
6959 address => 1, article => 1, aside => 1, blockquote => 1,
6960 center => 1, datagrid => 1, details => 1, dialog => 1,
6961 dir => 1, div => 1, dl => 1, fieldset => 1, figure => 1,
6962 footer => 1, h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1,
6963 h6 => 1, header => 1, menu => 1, nav => 1, ol => 1, p => 1,
6964 section => 1, ul => 1,
6965 ## NOTE: As normal, but drops leading newline
6966 pre => 1, listing => 1,
6967 ## NOTE: As normal, but interacts with the form element pointer
6968 form => 1,
6969
6970 table => 1,
6971 hr => 1,
6972 }->{$token->{tag_name}}) {
6973 if ($token->{tag_name} eq 'form' and defined $self->{form_element}) {
6974 !!!cp ('t350');
6975 !!!parse-error (type => 'in form:form', token => $token);
6976 ## Ignore the token
6977 !!!nack ('t350.1');
6978 !!!next-token;
6979 next B;
6980 }
6981
6982 ## has a p element in scope
6983 INSCOPE: for (reverse @{$self->{open_elements}}) {
6984 if ($_->[1] & P_EL) {
6985 !!!cp ('t344');
6986 !!!back-token; # <form>
6987 $token = {type => END_TAG_TOKEN, tag_name => 'p',
6988 line => $token->{line}, column => $token->{column}};
6989 next B;
6990 } elsif ($_->[1] & SCOPING_EL) {
6991 !!!cp ('t345');
6992 last INSCOPE;
6993 }
6994 } # INSCOPE
6995
6996 !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
6997 if ($token->{tag_name} eq 'pre' or $token->{tag_name} eq 'listing') {
6998 !!!nack ('t346.1');
6999 !!!next-token;
7000 if ($token->{type} == CHARACTER_TOKEN) {
7001 $token->{data} =~ s/^\x0A//;
7002 unless (length $token->{data}) {
7003 !!!cp ('t346');
7004 !!!next-token;
7005 } else {
7006 !!!cp ('t349');
7007 }
7008 } else {
7009 !!!cp ('t348');
7010 }
7011 } elsif ($token->{tag_name} eq 'form') {
7012 !!!cp ('t347.1');
7013 $self->{form_element} = $self->{open_elements}->[-1]->[0];
7014
7015 !!!nack ('t347.2');
7016 !!!next-token;
7017 } elsif ($token->{tag_name} eq 'table') {
7018 !!!cp ('t382');
7019 push @{$open_tables}, [$self->{open_elements}->[-1]->[0]];
7020
7021 $self->{insertion_mode} = IN_TABLE_IM;
7022
7023 !!!nack ('t382.1');
7024 !!!next-token;
7025 } elsif ($token->{tag_name} eq 'hr') {
7026 !!!cp ('t386');
7027 pop @{$self->{open_elements}};
7028
7029 !!!nack ('t386.1');
7030 !!!next-token;
7031 } else {
7032 !!!nack ('t347.1');
7033 !!!next-token;
7034 }
7035 next B;
7036 } elsif ($token->{tag_name} eq 'li') {
7037 ## NOTE: As normal, but imply </li> when there's another <li> ...
7038
7039 ## NOTE: Special, Scope (<li><foo><li> == <li><foo><li/></foo></li>)
7040 ## Interpreted as <li><foo/></li><li/> (non-conforming)
7041 ## blockquote (O9.27), center (O), dd (Fx3, O, S3.1.2, IE7),
7042 ## dt (Fx, O, S, IE), dl (O), fieldset (O, S, IE), form (Fx, O, S),
7043 ## hn (O), pre (O), applet (O, S), button (O, S), marquee (Fx, O, S),
7044 ## object (Fx)
7045 ## Generate non-tree (non-conforming)
7046 ## basefont (IE7 (where basefont is non-void)), center (IE),
7047 ## form (IE), hn (IE)
7048 ## address, div, p (<li><foo><li> == <li><foo/></li><li/>)
7049 ## Interpreted as <li><foo><li/></foo></li> (non-conforming)
7050 ## div (Fx, S)
7051
7052 my $non_optional;
7053 my $i = -1;
7054
7055 ## 1.
7056 for my $node (reverse @{$self->{open_elements}}) {
7057 if ($node->[1] & LI_EL) {
7058 ## 2. (a) As if </li>
7059 {
7060 ## If no </li> - not applied
7061 #
7062
7063 ## Otherwise
7064
7065 ## 1. generate implied end tags, except for </li>
7066 #
7067
7068 ## 2. If current node != "li", parse error
7069 if ($non_optional) {
7070 !!!parse-error (type => 'not closed',
7071 text => $non_optional->[0]->manakai_local_name,
7072 token => $token);
7073 !!!cp ('t355');
7074 } else {
7075 !!!cp ('t356');
7076 }
7077
7078 ## 3. Pop
7079 splice @{$self->{open_elements}}, $i;
7080 }
7081
7082 last; ## 2. (b) goto 5.
7083 } elsif (
7084 ## NOTE: not "formatting" and not "phrasing"
7085 ($node->[1] & SPECIAL_EL or
7086 $node->[1] & SCOPING_EL) and
7087 ## NOTE: "li", "dt", and "dd" are in |SPECIAL_EL|.
7088
7089 (not $node->[1] & ADDRESS_EL) &
7090 (not $node->[1] & DIV_EL) &
7091 (not $node->[1] & P_EL)) {
7092 ## 3.
7093 !!!cp ('t357');
7094 last; ## goto 5.
7095 } elsif ($node->[1] & END_TAG_OPTIONAL_EL) {
7096 !!!cp ('t358');
7097 #
7098 } else {
7099 !!!cp ('t359');
7100 $non_optional ||= $node;
7101 #
7102 }
7103 ## 4.
7104 ## goto 2.
7105 $i--;
7106 }
7107
7108 ## 5. (a) has a |p| element in scope
7109 INSCOPE: for (reverse @{$self->{open_elements}}) {
7110 if ($_->[1] & P_EL) {
7111 !!!cp ('t353');
7112
7113 ## NOTE: |<p><li>|, for example.
7114
7115 !!!back-token; # <x>
7116 $token = {type => END_TAG_TOKEN, tag_name => 'p',
7117 line => $token->{line}, column => $token->{column}};
7118 next B;
7119 } elsif ($_->[1] & SCOPING_EL) {
7120 !!!cp ('t354');
7121 last INSCOPE;
7122 }
7123 } # INSCOPE
7124
7125 ## 5. (b) insert
7126 !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
7127 !!!nack ('t359.1');
7128 !!!next-token;
7129 next B;
7130 } elsif ($token->{tag_name} eq 'dt' or
7131 $token->{tag_name} eq 'dd') {
7132 ## NOTE: As normal, but imply </dt> or </dd> when ...
7133
7134 my $non_optional;
7135 my $i = -1;
7136
7137 ## 1.
7138 for my $node (reverse @{$self->{open_elements}}) {
7139 if ($node->[1] & DT_EL or $node->[1] & DD_EL) {
7140 ## 2. (a) As if </li>
7141 {
7142 ## If no </li> - not applied
7143 #
7144
7145 ## Otherwise
7146
7147 ## 1. generate implied end tags, except for </dt> or </dd>
7148 #
7149
7150 ## 2. If current node != "dt"|"dd", parse error
7151 if ($non_optional) {
7152 !!!parse-error (type => 'not closed',
7153 text => $non_optional->[0]->manakai_local_name,
7154 token => $token);
7155 !!!cp ('t355.1');
7156 } else {
7157 !!!cp ('t356.1');
7158 }
7159
7160 ## 3. Pop
7161 splice @{$self->{open_elements}}, $i;
7162 }
7163
7164 last; ## 2. (b) goto 5.
7165 } elsif (
7166 ## NOTE: not "formatting" and not "phrasing"
7167 ($node->[1] & SPECIAL_EL or
7168 $node->[1] & SCOPING_EL) and
7169 ## NOTE: "li", "dt", and "dd" are in |SPECIAL_EL|.
7170
7171 (not $node->[1] & ADDRESS_EL) &
7172 (not $node->[1] & DIV_EL) &
7173 (not $node->[1] & P_EL)) {
7174 ## 3.
7175 !!!cp ('t357.1');
7176 last; ## goto 5.
7177 } elsif ($node->[1] & END_TAG_OPTIONAL_EL) {
7178 !!!cp ('t358.1');
7179 #
7180 } else {
7181 !!!cp ('t359.1');
7182 $non_optional ||= $node;
7183 #
7184 }
7185 ## 4.
7186 ## goto 2.
7187 $i--;
7188 }
7189
7190 ## 5. (a) has a |p| element in scope
7191 INSCOPE: for (reverse @{$self->{open_elements}}) {
7192 if ($_->[1] & P_EL) {
7193 !!!cp ('t353.1');
7194 !!!back-token; # <x>
7195 $token = {type => END_TAG_TOKEN, tag_name => 'p',
7196 line => $token->{line}, column => $token->{column}};
7197 next B;
7198 } elsif ($_->[1] & SCOPING_EL) {
7199 !!!cp ('t354.1');
7200 last INSCOPE;
7201 }
7202 } # INSCOPE
7203
7204 ## 5. (b) insert
7205 !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
7206 !!!nack ('t359.2');
7207 !!!next-token;
7208 next B;
7209 } elsif ($token->{tag_name} eq 'plaintext') {
7210 ## NOTE: As normal, but effectively ends parsing
7211
7212 ## has a p element in scope
7213 INSCOPE: for (reverse @{$self->{open_elements}}) {
7214 if ($_->[1] & P_EL) {
7215 !!!cp ('t367');
7216 !!!back-token; # <plaintext>
7217 $token = {type => END_TAG_TOKEN, tag_name => 'p',
7218 line => $token->{line}, column => $token->{column}};
7219 next B;
7220 } elsif ($_->[1] & SCOPING_EL) {
7221 !!!cp ('t368');
7222 last INSCOPE;
7223 }
7224 } # INSCOPE
7225
7226 !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
7227
7228 $self->{content_model} = PLAINTEXT_CONTENT_MODEL;
7229
7230 !!!nack ('t368.1');
7231 !!!next-token;
7232 next B;
7233 } elsif ($token->{tag_name} eq 'a') {
7234 AFE: for my $i (reverse 0..$#$active_formatting_elements) {
7235 my $node = $active_formatting_elements->[$i];
7236 if ($node->[1] & A_EL) {
7237 !!!cp ('t371');
7238 !!!parse-error (type => 'in a:a', token => $token);
7239
7240 !!!back-token; # <a>
7241 $token = {type => END_TAG_TOKEN, tag_name => 'a',
7242 line => $token->{line}, column => $token->{column}};
7243 $formatting_end_tag->($token);
7244
7245 AFE2: for (reverse 0..$#$active_formatting_elements) {
7246 if ($active_formatting_elements->[$_]->[0] eq $node->[0]) {
7247 !!!cp ('t372');
7248 splice @$active_formatting_elements, $_, 1;
7249 last AFE2;
7250 }
7251 } # AFE2
7252 OE: for (reverse 0..$#{$self->{open_elements}}) {
7253 if ($self->{open_elements}->[$_]->[0] eq $node->[0]) {
7254 !!!cp ('t373');
7255 splice @{$self->{open_elements}}, $_, 1;
7256 last OE;
7257 }
7258 } # OE
7259 last AFE;
7260 } elsif ($node->[0] eq '#marker') {
7261 !!!cp ('t374');
7262 last AFE;
7263 }
7264 } # AFE
7265
7266 $reconstruct_active_formatting_elements->($insert_to_current);
7267
7268 !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
7269 push @$active_formatting_elements, $self->{open_elements}->[-1];
7270
7271 !!!nack ('t374.1');
7272 !!!next-token;
7273 next B;
7274 } elsif ($token->{tag_name} eq 'nobr') {
7275 $reconstruct_active_formatting_elements->($insert_to_current);
7276
7277 ## has a |nobr| element in scope
7278 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
7279 my $node = $self->{open_elements}->[$_];
7280 if ($node->[1] & NOBR_EL) {
7281 !!!cp ('t376');
7282 !!!parse-error (type => 'in nobr:nobr', token => $token);
7283 !!!back-token; # <nobr>
7284 $token = {type => END_TAG_TOKEN, tag_name => 'nobr',
7285 line => $token->{line}, column => $token->{column}};
7286 next B;
7287 } elsif ($node->[1] & SCOPING_EL) {
7288 !!!cp ('t377');
7289 last INSCOPE;
7290 }
7291 } # INSCOPE
7292
7293 !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
7294 push @$active_formatting_elements, $self->{open_elements}->[-1];
7295
7296 !!!nack ('t377.1');
7297 !!!next-token;
7298 next B;
7299 } elsif ($token->{tag_name} eq 'button') {
7300 ## has a button element in scope
7301 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
7302 my $node = $self->{open_elements}->[$_];
7303 if ($node->[1] & BUTTON_EL) {
7304 !!!cp ('t378');
7305 !!!parse-error (type => 'in button:button', token => $token);
7306 !!!back-token; # <button>
7307 $token = {type => END_TAG_TOKEN, tag_name => 'button',
7308 line => $token->{line}, column => $token->{column}};
7309 next B;
7310 } elsif ($node->[1] & SCOPING_EL) {
7311 !!!cp ('t379');
7312 last INSCOPE;
7313 }
7314 } # INSCOPE
7315
7316 $reconstruct_active_formatting_elements->($insert_to_current);
7317
7318 !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
7319
7320 ## TODO: associate with $self->{form_element} if defined
7321
7322 push @$active_formatting_elements, ['#marker', ''];
7323
7324 !!!nack ('t379.1');
7325 !!!next-token;
7326 next B;
7327 } elsif ({
7328 xmp => 1,
7329 iframe => 1,
7330 noembed => 1,
7331 noframes => 1, ## NOTE: This is an "as if in head" code clone.
7332 noscript => 0, ## TODO: 1 if scripting is enabled
7333 }->{$token->{tag_name}}) {
7334 if ($token->{tag_name} eq 'xmp') {
7335 !!!cp ('t381');
7336 $reconstruct_active_formatting_elements->($insert_to_current);
7337 } else {
7338 !!!cp ('t399');
7339 }
7340 ## NOTE: There is an "as if in body" code clone.
7341 $parse_rcdata->(CDATA_CONTENT_MODEL);
7342 next B;
7343 } elsif ($token->{tag_name} eq 'isindex') {
7344 !!!parse-error (type => 'isindex', token => $token);
7345
7346 if (defined $self->{form_element}) {
7347 !!!cp ('t389');
7348 ## Ignore the token
7349 !!!nack ('t389'); ## NOTE: Not acknowledged.
7350 !!!next-token;
7351 next B;
7352 } else {
7353 !!!ack ('t391.1');
7354
7355 my $at = $token->{attributes};
7356 my $form_attrs;
7357 $form_attrs->{action} = $at->{action} if $at->{action};
7358 my $prompt_attr = $at->{prompt};
7359 $at->{name} = {name => 'name', value => 'isindex'};
7360 delete $at->{action};
7361 delete $at->{prompt};
7362 my @tokens = (
7363 {type => START_TAG_TOKEN, tag_name => 'form',
7364 attributes => $form_attrs,
7365 line => $token->{line}, column => $token->{column}},
7366 {type => START_TAG_TOKEN, tag_name => 'hr',
7367 line => $token->{line}, column => $token->{column}},
7368 {type => START_TAG_TOKEN, tag_name => 'p',
7369 line => $token->{line}, column => $token->{column}},
7370 {type => START_TAG_TOKEN, tag_name => 'label',
7371 line => $token->{line}, column => $token->{column}},
7372 );
7373 if ($prompt_attr) {
7374 !!!cp ('t390');
7375 push @tokens, {type => CHARACTER_TOKEN, data => $prompt_attr->{value},
7376 #line => $token->{line}, column => $token->{column},
7377 };
7378 } else {
7379 !!!cp ('t391');
7380 push @tokens, {type => CHARACTER_TOKEN,
7381 data => 'This is a searchable index. Insert your search keywords here: ',
7382 #line => $token->{line}, column => $token->{column},
7383 }; # SHOULD
7384 ## TODO: make this configurable
7385 }
7386 push @tokens,
7387 {type => START_TAG_TOKEN, tag_name => 'input', attributes => $at,
7388 line => $token->{line}, column => $token->{column}},
7389 #{type => CHARACTER_TOKEN, data => ''}, # SHOULD
7390 {type => END_TAG_TOKEN, tag_name => 'label',
7391 line => $token->{line}, column => $token->{column}},
7392 {type => END_TAG_TOKEN, tag_name => 'p',
7393 line => $token->{line}, column => $token->{column}},
7394 {type => START_TAG_TOKEN, tag_name => 'hr',
7395 line => $token->{line}, column => $token->{column}},
7396 {type => END_TAG_TOKEN, tag_name => 'form',
7397 line => $token->{line}, column => $token->{column}};
7398 !!!back-token (@tokens);
7399 !!!next-token;
7400 next B;
7401 }
7402 } elsif ($token->{tag_name} eq 'textarea') {
7403 my $tag_name = $token->{tag_name};
7404 my $el;
7405 !!!create-element ($el, $HTML_NS, $token->{tag_name}, $token->{attributes}, $token);
7406
7407 ## TODO: $self->{form_element} if defined
7408 $self->{content_model} = RCDATA_CONTENT_MODEL;
7409 delete $self->{escape}; # MUST
7410
7411 $insert->($el);
7412
7413 my $text = '';
7414 !!!nack ('t392.1');
7415 !!!next-token;
7416 if ($token->{type} == CHARACTER_TOKEN) {
7417 $token->{data} =~ s/^\x0A//;
7418 unless (length $token->{data}) {
7419 !!!cp ('t392');
7420 !!!next-token;
7421 } else {
7422 !!!cp ('t393');
7423 }
7424 } else {
7425 !!!cp ('t394');
7426 }
7427 while ($token->{type} == CHARACTER_TOKEN) {
7428 !!!cp ('t395');
7429 $text .= $token->{data};
7430 !!!next-token;
7431 }
7432 if (length $text) {
7433 !!!cp ('t396');
7434 $el->manakai_append_text ($text);
7435 }
7436
7437 $self->{content_model} = PCDATA_CONTENT_MODEL;
7438
7439 if ($token->{type} == END_TAG_TOKEN and
7440 $token->{tag_name} eq $tag_name) {
7441 !!!cp ('t397');
7442 ## Ignore the token
7443 } else {
7444 !!!cp ('t398');
7445 !!!parse-error (type => 'in RCDATA:#eof', token => $token);
7446 }
7447 !!!next-token;
7448 next B;
7449 } elsif ($token->{tag_name} eq 'optgroup' or
7450 $token->{tag_name} eq 'option') {
7451 ## has an |option| element in scope
7452 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
7453 my $node = $self->{open_elements}->[$_];
7454 if ($node->[1] & OPTION_EL) {
7455 !!!cp ('t397.1');
7456 ## NOTE: As if </option>
7457 !!!back-token; # <option> or <optgroup>
7458 $token = {type => END_TAG_TOKEN, tag_name => 'option',
7459 line => $token->{line}, column => $token->{column}};
7460 next B;
7461 } elsif ($node->[1] & SCOPING_EL) {
7462 !!!cp ('t397.2');
7463 last INSCOPE;
7464 }
7465 } # INSCOPE
7466
7467 $reconstruct_active_formatting_elements->($insert_to_current);
7468
7469 !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
7470
7471 !!!nack ('t397.3');
7472 !!!next-token;
7473 redo B;
7474 } elsif ($token->{tag_name} eq 'rt' or
7475 $token->{tag_name} eq 'rp') {
7476 ## has a |ruby| element in scope
7477 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
7478 my $node = $self->{open_elements}->[$_];
7479 if ($node->[1] & RUBY_EL) {
7480 !!!cp ('t398.1');
7481 ## generate implied end tags
7482 while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
7483 !!!cp ('t398.2');
7484 pop @{$self->{open_elements}};
7485 }
7486 unless ($self->{open_elements}->[-1]->[1] & RUBY_EL) {
7487 !!!cp ('t398.3');
7488 !!!parse-error (type => 'not closed',
7489 text => $self->{open_elements}->[-1]->[0]
7490 ->manakai_local_name,
7491 token => $token);
7492 pop @{$self->{open_elements}}
7493 while not $self->{open_elements}->[-1]->[1] & RUBY_EL;
7494 }
7495 last INSCOPE;
7496 } elsif ($node->[1] & SCOPING_EL) {
7497 !!!cp ('t398.4');
7498 last INSCOPE;
7499 }
7500 } # INSCOPE
7501
7502 !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
7503
7504 !!!nack ('t398.5');
7505 !!!next-token;
7506 redo B;
7507 } elsif ($token->{tag_name} eq 'math' or
7508 $token->{tag_name} eq 'svg') {
7509 $reconstruct_active_formatting_elements->($insert_to_current);
7510
7511 ## "Adjust MathML attributes" ('math' only) - done in insert-element-f
7512
7513 ## "adjust SVG attributes" ('svg' only) - done in insert-element-f
7514
7515 ## "adjust foreign attributes" - done in insert-element-f
7516
7517 !!!insert-element-f ($token->{tag_name} eq 'math' ? $MML_NS : $SVG_NS, $token->{tag_name}, $token->{attributes}, $token);
7518
7519 if ($self->{self_closing}) {
7520 pop @{$self->{open_elements}};
7521 !!!ack ('t398.6');
7522 } else {
7523 !!!cp ('t398.7');
7524 $self->{insertion_mode} |= IN_FOREIGN_CONTENT_IM;
7525 ## NOTE: |<body><math><mi><svg>| -> "in foreign content" insertion
7526 ## mode, "in body" (not "in foreign content") secondary insertion
7527 ## mode, maybe.
7528 }
7529
7530 !!!next-token;
7531 next B;
7532 } elsif ({
7533 caption => 1, col => 1, colgroup => 1, frame => 1,
7534 frameset => 1, head => 1,
7535 tbody => 1, td => 1, tfoot => 1, th => 1,
7536 thead => 1, tr => 1,
7537 }->{$token->{tag_name}}) {
7538 !!!cp ('t401');
7539 !!!parse-error (type => 'in body',
7540 text => $token->{tag_name}, token => $token);
7541 ## Ignore the token
7542 !!!nack ('t401.1'); ## NOTE: |<col/>| or |<frame/>| here is an error.
7543 !!!next-token;
7544 next B;
7545 } elsif ($token->{tag_name} eq 'param' or
7546 $token->{tag_name} eq 'source') {
7547 !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
7548 pop @{$self->{open_elements}};
7549
7550 !!!ack ('t398.5');
7551 !!!next-token;
7552 redo B;
7553 } else {
7554 if ($token->{tag_name} eq 'image') {
7555 !!!cp ('t384');
7556 !!!parse-error (type => 'image', token => $token);
7557 $token->{tag_name} = 'img';
7558 } else {
7559 !!!cp ('t385');
7560 }
7561
7562 ## NOTE: There is an "as if <br>" code clone.
7563 $reconstruct_active_formatting_elements->($insert_to_current);
7564
7565 !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
7566
7567 if ({
7568 applet => 1, marquee => 1, object => 1,
7569 }->{$token->{tag_name}}) {
7570 !!!cp ('t380');
7571 push @$active_formatting_elements, ['#marker', ''];
7572 !!!nack ('t380.1');
7573 } elsif ({
7574 b => 1, big => 1, em => 1, font => 1, i => 1,
7575 s => 1, small => 1, strike => 1,
7576 strong => 1, tt => 1, u => 1,
7577 }->{$token->{tag_name}}) {
7578 !!!cp ('t375');
7579 push @$active_formatting_elements, $self->{open_elements}->[-1];
7580 !!!nack ('t375.1');
7581 } elsif ($token->{tag_name} eq 'input') {
7582 !!!cp ('t388');
7583 ## TODO: associate with $self->{form_element} if defined
7584 pop @{$self->{open_elements}};
7585 !!!ack ('t388.2');
7586 } elsif ({
7587 area => 1, basefont => 1, bgsound => 1, br => 1,
7588 embed => 1, img => 1, spacer => 1, wbr => 1,
7589 }->{$token->{tag_name}}) {
7590 !!!cp ('t388.1');
7591 pop @{$self->{open_elements}};
7592 !!!ack ('t388.3');
7593 } elsif ($token->{tag_name} eq 'select') {
7594 ## TODO: associate with $self->{form_element} if defined
7595
7596 if ($self->{insertion_mode} & TABLE_IMS or
7597 $self->{insertion_mode} & BODY_TABLE_IMS or
7598 $self->{insertion_mode} == IN_COLUMN_GROUP_IM) {
7599 !!!cp ('t400.1');
7600 $self->{insertion_mode} = IN_SELECT_IN_TABLE_IM;
7601 } else {
7602 !!!cp ('t400.2');
7603 $self->{insertion_mode} = IN_SELECT_IM;
7604 }
7605 !!!nack ('t400.3');
7606 } else {
7607 !!!nack ('t402');
7608 }
7609
7610 !!!next-token;
7611 next B;
7612 }
7613 } elsif ($token->{type} == END_TAG_TOKEN) {
7614 if ($token->{tag_name} eq 'body') {
7615 ## has a |body| element in scope
7616 my $i;
7617 INSCOPE: {
7618 for (reverse @{$self->{open_elements}}) {
7619 if ($_->[1] & BODY_EL) {
7620 !!!cp ('t405');
7621 $i = $_;
7622 last INSCOPE;
7623 } elsif ($_->[1] & SCOPING_EL) {
7624 !!!cp ('t405.1');
7625 last;
7626 }
7627 }
7628
7629 ## NOTE: |<marquee></body>|, |<svg><foreignobject></body>|
7630
7631 !!!parse-error (type => 'unmatched end tag',
7632 text => $token->{tag_name}, token => $token);
7633 ## NOTE: Ignore the token.
7634 !!!next-token;
7635 next B;
7636 } # INSCOPE
7637
7638 for (@{$self->{open_elements}}) {
7639 unless ($_->[1] & ALL_END_TAG_OPTIONAL_EL) {
7640 !!!cp ('t403');
7641 !!!parse-error (type => 'not closed',
7642 text => $_->[0]->manakai_local_name,
7643 token => $token);
7644 last;
7645 } else {
7646 !!!cp ('t404');
7647 }
7648 }
7649
7650 $self->{insertion_mode} = AFTER_BODY_IM;
7651 !!!next-token;
7652 next B;
7653 } elsif ($token->{tag_name} eq 'html') {
7654 ## TODO: Update this code. It seems that the code below is not
7655 ## up-to-date, though it has same effect as speced.
7656 if (@{$self->{open_elements}} > 1 and
7657 $self->{open_elements}->[1]->[1] & BODY_EL) {
7658 unless ($self->{open_elements}->[-1]->[1] & BODY_EL) {
7659 !!!cp ('t406');
7660 !!!parse-error (type => 'not closed',
7661 text => $self->{open_elements}->[1]->[0]
7662 ->manakai_local_name,
7663 token => $token);
7664 } else {
7665 !!!cp ('t407');
7666 }
7667 $self->{insertion_mode} = AFTER_BODY_IM;
7668 ## reprocess
7669 next B;
7670 } else {
7671 !!!cp ('t408');
7672 !!!parse-error (type => 'unmatched end tag',
7673 text => $token->{tag_name}, token => $token);
7674 ## Ignore the token
7675 !!!next-token;
7676 next B;
7677 }
7678 } elsif ({
7679 ## NOTE: End tags for non-phrasing flow content elements
7680
7681 ## NOTE: The normal ones
7682 address => 1, article => 1, aside => 1, blockquote => 1,
7683 center => 1, datagrid => 1, details => 1, dialog => 1,
7684 dir => 1, div => 1, dl => 1, fieldset => 1, figure => 1,
7685 footer => 1, header => 1, listing => 1, menu => 1, nav => 1,
7686 ol => 1, pre => 1, section => 1, ul => 1,
7687
7688 ## NOTE: As normal, but ... optional tags
7689 dd => 1, dt => 1, li => 1,
7690
7691 applet => 1, button => 1, marquee => 1, object => 1,
7692 }->{$token->{tag_name}}) {
7693 ## NOTE: Code for <li> start tags includes "as if </li>" code.
7694 ## Code for <dt> or <dd> start tags includes "as if </dt> or
7695 ## </dd>" code.
7696
7697 ## has an element in scope
7698 my $i;
7699 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
7700 my $node = $self->{open_elements}->[$_];
7701 if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
7702 !!!cp ('t410');
7703 $i = $_;
7704 last INSCOPE;
7705 } elsif ($node->[1] & SCOPING_EL) {
7706 !!!cp ('t411');
7707 last INSCOPE;
7708 }
7709 } # INSCOPE
7710
7711 unless (defined $i) { # has an element in scope
7712 !!!cp ('t413');
7713 !!!parse-error (type => 'unmatched end tag',
7714 text => $token->{tag_name}, token => $token);
7715 ## NOTE: Ignore the token.
7716 } else {
7717 ## Step 1. generate implied end tags
7718 while ({
7719 ## END_TAG_OPTIONAL_EL
7720 dd => ($token->{tag_name} ne 'dd'),
7721 dt => ($token->{tag_name} ne 'dt'),
7722 li => ($token->{tag_name} ne 'li'),
7723 option => 1,
7724 optgroup => 1,
7725 p => 1,
7726 rt => 1,
7727 rp => 1,
7728 }->{$self->{open_elements}->[-1]->[0]->manakai_local_name}) {
7729 !!!cp ('t409');
7730 pop @{$self->{open_elements}};
7731 }
7732
7733 ## Step 2.
7734 if ($self->{open_elements}->[-1]->[0]->manakai_local_name
7735 ne $token->{tag_name}) {
7736 !!!cp ('t412');
7737 !!!parse-error (type => 'not closed',
7738 text => $self->{open_elements}->[-1]->[0]
7739 ->manakai_local_name,
7740 token => $token);
7741 } else {
7742 !!!cp ('t414');
7743 }
7744
7745 ## Step 3.
7746 splice @{$self->{open_elements}}, $i;
7747
7748 ## Step 4.
7749 $clear_up_to_marker->()
7750 if {
7751 applet => 1, button => 1, marquee => 1, object => 1,
7752 }->{$token->{tag_name}};
7753 }
7754 !!!next-token;
7755 next B;
7756 } elsif ($token->{tag_name} eq 'form') {
7757 ## NOTE: As normal, but interacts with the form element pointer
7758
7759 undef $self->{form_element};
7760
7761 ## has an element in scope
7762 my $i;
7763 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
7764 my $node = $self->{open_elements}->[$_];
7765 if ($node->[1] & FORM_EL) {
7766 !!!cp ('t418');
7767 $i = $_;
7768 last INSCOPE;
7769 } elsif ($node->[1] & SCOPING_EL) {
7770 !!!cp ('t419');
7771 last INSCOPE;
7772 }
7773 } # INSCOPE
7774
7775 unless (defined $i) { # has an element in scope
7776 !!!cp ('t421');
7777 !!!parse-error (type => 'unmatched end tag',
7778 text => $token->{tag_name}, token => $token);
7779 ## NOTE: Ignore the token.
7780 } else {
7781 ## Step 1. generate implied end tags
7782 while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
7783 !!!cp ('t417');
7784 pop @{$self->{open_elements}};
7785 }
7786
7787 ## Step 2.
7788 if ($self->{open_elements}->[-1]->[0]->manakai_local_name
7789 ne $token->{tag_name}) {
7790 !!!cp ('t417.1');
7791 !!!parse-error (type => 'not closed',
7792 text => $self->{open_elements}->[-1]->[0]
7793 ->manakai_local_name,
7794 token => $token);
7795 } else {
7796 !!!cp ('t420');
7797 }
7798
7799 ## Step 3.
7800 splice @{$self->{open_elements}}, $i;
7801 }
7802
7803 !!!next-token;
7804 next B;
7805 } elsif ({
7806 ## NOTE: As normal, except acts as a closer for any ...
7807 h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,
7808 }->{$token->{tag_name}}) {
7809 ## has an element in scope
7810 my $i;
7811 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
7812 my $node = $self->{open_elements}->[$_];
7813 if ($node->[1] & HEADING_EL) {
7814 !!!cp ('t423');
7815 $i = $_;
7816 last INSCOPE;
7817 } elsif ($node->[1] & SCOPING_EL) {
7818 !!!cp ('t424');
7819 last INSCOPE;
7820 }
7821 } # INSCOPE
7822
7823 unless (defined $i) { # has an element in scope
7824 !!!cp ('t425.1');
7825 !!!parse-error (type => 'unmatched end tag',
7826 text => $token->{tag_name}, token => $token);
7827 ## NOTE: Ignore the token.
7828 } else {
7829 ## Step 1. generate implied end tags
7830 while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
7831 !!!cp ('t422');
7832 pop @{$self->{open_elements}};
7833 }
7834
7835 ## Step 2.
7836 if ($self->{open_elements}->[-1]->[0]->manakai_local_name
7837 ne $token->{tag_name}) {
7838 !!!cp ('t425');
7839 !!!parse-error (type => 'unmatched end tag',
7840 text => $token->{tag_name}, token => $token);
7841 } else {
7842 !!!cp ('t426');
7843 }
7844
7845 ## Step 3.
7846 splice @{$self->{open_elements}}, $i;
7847 }
7848
7849 !!!next-token;
7850 next B;
7851 } elsif ($token->{tag_name} eq 'p') {
7852 ## NOTE: As normal, except </p> implies <p> and ...
7853
7854 ## has an element in scope
7855 my $non_optional;
7856 my $i;
7857 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
7858 my $node = $self->{open_elements}->[$_];
7859 if ($node->[1] & P_EL) {
7860 !!!cp ('t410.1');
7861 $i = $_;
7862 last INSCOPE;
7863 } elsif ($node->[1] & SCOPING_EL) {
7864 !!!cp ('t411.1');
7865 last INSCOPE;
7866 } elsif ($node->[1] & END_TAG_OPTIONAL_EL) {
7867 ## NOTE: |END_TAG_OPTIONAL_EL| includes "p"
7868 !!!cp ('t411.2');
7869 #
7870 } else {
7871 !!!cp ('t411.3');
7872 $non_optional ||= $node;
7873 #
7874 }
7875 } # INSCOPE
7876
7877 if (defined $i) {
7878 ## 1. Generate implied end tags
7879 #
7880
7881 ## 2. If current node != "p", parse error
7882 if ($non_optional) {
7883 !!!cp ('t412.1');
7884 !!!parse-error (type => 'not closed',
7885 text => $non_optional->[0]->manakai_local_name,
7886 token => $token);
7887 } else {
7888 !!!cp ('t414.1');
7889 }
7890
7891 ## 3. Pop
7892 splice @{$self->{open_elements}}, $i;
7893 } else {
7894 !!!cp ('t413.1');
7895 !!!parse-error (type => 'unmatched end tag',
7896 text => $token->{tag_name}, token => $token);
7897
7898 !!!cp ('t415.1');
7899 ## As if <p>, then reprocess the current token
7900 my $el;
7901 !!!create-element ($el, $HTML_NS, 'p',, $token);
7902 $insert->($el);
7903 ## NOTE: Not inserted into |$self->{open_elements}|.
7904 }
7905
7906 !!!next-token;
7907 next B;
7908 } elsif ({
7909 a => 1,
7910 b => 1, big => 1, em => 1, font => 1, i => 1,
7911 nobr => 1, s => 1, small => 1, strike => 1,
7912 strong => 1, tt => 1, u => 1,
7913 }->{$token->{tag_name}}) {
7914 !!!cp ('t427');
7915 $formatting_end_tag->($token);
7916 next B;
7917 } elsif ($token->{tag_name} eq 'br') {
7918 !!!cp ('t428');
7919 !!!parse-error (type => 'unmatched end tag',
7920 text => 'br', token => $token);
7921
7922 ## As if <br>
7923 $reconstruct_active_formatting_elements->($insert_to_current);
7924
7925 my $el;
7926 !!!create-element ($el, $HTML_NS, 'br',, $token);
7927 $insert->($el);
7928
7929 ## Ignore the token.
7930 !!!next-token;
7931 next B;
7932 } else {
7933 if ($token->{tag_name} eq 'sarcasm') {
7934 sleep 0.001; # take a deep breath
7935 }
7936
7937 ## Step 1
7938 my $node_i = -1;
7939 my $node = $self->{open_elements}->[$node_i];
7940
7941 ## Step 2
7942 S2: {
7943 my $node_tag_name = $node->[0]->manakai_local_name;
7944 $node_tag_name =~ tr/A-Z/a-z/; # for SVG camelCase tag names
7945 if ($node_tag_name eq $token->{tag_name}) {
7946 ## Step 1
7947 ## generate implied end tags
7948 while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
7949 !!!cp ('t430');
7950 ## NOTE: |<ruby><rt></ruby>|.
7951 ## ISSUE: <ruby><rt></rt> will also take this code path,
7952 ## which seems wrong.
7953 pop @{$self->{open_elements}};
7954 $node_i++;
7955 }
7956
7957 ## Step 2
7958 my $current_tag_name
7959 = $self->{open_elements}->[-1]->[0]->manakai_local_name;
7960 $current_tag_name =~ tr/A-Z/a-z/;
7961 if ($current_tag_name ne $token->{tag_name}) {
7962 !!!cp ('t431');
7963 ## NOTE: <x><y></x>
7964 !!!parse-error (type => 'not closed',
7965 text => $self->{open_elements}->[-1]->[0]
7966 ->manakai_local_name,
7967 token => $token);
7968 } else {
7969 !!!cp ('t432');
7970 }
7971
7972 ## Step 3
7973 splice @{$self->{open_elements}}, $node_i if $node_i < 0;
7974
7975 !!!next-token;
7976 last S2;
7977 } else {
7978 ## Step 3
7979 if (not ($node->[1] & FORMATTING_EL) and
7980 #not $phrasing_category->{$node->[1]} and
7981 ($node->[1] & SPECIAL_EL or
7982 $node->[1] & SCOPING_EL)) {
7983 !!!cp ('t433');
7984 !!!parse-error (type => 'unmatched end tag',
7985 text => $token->{tag_name}, token => $token);
7986 ## Ignore the token
7987 !!!next-token;
7988 last S2;
7989
7990 ## NOTE: |<span><dd></span>a|: In Safari 3.1.2 and Opera
7991 ## 9.27, "a" is a child of <dd> (conforming). In
7992 ## Firefox 3.0.2, "a" is a child of <body>. In WinIE 7,
7993 ## "a" is a child of both <body> and <dd>.
7994 }
7995
7996 !!!cp ('t434');
7997 }
7998
7999 ## Step 4
8000 $node_i--;
8001 $node = $self->{open_elements}->[$node_i];
8002
8003 ## Step 5;
8004 redo S2;
8005 } # S2
8006 next B;
8007 }
8008 }
8009 next B;
8010 } continue { # B
8011 if ($self->{insertion_mode} & IN_FOREIGN_CONTENT_IM) {
8012 ## NOTE: The code below is executed in cases where it does not have
8013 ## to be, but it it is harmless even in those cases.
8014 ## has an element in scope
8015 INSCOPE: {
8016 for (reverse 0..$#{$self->{open_elements}}) {
8017 my $node = $self->{open_elements}->[$_];
8018 if ($node->[1] & FOREIGN_EL) {
8019 last INSCOPE;
8020 } elsif ($node->[1] & SCOPING_EL) {
8021 last;
8022 }
8023 }
8024
8025 ## NOTE: No foreign element in scope.
8026 $self->{insertion_mode} &= ~ IN_FOREIGN_CONTENT_IM;
8027 } # INSCOPE
8028 }
8029 } # B
8030
8031 ## Stop parsing # MUST
8032
8033 ## TODO: script stuffs
8034 } # _tree_construct_main
8035
8036 sub set_inner_html ($$$$;$) {
8037 my $class = shift;
8038 my $node = shift;
8039 #my $s = \$_[0];
8040 my $onerror = $_[1];
8041 my $get_wrapper = $_[2] || sub ($) { return $_[0] };
8042
8043 ## ISSUE: Should {confident} be true?
8044
8045 my $nt = $node->node_type;
8046 if ($nt == 9) {
8047 # MUST
8048
8049 ## Step 1 # MUST
8050 ## TODO: If the document has an active parser, ...
8051 ## ISSUE: There is an issue in the spec.
8052
8053 ## Step 2 # MUST
8054 my @cn = @{$node->child_nodes};
8055 for (@cn) {
8056 $node->remove_child ($_);
8057 }
8058
8059 ## Step 3, 4, 5 # MUST
8060 $class->parse_char_string ($_[0] => $node, $onerror, $get_wrapper);
8061 } elsif ($nt == 1) {
8062 ## TODO: If non-html element
8063
8064 ## NOTE: Most of this code is copied from |parse_string|
8065
8066 ## TODO: Support for $get_wrapper
8067
8068 ## Step 1 # MUST
8069 my $this_doc = $node->owner_document;
8070 my $doc = $this_doc->implementation->create_document;
8071 $doc->manakai_is_html (1);
8072 my $p = $class->new;
8073 $p->{document} = $doc;
8074
8075 ## Step 8 # MUST
8076 my $i = 0;
8077 $p->{line_prev} = $p->{line} = 1;
8078 $p->{column_prev} = $p->{column} = 0;
8079 require Whatpm::Charset::DecodeHandle;
8080 my $input = Whatpm::Charset::DecodeHandle::CharString->new (\($_[0]));
8081 $input = $get_wrapper->($input);
8082 $p->{set_nc} = sub {
8083 my $self = shift;
8084
8085 my $char = '';
8086 if (defined $self->{next_nc}) {
8087 $char = $self->{next_nc};
8088 delete $self->{next_nc};
8089 $self->{nc} = ord $char;
8090 } else {
8091 $self->{char_buffer} = '';
8092 $self->{char_buffer_pos} = 0;
8093
8094 my $count = $input->manakai_read_until
8095 ($self->{char_buffer}, qr/[^\x00\x0A\x0D]/,
8096 $self->{char_buffer_pos});
8097 if ($count) {
8098 $self->{line_prev} = $self->{line};
8099 $self->{column_prev} = $self->{column};
8100 $self->{column}++;
8101 $self->{nc}
8102 = ord substr ($self->{char_buffer},
8103 $self->{char_buffer_pos}++, 1);
8104 return;
8105 }
8106
8107 if ($input->read ($char, 1)) {
8108 $self->{nc} = ord $char;
8109 } else {
8110 $self->{nc} = -1;
8111 return;
8112 }
8113 }
8114
8115 ($p->{line_prev}, $p->{column_prev}) = ($p->{line}, $p->{column});
8116 $p->{column}++;
8117
8118 if ($self->{nc} == 0x000A) { # LF
8119 $p->{line}++;
8120 $p->{column} = 0;
8121 !!!cp ('i1');
8122 } elsif ($self->{nc} == 0x000D) { # CR
8123 ## TODO: support for abort/streaming
8124 my $next = '';
8125 if ($input->read ($next, 1) and $next ne "\x0A") {
8126 $self->{next_nc} = $next;
8127 }
8128 $self->{nc} = 0x000A; # LF # MUST
8129 $p->{line}++;
8130 $p->{column} = 0;
8131 !!!cp ('i2');
8132 } elsif ($self->{nc} == 0x0000) { # NULL
8133 !!!cp ('i4');
8134 !!!parse-error (type => 'NULL');
8135 $self->{nc} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
8136 }
8137 };
8138
8139 $p->{read_until} = sub {
8140 #my ($scalar, $specials_range, $offset) = @_;
8141 return 0 if defined $p->{next_nc};
8142
8143 my $pattern = qr/[^$_[1]\x00\x0A\x0D]/;
8144 my $offset = $_[2] || 0;
8145
8146 if ($p->{char_buffer_pos} < length $p->{char_buffer}) {
8147 pos ($p->{char_buffer}) = $p->{char_buffer_pos};
8148 if ($p->{char_buffer} =~ /\G(?>$pattern)+/) {
8149 substr ($_[0], $offset)
8150 = substr ($p->{char_buffer}, $-[0], $+[0] - $-[0]);
8151 my $count = $+[0] - $-[0];
8152 if ($count) {
8153 $p->{column} += $count;
8154 $p->{char_buffer_pos} += $count;
8155 $p->{line_prev} = $p->{line};
8156 $p->{column_prev} = $p->{column} - 1;
8157 $p->{nc} = -1;
8158 }
8159 return $count;
8160 } else {
8161 return 0;
8162 }
8163 } else {
8164 my $count = $input->manakai_read_until ($_[0], $pattern, $_[2]);
8165 if ($count) {
8166 $p->{column} += $count;
8167 $p->{column_prev} += $count;
8168 $p->{nc} = -1;
8169 }
8170 return $count;
8171 }
8172 }; # $p->{read_until}
8173
8174 my $ponerror = $onerror || sub {
8175 my (%opt) = @_;
8176 my $line = $opt{line};
8177 my $column = $opt{column};
8178 if (defined $opt{token} and defined $opt{token}->{line}) {
8179 $line = $opt{token}->{line};
8180 $column = $opt{token}->{column};
8181 }
8182 warn "Parse error ($opt{type}) at line $line column $column\n";
8183 };
8184 $p->{parse_error} = sub {
8185 $ponerror->(line => $p->{line}, column => $p->{column}, @_);
8186 };
8187
8188 my $char_onerror = sub {
8189 my (undef, $type, %opt) = @_;
8190 $ponerror->(layer => 'encode',
8191 line => $p->{line}, column => $p->{column} + 1,
8192 %opt, type => $type);
8193 }; # $char_onerror
8194 $input->onerror ($char_onerror);
8195
8196 $p->_initialize_tokenizer;
8197 $p->_initialize_tree_constructor;
8198
8199 ## Step 2
8200 my $node_ln = $node->manakai_local_name;
8201 $p->{content_model} = {
8202 title => RCDATA_CONTENT_MODEL,
8203 textarea => RCDATA_CONTENT_MODEL,
8204 style => CDATA_CONTENT_MODEL,
8205 script => CDATA_CONTENT_MODEL,
8206 xmp => CDATA_CONTENT_MODEL,
8207 iframe => CDATA_CONTENT_MODEL,
8208 noembed => CDATA_CONTENT_MODEL,
8209 noframes => CDATA_CONTENT_MODEL,
8210 noscript => CDATA_CONTENT_MODEL,
8211 plaintext => PLAINTEXT_CONTENT_MODEL,
8212 }->{$node_ln};
8213 $p->{content_model} = PCDATA_CONTENT_MODEL
8214 unless defined $p->{content_model};
8215 ## ISSUE: What is "the name of the element"? local name?
8216
8217 $p->{inner_html_node} = [$node, $el_category->{$node_ln}];
8218 ## TODO: Foreign element OK?
8219
8220 ## Step 3
8221 my $root = $doc->create_element_ns
8222 ('http://www.w3.org/1999/xhtml', [undef, 'html']);
8223
8224 ## Step 4 # MUST
8225 $doc->append_child ($root);
8226
8227 ## Step 5 # MUST
8228 push @{$p->{open_elements}}, [$root, $el_category->{html}];
8229
8230 undef $p->{head_element};
8231 undef $p->{head_element_inserted};
8232
8233 ## Step 6 # MUST
8234 $p->_reset_insertion_mode;
8235
8236 ## Step 7 # MUST
8237 my $anode = $node;
8238 AN: while (defined $anode) {
8239 if ($anode->node_type == 1) {
8240 my $nsuri = $anode->namespace_uri;
8241 if (defined $nsuri and $nsuri eq 'http://www.w3.org/1999/xhtml') {
8242 if ($anode->manakai_local_name eq 'form') {
8243 !!!cp ('i5');
8244 $p->{form_element} = $anode;
8245 last AN;
8246 }
8247 }
8248 }
8249 $anode = $anode->parent_node;
8250 } # AN
8251
8252 ## Step 9 # MUST
8253 {
8254 my $self = $p;
8255 !!!next-token;
8256 }
8257 $p->_tree_construction_main;
8258
8259 ## Step 10 # MUST
8260 my @cn = @{$node->child_nodes};
8261 for (@cn) {
8262 $node->remove_child ($_);
8263 }
8264 ## ISSUE: mutation events? read-only?
8265
8266 ## Step 11 # MUST
8267 @cn = @{$root->child_nodes};
8268 for (@cn) {
8269 $this_doc->adopt_node ($_);
8270 $node->append_child ($_);
8271 }
8272 ## ISSUE: mutation events?
8273
8274 $p->_terminate_tree_constructor;
8275
8276 delete $p->{parse_error}; # delete loop
8277 } else {
8278 die "$0: |set_inner_html| is not defined for node of type $nt";
8279 }
8280 } # set_inner_html
8281
8282 } # tree construction stage
8283
8284 package Whatpm::HTML::RestartParser;
8285 push our @ISA, 'Error';
8286
8287 1;
8288 # $Date: 2008/10/04 17:16:02 $

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24