/[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.161 - (show annotations) (download) (as text)
Wed Sep 10 10:46:50 2008 UTC (18 years ago) by wakaba
Branch: MAIN
Changes since 1.160: +12 -10 lines
File MIME type: application/x-wais-source
++ whatpm/Whatpm/ChangeLog	10 Sep 2008 10:46:48 -0000
	* HTML.pm.src: Ignore punctuations in charset names.

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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24