/[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.169 - (show annotations) (download) (as text)
Sat Sep 13 11:31:09 2008 UTC (18 years ago) by wakaba
Branch: MAIN
Changes since 1.168: +59 -74 lines
File MIME type: application/x-wais-source
++ whatpm/Whatpm/ChangeLog	13 Sep 2008 11:31:02 -0000
	* HTML.pm.src: Remove |{char}|, which is no longer used.
	Remove |{entity_in_attr}| and |{last_attribute_value_state}|
	and replaced by |{prev_state}|.

	* mkhtmlparser.pl: Remove |{char}| feature.
	Remove |!!!back-next-input-character;| macro.

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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24