/[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.80 - (show annotations) (download) (as text)
Tue Mar 4 00:03:13 2008 UTC (18 years, 7 months ago) by wakaba
Branch: MAIN
Changes since 1.79: +5 -4 lines
File MIME type: application/x-wais-source
++ whatpm/t/ChangeLog	4 Mar 2008 00:02:19 -0000
2008-03-04  Wakaba  <wakaba@suika.fam.cx>

	* tree-test-1.dat: Missing tests from the initial stage are added.

++ whatpm/Whatpm/ChangeLog	4 Mar 2008 00:02:58 -0000
2008-03-04  Wakaba  <wakaba@suika.fam.cx>

	* HTML.pm.src (_tree_construction_initial): Some limited quirks
	doctypes were not uppercased for comparison.

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24