/[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.191 - (show annotations) (download) (as text)
Mon Sep 22 06:04:29 2008 UTC (18 years ago) by wakaba
Branch: MAIN
Changes since 1.190: +17 -27 lines
File MIME type: application/x-wais-source
++ whatpm/t/ChangeLog	22 Sep 2008 05:59:48 -0000
	* tokenizer-test-1.test: Test data on invalid character references
	are added (cf. HTML5 revision 2138).

	* tokenizer-test-2.dat: Test data on U+000B are updated (HTML5
	revision 2138).

2008-09-22  Wakaba  <wakaba@suika.fam.cx>

++ whatpm/Whatpm/ChangeLog	22 Sep 2008 06:02:01 -0000
2008-09-22  Wakaba  <wakaba@suika.fam.cx>

	* HTML.pm.src: Character references for non-space C0 characters,
	including U+000B VT, DEL character, noncharacter code points, are
	now converted to the U+FFFD character (cf. HTML5 revision 2138).

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24