/[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.170 - (show annotations) (download) (as text)
Sat Sep 13 12:25:44 2008 UTC (18 years ago) by wakaba
Branch: MAIN
Changes since 1.169: +20 -4 lines
File MIME type: application/x-wais-source
++ whatpm/Whatpm/ChangeLog	13 Sep 2008 12:25:41 -0000
	* HTML.pm.src: Check points added to newly added branches.

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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24