/[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.50 - (show annotations) (download) (as text)
Sat Jul 21 10:59:40 2007 UTC (19 years, 2 months ago) by wakaba
Branch: MAIN
Changes since 1.49: +113 -94 lines
File MIME type: application/x-wais-source
++ whatpm/Whatpm/ChangeLog	21 Jul 2007 10:59:21 -0000
	* HTML.pm.src: The "before head" insertion mode is
	merged with the "in head" insertion mode.

2007-07-21  Wakaba  <wakaba@suika.fam.cx>

1 package Whatpm::HTML;
2 use strict;
3 our $VERSION=do{my @r=(q$Revision: 1.49 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
4
5 ## ISSUE:
6 ## var doc = implementation.createDocument (null, null, null);
7 ## doc.write ('');
8 ## alert (doc.compatMode);
9
10 ## ISSUE: HTML5 revision 967 says that the encoding layer MUST NOT
11 ## strip BOM and the HTML layer MUST ignore it. Whether we can do it
12 ## is not yet clear.
13 ## "{U+FEFF}..." in UTF-16BE/UTF-16LE is three or four characters?
14 ## "{U+FEFF}..." in GB18030?
15
16 my $permitted_slash_tag_name = {
17 base => 1,
18 link => 1,
19 meta => 1,
20 hr => 1,
21 br => 1,
22 img=> 1,
23 embed => 1,
24 param => 1,
25 area => 1,
26 col => 1,
27 input => 1,
28 };
29
30 my $c1_entity_char = {
31 0x80 => 0x20AC,
32 0x81 => 0xFFFD,
33 0x82 => 0x201A,
34 0x83 => 0x0192,
35 0x84 => 0x201E,
36 0x85 => 0x2026,
37 0x86 => 0x2020,
38 0x87 => 0x2021,
39 0x88 => 0x02C6,
40 0x89 => 0x2030,
41 0x8A => 0x0160,
42 0x8B => 0x2039,
43 0x8C => 0x0152,
44 0x8D => 0xFFFD,
45 0x8E => 0x017D,
46 0x8F => 0xFFFD,
47 0x90 => 0xFFFD,
48 0x91 => 0x2018,
49 0x92 => 0x2019,
50 0x93 => 0x201C,
51 0x94 => 0x201D,
52 0x95 => 0x2022,
53 0x96 => 0x2013,
54 0x97 => 0x2014,
55 0x98 => 0x02DC,
56 0x99 => 0x2122,
57 0x9A => 0x0161,
58 0x9B => 0x203A,
59 0x9C => 0x0153,
60 0x9D => 0xFFFD,
61 0x9E => 0x017E,
62 0x9F => 0x0178,
63 }; # $c1_entity_char
64
65 my $special_category = {
66 address => 1, area => 1, base => 1, basefont => 1, bgsound => 1,
67 blockquote => 1, body => 1, br => 1, center => 1, col => 1, colgroup => 1,
68 dd => 1, dir => 1, div => 1, dl => 1, dt => 1, embed => 1, fieldset => 1,
69 form => 1, frame => 1, frameset => 1, h1 => 1, h2 => 1, h3 => 1,
70 h4 => 1, h5 => 1, h6 => 1, head => 1, hr => 1, iframe => 1, image => 1,
71 img => 1, input => 1, isindex => 1, li => 1, link => 1, listing => 1,
72 menu => 1, meta => 1, noembed => 1, noframes => 1, noscript => 1,
73 ol => 1, optgroup => 1, option => 1, p => 1, param => 1, plaintext => 1,
74 pre => 1, script => 1, select => 1, spacer => 1, style => 1, tbody => 1,
75 textarea => 1, tfoot => 1, thead => 1, title => 1, tr => 1, ul => 1, wbr => 1,
76 };
77 my $scoping_category = {
78 button => 1, caption => 1, html => 1, marquee => 1, object => 1,
79 table => 1, td => 1, th => 1,
80 };
81 my $formatting_category = {
82 a => 1, b => 1, big => 1, em => 1, font => 1, i => 1, nobr => 1,
83 s => 1, small => 1, strile => 1, strong => 1, tt => 1, u => 1,
84 };
85 # $phrasing_category: all other elements
86
87 sub parse_string ($$$;$) {
88 my $self = shift->new;
89 my $s = \$_[0];
90 $self->{document} = $_[1];
91
92 ## NOTE: |set_inner_html| copies most of this method's code
93
94 my $i = 0;
95 my $line = 1;
96 my $column = 0;
97 $self->{set_next_input_character} = sub {
98 my $self = shift;
99
100 pop @{$self->{prev_input_character}};
101 unshift @{$self->{prev_input_character}}, $self->{next_input_character};
102
103 $self->{next_input_character} = -1 and return if $i >= length $$s;
104 $self->{next_input_character} = ord substr $$s, $i++, 1;
105 $column++;
106
107 if ($self->{next_input_character} == 0x000A) { # LF
108 $line++;
109 $column = 0;
110 } elsif ($self->{next_input_character} == 0x000D) { # CR
111 $i++ if substr ($$s, $i, 1) eq "\x0A";
112 $self->{next_input_character} = 0x000A; # LF # MUST
113 $line++;
114 $column = 0;
115 } elsif ($self->{next_input_character} > 0x10FFFF) {
116 $self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
117 } elsif ($self->{next_input_character} == 0x0000) { # NULL
118 !!!parse-error (type => 'NULL');
119 $self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
120 }
121 };
122 $self->{prev_input_character} = [-1, -1, -1];
123 $self->{next_input_character} = -1;
124
125 my $onerror = $_[2] || sub {
126 my (%opt) = @_;
127 warn "Parse error ($opt{type}) at line $opt{line} column $opt{column}\n";
128 };
129 $self->{parse_error} = sub {
130 $onerror->(@_, line => $line, column => $column);
131 };
132
133 $self->_initialize_tokenizer;
134 $self->_initialize_tree_constructor;
135 $self->_construct_tree;
136 $self->_terminate_tree_constructor;
137
138 return $self->{document};
139 } # parse_string
140
141 sub new ($) {
142 my $class = shift;
143 my $self = bless {}, $class;
144 $self->{set_next_input_character} = sub {
145 $self->{next_input_character} = -1;
146 };
147 $self->{parse_error} = sub {
148 #
149 };
150 return $self;
151 } # new
152
153 sub CM_ENTITY () { 0b001 } # & markup in data
154 sub CM_LIMITED_MARKUP () { 0b010 } # < markup in data (limited)
155 sub CM_FULL_MARKUP () { 0b100 } # < markup in data (any)
156
157 sub PLAINTEXT_CONTENT_MODEL () { 0 }
158 sub CDATA_CONTENT_MODEL () { CM_LIMITED_MARKUP }
159 sub RCDATA_CONTENT_MODEL () { CM_ENTITY | CM_LIMITED_MARKUP }
160 sub PCDATA_CONTENT_MODEL () { CM_ENTITY | CM_FULL_MARKUP }
161
162 ## Implementations MUST act as if state machine in the spec
163
164 sub _initialize_tokenizer ($) {
165 my $self = shift;
166 $self->{state} = 'data'; # MUST
167 $self->{content_model} = PCDATA_CONTENT_MODEL; # be
168 undef $self->{current_token}; # start tag, end tag, comment, or DOCTYPE
169 undef $self->{current_attribute};
170 undef $self->{last_emitted_start_tag_name};
171 undef $self->{last_attribute_value_state};
172 $self->{char} = [];
173 # $self->{next_input_character}
174 !!!next-input-character;
175 $self->{token} = [];
176 # $self->{escape}
177 } # _initialize_tokenizer
178
179 ## A token has:
180 ## ->{type} eq 'DOCTYPE', 'start tag', 'end tag', 'comment',
181 ## 'character', or 'end-of-file'
182 ## ->{name} (DOCTYPE, start tag (tag name), end tag (tag name))
183 ## ->{public_identifier} (DOCTYPE)
184 ## ->{system_identifier} (DOCTYPE)
185 ## ->{correct} == 1 or 0 (DOCTYPE)
186 ## ->{attributes} isa HASH (start tag, end tag)
187 ## ->{data} (comment, character)
188
189 ## Emitted token MUST immediately be handled by the tree construction state.
190
191 ## Before each step, UA MAY check to see if either one of the scripts in
192 ## "list of scripts that will execute as soon as possible" or the first
193 ## script in the "list of scripts that will execute asynchronously",
194 ## has completed loading. If one has, then it MUST be executed
195 ## and removed from the list.
196
197 sub _get_next_token ($) {
198 my $self = shift;
199 if (@{$self->{token}}) {
200 return shift @{$self->{token}};
201 }
202
203 A: {
204 if ($self->{state} eq 'data') {
205 if ($self->{next_input_character} == 0x0026) { # &
206 if ($self->{content_model} & CM_ENTITY) { # PCDATA | RCDATA
207 $self->{state} = 'entity data';
208 !!!next-input-character;
209 redo A;
210 } else {
211 #
212 }
213 } elsif ($self->{next_input_character} == 0x002D) { # -
214 if ($self->{content_model} & CM_LIMITED_MARKUP) { # RCDATA | CDATA
215 unless ($self->{escape}) {
216 if ($self->{prev_input_character}->[0] == 0x002D and # -
217 $self->{prev_input_character}->[1] == 0x0021 and # !
218 $self->{prev_input_character}->[2] == 0x003C) { # <
219 $self->{escape} = 1;
220 }
221 }
222 }
223
224 #
225 } elsif ($self->{next_input_character} == 0x003C) { # <
226 if ($self->{content_model} & CM_FULL_MARKUP or # PCDATA
227 (($self->{content_model} & CM_LIMITED_MARKUP) and # CDATA | RCDATA
228 not $self->{escape})) {
229 $self->{state} = 'tag open';
230 !!!next-input-character;
231 redo A;
232 } else {
233 #
234 }
235 } elsif ($self->{next_input_character} == 0x003E) { # >
236 if ($self->{escape} and
237 ($self->{content_model} & CM_LIMITED_MARKUP)) { # RCDATA | CDATA
238 if ($self->{prev_input_character}->[0] == 0x002D and # -
239 $self->{prev_input_character}->[1] == 0x002D) { # -
240 delete $self->{escape};
241 }
242 }
243
244 #
245 } elsif ($self->{next_input_character} == -1) {
246 !!!emit ({type => 'end-of-file'});
247 last A; ## TODO: ok?
248 }
249 # Anything else
250 my $token = {type => 'character',
251 data => chr $self->{next_input_character}};
252 ## Stay in the data state
253 !!!next-input-character;
254
255 !!!emit ($token);
256
257 redo A;
258 } elsif ($self->{state} eq 'entity data') {
259 ## (cannot happen in CDATA state)
260
261 my $token = $self->_tokenize_attempt_to_consume_an_entity (0);
262
263 $self->{state} = 'data';
264 # next-input-character is already done
265
266 unless (defined $token) {
267 !!!emit ({type => 'character', data => '&'});
268 } else {
269 !!!emit ($token);
270 }
271
272 redo A;
273 } elsif ($self->{state} eq 'tag open') {
274 if ($self->{content_model} & CM_LIMITED_MARKUP) { # RCDATA | CDATA
275 if ($self->{next_input_character} == 0x002F) { # /
276 !!!next-input-character;
277 $self->{state} = 'close tag open';
278 redo A;
279 } else {
280 ## reconsume
281 $self->{state} = 'data';
282
283 !!!emit ({type => 'character', data => '<'});
284
285 redo A;
286 }
287 } elsif ($self->{content_model} & CM_FULL_MARKUP) { # PCDATA
288 if ($self->{next_input_character} == 0x0021) { # !
289 $self->{state} = 'markup declaration open';
290 !!!next-input-character;
291 redo A;
292 } elsif ($self->{next_input_character} == 0x002F) { # /
293 $self->{state} = 'close tag open';
294 !!!next-input-character;
295 redo A;
296 } elsif (0x0041 <= $self->{next_input_character} and
297 $self->{next_input_character} <= 0x005A) { # A..Z
298 $self->{current_token}
299 = {type => 'start tag',
300 tag_name => chr ($self->{next_input_character} + 0x0020)};
301 $self->{state} = 'tag name';
302 !!!next-input-character;
303 redo A;
304 } elsif (0x0061 <= $self->{next_input_character} and
305 $self->{next_input_character} <= 0x007A) { # a..z
306 $self->{current_token} = {type => 'start tag',
307 tag_name => chr ($self->{next_input_character})};
308 $self->{state} = 'tag name';
309 !!!next-input-character;
310 redo A;
311 } elsif ($self->{next_input_character} == 0x003E) { # >
312 !!!parse-error (type => 'empty start tag');
313 $self->{state} = 'data';
314 !!!next-input-character;
315
316 !!!emit ({type => 'character', data => '<>'});
317
318 redo A;
319 } elsif ($self->{next_input_character} == 0x003F) { # ?
320 !!!parse-error (type => 'pio');
321 $self->{state} = 'bogus comment';
322 ## $self->{next_input_character} is intentionally left as is
323 redo A;
324 } else {
325 !!!parse-error (type => 'bare stago');
326 $self->{state} = 'data';
327 ## reconsume
328
329 !!!emit ({type => 'character', data => '<'});
330
331 redo A;
332 }
333 } else {
334 die "$0: $self->{content_model} in tag open";
335 }
336 } elsif ($self->{state} eq 'close tag open') {
337 if ($self->{content_model} & CM_LIMITED_MARKUP) { # RCDATA | CDATA
338 if (defined $self->{last_emitted_start_tag_name}) {
339 ## NOTE: <http://krijnhoetmer.nl/irc-logs/whatwg/20070626#l-564>
340 my @next_char;
341 TAGNAME: for (my $i = 0; $i < length $self->{last_emitted_start_tag_name}; $i++) {
342 push @next_char, $self->{next_input_character};
343 my $c = ord substr ($self->{last_emitted_start_tag_name}, $i, 1);
344 my $C = 0x0061 <= $c && $c <= 0x007A ? $c - 0x0020 : $c;
345 if ($self->{next_input_character} == $c or $self->{next_input_character} == $C) {
346 !!!next-input-character;
347 next TAGNAME;
348 } else {
349 $self->{next_input_character} = shift @next_char; # reconsume
350 !!!back-next-input-character (@next_char);
351 $self->{state} = 'data';
352
353 !!!emit ({type => 'character', data => '</'});
354
355 redo A;
356 }
357 }
358 push @next_char, $self->{next_input_character};
359
360 unless ($self->{next_input_character} == 0x0009 or # HT
361 $self->{next_input_character} == 0x000A or # LF
362 $self->{next_input_character} == 0x000B or # VT
363 $self->{next_input_character} == 0x000C or # FF
364 $self->{next_input_character} == 0x0020 or # SP
365 $self->{next_input_character} == 0x003E or # >
366 $self->{next_input_character} == 0x002F or # /
367 $self->{next_input_character} == -1) {
368 $self->{next_input_character} = shift @next_char; # reconsume
369 !!!back-next-input-character (@next_char);
370 $self->{state} = 'data';
371 !!!emit ({type => 'character', data => '</'});
372 redo A;
373 } else {
374 $self->{next_input_character} = shift @next_char;
375 !!!back-next-input-character (@next_char);
376 # and consume...
377 }
378 } else {
379 ## No start tag token has ever been emitted
380 # next-input-character is already done
381 $self->{state} = 'data';
382 !!!emit ({type => 'character', data => '</'});
383 redo A;
384 }
385 }
386
387 if (0x0041 <= $self->{next_input_character} and
388 $self->{next_input_character} <= 0x005A) { # A..Z
389 $self->{current_token} = {type => 'end tag',
390 tag_name => chr ($self->{next_input_character} + 0x0020)};
391 $self->{state} = 'tag name';
392 !!!next-input-character;
393 redo A;
394 } elsif (0x0061 <= $self->{next_input_character} and
395 $self->{next_input_character} <= 0x007A) { # a..z
396 $self->{current_token} = {type => 'end tag',
397 tag_name => chr ($self->{next_input_character})};
398 $self->{state} = 'tag name';
399 !!!next-input-character;
400 redo A;
401 } elsif ($self->{next_input_character} == 0x003E) { # >
402 !!!parse-error (type => 'empty end tag');
403 $self->{state} = 'data';
404 !!!next-input-character;
405 redo A;
406 } elsif ($self->{next_input_character} == -1) {
407 !!!parse-error (type => 'bare etago');
408 $self->{state} = 'data';
409 # reconsume
410
411 !!!emit ({type => 'character', data => '</'});
412
413 redo A;
414 } else {
415 !!!parse-error (type => 'bogus end tag');
416 $self->{state} = 'bogus comment';
417 ## $self->{next_input_character} is intentionally left as is
418 redo A;
419 }
420 } elsif ($self->{state} eq 'tag name') {
421 if ($self->{next_input_character} == 0x0009 or # HT
422 $self->{next_input_character} == 0x000A or # LF
423 $self->{next_input_character} == 0x000B or # VT
424 $self->{next_input_character} == 0x000C or # FF
425 $self->{next_input_character} == 0x0020) { # SP
426 $self->{state} = 'before attribute name';
427 !!!next-input-character;
428 redo A;
429 } elsif ($self->{next_input_character} == 0x003E) { # >
430 if ($self->{current_token}->{type} eq 'start tag') {
431 $self->{current_token}->{first_start_tag}
432 = not defined $self->{last_emitted_start_tag_name};
433 $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};
434 } elsif ($self->{current_token}->{type} eq 'end tag') {
435 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
436 if ($self->{current_token}->{attributes}) {
437 !!!parse-error (type => 'end tag attribute');
438 }
439 } else {
440 die "$0: $self->{current_token}->{type}: Unknown token type";
441 }
442 $self->{state} = 'data';
443 !!!next-input-character;
444
445 !!!emit ($self->{current_token}); # start tag or end tag
446
447 redo A;
448 } elsif (0x0041 <= $self->{next_input_character} and
449 $self->{next_input_character} <= 0x005A) { # A..Z
450 $self->{current_token}->{tag_name} .= chr ($self->{next_input_character} + 0x0020);
451 # start tag or end tag
452 ## Stay in this state
453 !!!next-input-character;
454 redo A;
455 } elsif ($self->{next_input_character} == -1) {
456 !!!parse-error (type => 'unclosed tag');
457 if ($self->{current_token}->{type} eq 'start tag') {
458 $self->{current_token}->{first_start_tag}
459 = not defined $self->{last_emitted_start_tag_name};
460 $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};
461 } elsif ($self->{current_token}->{type} eq 'end tag') {
462 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
463 if ($self->{current_token}->{attributes}) {
464 !!!parse-error (type => 'end tag attribute');
465 }
466 } else {
467 die "$0: $self->{current_token}->{type}: Unknown token type";
468 }
469 $self->{state} = 'data';
470 # reconsume
471
472 !!!emit ($self->{current_token}); # start tag or end tag
473
474 redo A;
475 } elsif ($self->{next_input_character} == 0x002F) { # /
476 !!!next-input-character;
477 if ($self->{next_input_character} == 0x003E and # >
478 $self->{current_token}->{type} eq 'start tag' and
479 $permitted_slash_tag_name->{$self->{current_token}->{tag_name}}) {
480 # permitted slash
481 #
482 } else {
483 !!!parse-error (type => 'nestc');
484 }
485 $self->{state} = 'before attribute name';
486 # next-input-character is already done
487 redo A;
488 } else {
489 $self->{current_token}->{tag_name} .= chr $self->{next_input_character};
490 # start tag or end tag
491 ## Stay in the state
492 !!!next-input-character;
493 redo A;
494 }
495 } elsif ($self->{state} eq 'before attribute name') {
496 if ($self->{next_input_character} == 0x0009 or # HT
497 $self->{next_input_character} == 0x000A or # LF
498 $self->{next_input_character} == 0x000B or # VT
499 $self->{next_input_character} == 0x000C or # FF
500 $self->{next_input_character} == 0x0020) { # SP
501 ## Stay in the state
502 !!!next-input-character;
503 redo A;
504 } elsif ($self->{next_input_character} == 0x003E) { # >
505 if ($self->{current_token}->{type} eq 'start tag') {
506 $self->{current_token}->{first_start_tag}
507 = not defined $self->{last_emitted_start_tag_name};
508 $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};
509 } elsif ($self->{current_token}->{type} eq 'end tag') {
510 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
511 if ($self->{current_token}->{attributes}) {
512 !!!parse-error (type => 'end tag attribute');
513 }
514 } else {
515 die "$0: $self->{current_token}->{type}: Unknown token type";
516 }
517 $self->{state} = 'data';
518 !!!next-input-character;
519
520 !!!emit ($self->{current_token}); # start tag or end tag
521
522 redo A;
523 } elsif (0x0041 <= $self->{next_input_character} and
524 $self->{next_input_character} <= 0x005A) { # A..Z
525 $self->{current_attribute} = {name => chr ($self->{next_input_character} + 0x0020),
526 value => ''};
527 $self->{state} = 'attribute name';
528 !!!next-input-character;
529 redo A;
530 } elsif ($self->{next_input_character} == 0x002F) { # /
531 !!!next-input-character;
532 if ($self->{next_input_character} == 0x003E and # >
533 $self->{current_token}->{type} eq 'start tag' and
534 $permitted_slash_tag_name->{$self->{current_token}->{tag_name}}) {
535 # permitted slash
536 #
537 } else {
538 !!!parse-error (type => 'nestc');
539 }
540 ## Stay in the state
541 # next-input-character is already done
542 redo A;
543 } elsif ($self->{next_input_character} == -1) {
544 !!!parse-error (type => 'unclosed tag');
545 if ($self->{current_token}->{type} eq 'start tag') {
546 $self->{current_token}->{first_start_tag}
547 = not defined $self->{last_emitted_start_tag_name};
548 $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};
549 } elsif ($self->{current_token}->{type} eq 'end tag') {
550 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
551 if ($self->{current_token}->{attributes}) {
552 !!!parse-error (type => 'end tag attribute');
553 }
554 } else {
555 die "$0: $self->{current_token}->{type}: Unknown token type";
556 }
557 $self->{state} = 'data';
558 # reconsume
559
560 !!!emit ($self->{current_token}); # start tag or end tag
561
562 redo A;
563 } else {
564 $self->{current_attribute} = {name => chr ($self->{next_input_character}),
565 value => ''};
566 $self->{state} = 'attribute name';
567 !!!next-input-character;
568 redo A;
569 }
570 } elsif ($self->{state} eq 'attribute name') {
571 my $before_leave = sub {
572 if (exists $self->{current_token}->{attributes} # start tag or end tag
573 ->{$self->{current_attribute}->{name}}) { # MUST
574 !!!parse-error (type => 'duplicate attribute:'.$self->{current_attribute}->{name});
575 ## Discard $self->{current_attribute} # MUST
576 } else {
577 $self->{current_token}->{attributes}->{$self->{current_attribute}->{name}}
578 = $self->{current_attribute};
579 }
580 }; # $before_leave
581
582 if ($self->{next_input_character} == 0x0009 or # HT
583 $self->{next_input_character} == 0x000A or # LF
584 $self->{next_input_character} == 0x000B or # VT
585 $self->{next_input_character} == 0x000C or # FF
586 $self->{next_input_character} == 0x0020) { # SP
587 $before_leave->();
588 $self->{state} = 'after attribute name';
589 !!!next-input-character;
590 redo A;
591 } elsif ($self->{next_input_character} == 0x003D) { # =
592 $before_leave->();
593 $self->{state} = 'before attribute value';
594 !!!next-input-character;
595 redo A;
596 } elsif ($self->{next_input_character} == 0x003E) { # >
597 $before_leave->();
598 if ($self->{current_token}->{type} eq 'start tag') {
599 $self->{current_token}->{first_start_tag}
600 = not defined $self->{last_emitted_start_tag_name};
601 $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};
602 } elsif ($self->{current_token}->{type} eq 'end tag') {
603 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
604 if ($self->{current_token}->{attributes}) {
605 !!!parse-error (type => 'end tag attribute');
606 }
607 } else {
608 die "$0: $self->{current_token}->{type}: Unknown token type";
609 }
610 $self->{state} = 'data';
611 !!!next-input-character;
612
613 !!!emit ($self->{current_token}); # start tag or end tag
614
615 redo A;
616 } elsif (0x0041 <= $self->{next_input_character} and
617 $self->{next_input_character} <= 0x005A) { # A..Z
618 $self->{current_attribute}->{name} .= chr ($self->{next_input_character} + 0x0020);
619 ## Stay in the state
620 !!!next-input-character;
621 redo A;
622 } elsif ($self->{next_input_character} == 0x002F) { # /
623 $before_leave->();
624 !!!next-input-character;
625 if ($self->{next_input_character} == 0x003E and # >
626 $self->{current_token}->{type} eq 'start tag' and
627 $permitted_slash_tag_name->{$self->{current_token}->{tag_name}}) {
628 # permitted slash
629 #
630 } else {
631 !!!parse-error (type => 'nestc');
632 }
633 $self->{state} = 'before attribute name';
634 # next-input-character is already done
635 redo A;
636 } elsif ($self->{next_input_character} == -1) {
637 !!!parse-error (type => 'unclosed tag');
638 $before_leave->();
639 if ($self->{current_token}->{type} eq 'start tag') {
640 $self->{current_token}->{first_start_tag}
641 = not defined $self->{last_emitted_start_tag_name};
642 $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};
643 } elsif ($self->{current_token}->{type} eq 'end tag') {
644 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
645 if ($self->{current_token}->{attributes}) {
646 !!!parse-error (type => 'end tag attribute');
647 }
648 } else {
649 die "$0: $self->{current_token}->{type}: Unknown token type";
650 }
651 $self->{state} = 'data';
652 # reconsume
653
654 !!!emit ($self->{current_token}); # start tag or end tag
655
656 redo A;
657 } else {
658 $self->{current_attribute}->{name} .= chr ($self->{next_input_character});
659 ## Stay in the state
660 !!!next-input-character;
661 redo A;
662 }
663 } elsif ($self->{state} eq 'after attribute name') {
664 if ($self->{next_input_character} == 0x0009 or # HT
665 $self->{next_input_character} == 0x000A or # LF
666 $self->{next_input_character} == 0x000B or # VT
667 $self->{next_input_character} == 0x000C or # FF
668 $self->{next_input_character} == 0x0020) { # SP
669 ## Stay in the state
670 !!!next-input-character;
671 redo A;
672 } elsif ($self->{next_input_character} == 0x003D) { # =
673 $self->{state} = 'before attribute value';
674 !!!next-input-character;
675 redo A;
676 } elsif ($self->{next_input_character} == 0x003E) { # >
677 if ($self->{current_token}->{type} eq 'start tag') {
678 $self->{current_token}->{first_start_tag}
679 = not defined $self->{last_emitted_start_tag_name};
680 $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};
681 } elsif ($self->{current_token}->{type} eq 'end tag') {
682 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
683 if ($self->{current_token}->{attributes}) {
684 !!!parse-error (type => 'end tag attribute');
685 }
686 } else {
687 die "$0: $self->{current_token}->{type}: Unknown token type";
688 }
689 $self->{state} = 'data';
690 !!!next-input-character;
691
692 !!!emit ($self->{current_token}); # start tag or end tag
693
694 redo A;
695 } elsif (0x0041 <= $self->{next_input_character} and
696 $self->{next_input_character} <= 0x005A) { # A..Z
697 $self->{current_attribute} = {name => chr ($self->{next_input_character} + 0x0020),
698 value => ''};
699 $self->{state} = 'attribute name';
700 !!!next-input-character;
701 redo A;
702 } elsif ($self->{next_input_character} == 0x002F) { # /
703 !!!next-input-character;
704 if ($self->{next_input_character} == 0x003E and # >
705 $self->{current_token}->{type} eq 'start tag' and
706 $permitted_slash_tag_name->{$self->{current_token}->{tag_name}}) {
707 # permitted slash
708 #
709 } else {
710 !!!parse-error (type => 'nestc');
711 ## TODO: Different error type for <aa / bb> than <aa/>
712 }
713 $self->{state} = 'before attribute name';
714 # next-input-character is already done
715 redo A;
716 } elsif ($self->{next_input_character} == -1) {
717 !!!parse-error (type => 'unclosed tag');
718 if ($self->{current_token}->{type} eq 'start tag') {
719 $self->{current_token}->{first_start_tag}
720 = not defined $self->{last_emitted_start_tag_name};
721 $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};
722 } elsif ($self->{current_token}->{type} eq 'end tag') {
723 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
724 if ($self->{current_token}->{attributes}) {
725 !!!parse-error (type => 'end tag attribute');
726 }
727 } else {
728 die "$0: $self->{current_token}->{type}: Unknown token type";
729 }
730 $self->{state} = 'data';
731 # reconsume
732
733 !!!emit ($self->{current_token}); # start tag or end tag
734
735 redo A;
736 } else {
737 $self->{current_attribute} = {name => chr ($self->{next_input_character}),
738 value => ''};
739 $self->{state} = 'attribute name';
740 !!!next-input-character;
741 redo A;
742 }
743 } elsif ($self->{state} eq 'before attribute value') {
744 if ($self->{next_input_character} == 0x0009 or # HT
745 $self->{next_input_character} == 0x000A or # LF
746 $self->{next_input_character} == 0x000B or # VT
747 $self->{next_input_character} == 0x000C or # FF
748 $self->{next_input_character} == 0x0020) { # SP
749 ## Stay in the state
750 !!!next-input-character;
751 redo A;
752 } elsif ($self->{next_input_character} == 0x0022) { # "
753 $self->{state} = 'attribute value (double-quoted)';
754 !!!next-input-character;
755 redo A;
756 } elsif ($self->{next_input_character} == 0x0026) { # &
757 $self->{state} = 'attribute value (unquoted)';
758 ## reconsume
759 redo A;
760 } elsif ($self->{next_input_character} == 0x0027) { # '
761 $self->{state} = 'attribute value (single-quoted)';
762 !!!next-input-character;
763 redo A;
764 } elsif ($self->{next_input_character} == 0x003E) { # >
765 if ($self->{current_token}->{type} eq 'start tag') {
766 $self->{current_token}->{first_start_tag}
767 = not defined $self->{last_emitted_start_tag_name};
768 $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};
769 } elsif ($self->{current_token}->{type} eq 'end tag') {
770 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
771 if ($self->{current_token}->{attributes}) {
772 !!!parse-error (type => 'end tag attribute');
773 }
774 } else {
775 die "$0: $self->{current_token}->{type}: Unknown token type";
776 }
777 $self->{state} = 'data';
778 !!!next-input-character;
779
780 !!!emit ($self->{current_token}); # start tag or end tag
781
782 redo A;
783 } elsif ($self->{next_input_character} == -1) {
784 !!!parse-error (type => 'unclosed tag');
785 if ($self->{current_token}->{type} eq 'start tag') {
786 $self->{current_token}->{first_start_tag}
787 = not defined $self->{last_emitted_start_tag_name};
788 $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};
789 } elsif ($self->{current_token}->{type} eq 'end tag') {
790 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
791 if ($self->{current_token}->{attributes}) {
792 !!!parse-error (type => 'end tag attribute');
793 }
794 } else {
795 die "$0: $self->{current_token}->{type}: Unknown token type";
796 }
797 $self->{state} = 'data';
798 ## reconsume
799
800 !!!emit ($self->{current_token}); # start tag or end tag
801
802 redo A;
803 } else {
804 $self->{current_attribute}->{value} .= chr ($self->{next_input_character});
805 $self->{state} = 'attribute value (unquoted)';
806 !!!next-input-character;
807 redo A;
808 }
809 } elsif ($self->{state} eq 'attribute value (double-quoted)') {
810 if ($self->{next_input_character} == 0x0022) { # "
811 $self->{state} = 'before attribute name';
812 !!!next-input-character;
813 redo A;
814 } elsif ($self->{next_input_character} == 0x0026) { # &
815 $self->{last_attribute_value_state} = 'attribute value (double-quoted)';
816 $self->{state} = 'entity in attribute value';
817 !!!next-input-character;
818 redo A;
819 } elsif ($self->{next_input_character} == -1) {
820 !!!parse-error (type => 'unclosed attribute value');
821 if ($self->{current_token}->{type} eq 'start tag') {
822 $self->{current_token}->{first_start_tag}
823 = not defined $self->{last_emitted_start_tag_name};
824 $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};
825 } elsif ($self->{current_token}->{type} eq 'end tag') {
826 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
827 if ($self->{current_token}->{attributes}) {
828 !!!parse-error (type => 'end tag attribute');
829 }
830 } else {
831 die "$0: $self->{current_token}->{type}: Unknown token type";
832 }
833 $self->{state} = 'data';
834 ## reconsume
835
836 !!!emit ($self->{current_token}); # start tag or end tag
837
838 redo A;
839 } else {
840 $self->{current_attribute}->{value} .= chr ($self->{next_input_character});
841 ## Stay in the state
842 !!!next-input-character;
843 redo A;
844 }
845 } elsif ($self->{state} eq 'attribute value (single-quoted)') {
846 if ($self->{next_input_character} == 0x0027) { # '
847 $self->{state} = 'before attribute name';
848 !!!next-input-character;
849 redo A;
850 } elsif ($self->{next_input_character} == 0x0026) { # &
851 $self->{last_attribute_value_state} = 'attribute value (single-quoted)';
852 $self->{state} = 'entity in attribute value';
853 !!!next-input-character;
854 redo A;
855 } elsif ($self->{next_input_character} == -1) {
856 !!!parse-error (type => 'unclosed attribute value');
857 if ($self->{current_token}->{type} eq 'start tag') {
858 $self->{current_token}->{first_start_tag}
859 = not defined $self->{last_emitted_start_tag_name};
860 $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};
861 } elsif ($self->{current_token}->{type} eq 'end tag') {
862 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
863 if ($self->{current_token}->{attributes}) {
864 !!!parse-error (type => 'end tag attribute');
865 }
866 } else {
867 die "$0: $self->{current_token}->{type}: Unknown token type";
868 }
869 $self->{state} = 'data';
870 ## reconsume
871
872 !!!emit ($self->{current_token}); # start tag or end tag
873
874 redo A;
875 } else {
876 $self->{current_attribute}->{value} .= chr ($self->{next_input_character});
877 ## Stay in the state
878 !!!next-input-character;
879 redo A;
880 }
881 } elsif ($self->{state} eq 'attribute value (unquoted)') {
882 if ($self->{next_input_character} == 0x0009 or # HT
883 $self->{next_input_character} == 0x000A or # LF
884 $self->{next_input_character} == 0x000B or # HT
885 $self->{next_input_character} == 0x000C or # FF
886 $self->{next_input_character} == 0x0020) { # SP
887 $self->{state} = 'before attribute name';
888 !!!next-input-character;
889 redo A;
890 } elsif ($self->{next_input_character} == 0x0026) { # &
891 $self->{last_attribute_value_state} = 'attribute value (unquoted)';
892 $self->{state} = 'entity in attribute value';
893 !!!next-input-character;
894 redo A;
895 } elsif ($self->{next_input_character} == 0x003E) { # >
896 if ($self->{current_token}->{type} eq 'start tag') {
897 $self->{current_token}->{first_start_tag}
898 = not defined $self->{last_emitted_start_tag_name};
899 $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};
900 } elsif ($self->{current_token}->{type} eq 'end tag') {
901 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
902 if ($self->{current_token}->{attributes}) {
903 !!!parse-error (type => 'end tag attribute');
904 }
905 } else {
906 die "$0: $self->{current_token}->{type}: Unknown token type";
907 }
908 $self->{state} = 'data';
909 !!!next-input-character;
910
911 !!!emit ($self->{current_token}); # start tag or end tag
912
913 redo A;
914 } elsif ($self->{next_input_character} == -1) {
915 !!!parse-error (type => 'unclosed tag');
916 if ($self->{current_token}->{type} eq 'start tag') {
917 $self->{current_token}->{first_start_tag}
918 = not defined $self->{last_emitted_start_tag_name};
919 $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};
920 } elsif ($self->{current_token}->{type} eq 'end tag') {
921 $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
922 if ($self->{current_token}->{attributes}) {
923 !!!parse-error (type => 'end tag attribute');
924 }
925 } else {
926 die "$0: $self->{current_token}->{type}: Unknown token type";
927 }
928 $self->{state} = 'data';
929 ## reconsume
930
931 !!!emit ($self->{current_token}); # start tag or end tag
932
933 redo A;
934 } else {
935 $self->{current_attribute}->{value} .= chr ($self->{next_input_character});
936 ## Stay in the state
937 !!!next-input-character;
938 redo A;
939 }
940 } elsif ($self->{state} eq 'entity in attribute value') {
941 my $token = $self->_tokenize_attempt_to_consume_an_entity (1);
942
943 unless (defined $token) {
944 $self->{current_attribute}->{value} .= '&';
945 } else {
946 $self->{current_attribute}->{value} .= $token->{data};
947 ## ISSUE: spec says "append the returned character token to the current attribute's value"
948 }
949
950 $self->{state} = $self->{last_attribute_value_state};
951 # next-input-character is already done
952 redo A;
953 } elsif ($self->{state} eq 'bogus comment') {
954 ## (only happen if PCDATA state)
955
956 my $token = {type => 'comment', data => ''};
957
958 BC: {
959 if ($self->{next_input_character} == 0x003E) { # >
960 $self->{state} = 'data';
961 !!!next-input-character;
962
963 !!!emit ($token);
964
965 redo A;
966 } elsif ($self->{next_input_character} == -1) {
967 $self->{state} = 'data';
968 ## reconsume
969
970 !!!emit ($token);
971
972 redo A;
973 } else {
974 $token->{data} .= chr ($self->{next_input_character});
975 !!!next-input-character;
976 redo BC;
977 }
978 } # BC
979 } elsif ($self->{state} eq 'markup declaration open') {
980 ## (only happen if PCDATA state)
981
982 my @next_char;
983 push @next_char, $self->{next_input_character};
984
985 if ($self->{next_input_character} == 0x002D) { # -
986 !!!next-input-character;
987 push @next_char, $self->{next_input_character};
988 if ($self->{next_input_character} == 0x002D) { # -
989 $self->{current_token} = {type => 'comment', data => ''};
990 $self->{state} = 'comment start';
991 !!!next-input-character;
992 redo A;
993 }
994 } elsif ($self->{next_input_character} == 0x0044 or # D
995 $self->{next_input_character} == 0x0064) { # d
996 !!!next-input-character;
997 push @next_char, $self->{next_input_character};
998 if ($self->{next_input_character} == 0x004F or # O
999 $self->{next_input_character} == 0x006F) { # o
1000 !!!next-input-character;
1001 push @next_char, $self->{next_input_character};
1002 if ($self->{next_input_character} == 0x0043 or # C
1003 $self->{next_input_character} == 0x0063) { # c
1004 !!!next-input-character;
1005 push @next_char, $self->{next_input_character};
1006 if ($self->{next_input_character} == 0x0054 or # T
1007 $self->{next_input_character} == 0x0074) { # t
1008 !!!next-input-character;
1009 push @next_char, $self->{next_input_character};
1010 if ($self->{next_input_character} == 0x0059 or # Y
1011 $self->{next_input_character} == 0x0079) { # y
1012 !!!next-input-character;
1013 push @next_char, $self->{next_input_character};
1014 if ($self->{next_input_character} == 0x0050 or # P
1015 $self->{next_input_character} == 0x0070) { # p
1016 !!!next-input-character;
1017 push @next_char, $self->{next_input_character};
1018 if ($self->{next_input_character} == 0x0045 or # E
1019 $self->{next_input_character} == 0x0065) { # e
1020 ## ISSUE: What a stupid code this is!
1021 $self->{state} = 'DOCTYPE';
1022 !!!next-input-character;
1023 redo A;
1024 }
1025 }
1026 }
1027 }
1028 }
1029 }
1030 }
1031
1032 !!!parse-error (type => 'bogus comment');
1033 $self->{next_input_character} = shift @next_char;
1034 !!!back-next-input-character (@next_char);
1035 $self->{state} = 'bogus comment';
1036 redo A;
1037
1038 ## ISSUE: typos in spec: chacacters, is is a parse error
1039 ## 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?
1040 } elsif ($self->{state} eq 'comment start') {
1041 if ($self->{next_input_character} == 0x002D) { # -
1042 $self->{state} = 'comment start dash';
1043 !!!next-input-character;
1044 redo A;
1045 } elsif ($self->{next_input_character} == 0x003E) { # >
1046 !!!parse-error (type => 'bogus comment');
1047 $self->{state} = 'data';
1048 !!!next-input-character;
1049
1050 !!!emit ($self->{current_token}); # comment
1051
1052 redo A;
1053 } elsif ($self->{next_input_character} == -1) {
1054 !!!parse-error (type => 'unclosed comment');
1055 $self->{state} = 'data';
1056 ## reconsume
1057
1058 !!!emit ($self->{current_token}); # comment
1059
1060 redo A;
1061 } else {
1062 $self->{current_token}->{data} # comment
1063 .= chr ($self->{next_input_character});
1064 $self->{state} = 'comment';
1065 !!!next-input-character;
1066 redo A;
1067 }
1068 } elsif ($self->{state} eq 'comment start dash') {
1069 if ($self->{next_input_character} == 0x002D) { # -
1070 $self->{state} = 'comment end';
1071 !!!next-input-character;
1072 redo A;
1073 } elsif ($self->{next_input_character} == 0x003E) { # >
1074 !!!parse-error (type => 'bogus comment');
1075 $self->{state} = 'data';
1076 !!!next-input-character;
1077
1078 !!!emit ($self->{current_token}); # comment
1079
1080 redo A;
1081 } elsif ($self->{next_input_character} == -1) {
1082 !!!parse-error (type => 'unclosed comment');
1083 $self->{state} = 'data';
1084 ## reconsume
1085
1086 !!!emit ($self->{current_token}); # comment
1087
1088 redo A;
1089 } else {
1090 $self->{current_token}->{data} # comment
1091 .= '-' . chr ($self->{next_input_character});
1092 $self->{state} = 'comment';
1093 !!!next-input-character;
1094 redo A;
1095 }
1096 } elsif ($self->{state} eq 'comment') {
1097 if ($self->{next_input_character} == 0x002D) { # -
1098 $self->{state} = 'comment end dash';
1099 !!!next-input-character;
1100 redo A;
1101 } elsif ($self->{next_input_character} == -1) {
1102 !!!parse-error (type => 'unclosed comment');
1103 $self->{state} = 'data';
1104 ## reconsume
1105
1106 !!!emit ($self->{current_token}); # comment
1107
1108 redo A;
1109 } else {
1110 $self->{current_token}->{data} .= chr ($self->{next_input_character}); # comment
1111 ## Stay in the state
1112 !!!next-input-character;
1113 redo A;
1114 }
1115 } elsif ($self->{state} eq 'comment end dash') {
1116 if ($self->{next_input_character} == 0x002D) { # -
1117 $self->{state} = 'comment end';
1118 !!!next-input-character;
1119 redo A;
1120 } elsif ($self->{next_input_character} == -1) {
1121 !!!parse-error (type => 'unclosed comment');
1122 $self->{state} = 'data';
1123 ## reconsume
1124
1125 !!!emit ($self->{current_token}); # comment
1126
1127 redo A;
1128 } else {
1129 $self->{current_token}->{data} .= '-' . chr ($self->{next_input_character}); # comment
1130 $self->{state} = 'comment';
1131 !!!next-input-character;
1132 redo A;
1133 }
1134 } elsif ($self->{state} eq 'comment end') {
1135 if ($self->{next_input_character} == 0x003E) { # >
1136 $self->{state} = 'data';
1137 !!!next-input-character;
1138
1139 !!!emit ($self->{current_token}); # comment
1140
1141 redo A;
1142 } elsif ($self->{next_input_character} == 0x002D) { # -
1143 !!!parse-error (type => 'dash in comment');
1144 $self->{current_token}->{data} .= '-'; # comment
1145 ## Stay in the state
1146 !!!next-input-character;
1147 redo A;
1148 } elsif ($self->{next_input_character} == -1) {
1149 !!!parse-error (type => 'unclosed comment');
1150 $self->{state} = 'data';
1151 ## reconsume
1152
1153 !!!emit ($self->{current_token}); # comment
1154
1155 redo A;
1156 } else {
1157 !!!parse-error (type => 'dash in comment');
1158 $self->{current_token}->{data} .= '--' . chr ($self->{next_input_character}); # comment
1159 $self->{state} = 'comment';
1160 !!!next-input-character;
1161 redo A;
1162 }
1163 } elsif ($self->{state} eq 'DOCTYPE') {
1164 if ($self->{next_input_character} == 0x0009 or # HT
1165 $self->{next_input_character} == 0x000A or # LF
1166 $self->{next_input_character} == 0x000B or # VT
1167 $self->{next_input_character} == 0x000C or # FF
1168 $self->{next_input_character} == 0x0020) { # SP
1169 $self->{state} = 'before DOCTYPE name';
1170 !!!next-input-character;
1171 redo A;
1172 } else {
1173 !!!parse-error (type => 'no space before DOCTYPE name');
1174 $self->{state} = 'before DOCTYPE name';
1175 ## reconsume
1176 redo A;
1177 }
1178 } elsif ($self->{state} eq 'before DOCTYPE name') {
1179 if ($self->{next_input_character} == 0x0009 or # HT
1180 $self->{next_input_character} == 0x000A or # LF
1181 $self->{next_input_character} == 0x000B or # VT
1182 $self->{next_input_character} == 0x000C or # FF
1183 $self->{next_input_character} == 0x0020) { # SP
1184 ## Stay in the state
1185 !!!next-input-character;
1186 redo A;
1187 } elsif ($self->{next_input_character} == 0x003E) { # >
1188 !!!parse-error (type => 'no DOCTYPE name');
1189 $self->{state} = 'data';
1190 !!!next-input-character;
1191
1192 !!!emit ({type => 'DOCTYPE'}); # incorrect
1193
1194 redo A;
1195 } elsif ($self->{next_input_character} == -1) {
1196 !!!parse-error (type => 'no DOCTYPE name');
1197 $self->{state} = 'data';
1198 ## reconsume
1199
1200 !!!emit ({type => 'DOCTYPE'}); # incorrect
1201
1202 redo A;
1203 } else {
1204 $self->{current_token}
1205 = {type => 'DOCTYPE',
1206 name => chr ($self->{next_input_character}),
1207 correct => 1};
1208 ## ISSUE: "Set the token's name name to the" in the spec
1209 $self->{state} = 'DOCTYPE name';
1210 !!!next-input-character;
1211 redo A;
1212 }
1213 } elsif ($self->{state} eq 'DOCTYPE name') {
1214 ## ISSUE: Redundant "First," in the spec.
1215 if ($self->{next_input_character} == 0x0009 or # HT
1216 $self->{next_input_character} == 0x000A or # LF
1217 $self->{next_input_character} == 0x000B or # VT
1218 $self->{next_input_character} == 0x000C or # FF
1219 $self->{next_input_character} == 0x0020) { # SP
1220 $self->{state} = 'after DOCTYPE name';
1221 !!!next-input-character;
1222 redo A;
1223 } elsif ($self->{next_input_character} == 0x003E) { # >
1224 $self->{state} = 'data';
1225 !!!next-input-character;
1226
1227 !!!emit ($self->{current_token}); # DOCTYPE
1228
1229 redo A;
1230 } elsif ($self->{next_input_character} == -1) {
1231 !!!parse-error (type => 'unclosed DOCTYPE');
1232 $self->{state} = 'data';
1233 ## reconsume
1234
1235 delete $self->{current_token}->{correct};
1236 !!!emit ($self->{current_token}); # DOCTYPE
1237
1238 redo A;
1239 } else {
1240 $self->{current_token}->{name}
1241 .= chr ($self->{next_input_character}); # DOCTYPE
1242 ## Stay in the state
1243 !!!next-input-character;
1244 redo A;
1245 }
1246 } elsif ($self->{state} eq 'after DOCTYPE name') {
1247 if ($self->{next_input_character} == 0x0009 or # HT
1248 $self->{next_input_character} == 0x000A or # LF
1249 $self->{next_input_character} == 0x000B or # VT
1250 $self->{next_input_character} == 0x000C or # FF
1251 $self->{next_input_character} == 0x0020) { # SP
1252 ## Stay in the state
1253 !!!next-input-character;
1254 redo A;
1255 } elsif ($self->{next_input_character} == 0x003E) { # >
1256 $self->{state} = 'data';
1257 !!!next-input-character;
1258
1259 !!!emit ($self->{current_token}); # DOCTYPE
1260
1261 redo A;
1262 } elsif ($self->{next_input_character} == -1) {
1263 !!!parse-error (type => 'unclosed DOCTYPE');
1264 $self->{state} = 'data';
1265 ## reconsume
1266
1267 delete $self->{current_token}->{correct};
1268 !!!emit ($self->{current_token}); # DOCTYPE
1269
1270 redo A;
1271 } elsif ($self->{next_input_character} == 0x0050 or # P
1272 $self->{next_input_character} == 0x0070) { # p
1273 !!!next-input-character;
1274 if ($self->{next_input_character} == 0x0055 or # U
1275 $self->{next_input_character} == 0x0075) { # u
1276 !!!next-input-character;
1277 if ($self->{next_input_character} == 0x0042 or # B
1278 $self->{next_input_character} == 0x0062) { # b
1279 !!!next-input-character;
1280 if ($self->{next_input_character} == 0x004C or # L
1281 $self->{next_input_character} == 0x006C) { # l
1282 !!!next-input-character;
1283 if ($self->{next_input_character} == 0x0049 or # I
1284 $self->{next_input_character} == 0x0069) { # i
1285 !!!next-input-character;
1286 if ($self->{next_input_character} == 0x0043 or # C
1287 $self->{next_input_character} == 0x0063) { # c
1288 $self->{state} = 'before DOCTYPE public identifier';
1289 !!!next-input-character;
1290 redo A;
1291 }
1292 }
1293 }
1294 }
1295 }
1296
1297 #
1298 } elsif ($self->{next_input_character} == 0x0053 or # S
1299 $self->{next_input_character} == 0x0073) { # s
1300 !!!next-input-character;
1301 if ($self->{next_input_character} == 0x0059 or # Y
1302 $self->{next_input_character} == 0x0079) { # y
1303 !!!next-input-character;
1304 if ($self->{next_input_character} == 0x0053 or # S
1305 $self->{next_input_character} == 0x0073) { # s
1306 !!!next-input-character;
1307 if ($self->{next_input_character} == 0x0054 or # T
1308 $self->{next_input_character} == 0x0074) { # t
1309 !!!next-input-character;
1310 if ($self->{next_input_character} == 0x0045 or # E
1311 $self->{next_input_character} == 0x0065) { # e
1312 !!!next-input-character;
1313 if ($self->{next_input_character} == 0x004D or # M
1314 $self->{next_input_character} == 0x006D) { # m
1315 $self->{state} = 'before DOCTYPE system identifier';
1316 !!!next-input-character;
1317 redo A;
1318 }
1319 }
1320 }
1321 }
1322 }
1323
1324 #
1325 } else {
1326 !!!next-input-character;
1327 #
1328 }
1329
1330 !!!parse-error (type => 'string after DOCTYPE name');
1331 $self->{state} = 'bogus DOCTYPE';
1332 # next-input-character is already done
1333 redo A;
1334 } elsif ($self->{state} eq 'before DOCTYPE public identifier') {
1335 if ({
1336 0x0009 => 1, 0x000A => 1, 0x000B => 1, 0x000C => 1, 0x0020 => 1,
1337 #0x000D => 1, # HT, LF, VT, FF, SP, CR
1338 }->{$self->{next_input_character}}) {
1339 ## Stay in the state
1340 !!!next-input-character;
1341 redo A;
1342 } elsif ($self->{next_input_character} eq 0x0022) { # "
1343 $self->{current_token}->{public_identifier} = ''; # DOCTYPE
1344 $self->{state} = 'DOCTYPE public identifier (double-quoted)';
1345 !!!next-input-character;
1346 redo A;
1347 } elsif ($self->{next_input_character} eq 0x0027) { # '
1348 $self->{current_token}->{public_identifier} = ''; # DOCTYPE
1349 $self->{state} = 'DOCTYPE public identifier (single-quoted)';
1350 !!!next-input-character;
1351 redo A;
1352 } elsif ($self->{next_input_character} eq 0x003E) { # >
1353 !!!parse-error (type => 'no PUBLIC literal');
1354
1355 $self->{state} = 'data';
1356 !!!next-input-character;
1357
1358 delete $self->{current_token}->{correct};
1359 !!!emit ($self->{current_token}); # DOCTYPE
1360
1361 redo A;
1362 } elsif ($self->{next_input_character} == -1) {
1363 !!!parse-error (type => 'unclosed DOCTYPE');
1364
1365 $self->{state} = 'data';
1366 ## reconsume
1367
1368 delete $self->{current_token}->{correct};
1369 !!!emit ($self->{current_token}); # DOCTYPE
1370
1371 redo A;
1372 } else {
1373 !!!parse-error (type => 'string after PUBLIC');
1374 $self->{state} = 'bogus DOCTYPE';
1375 !!!next-input-character;
1376 redo A;
1377 }
1378 } elsif ($self->{state} eq 'DOCTYPE public identifier (double-quoted)') {
1379 if ($self->{next_input_character} == 0x0022) { # "
1380 $self->{state} = 'after DOCTYPE public identifier';
1381 !!!next-input-character;
1382 redo A;
1383 } elsif ($self->{next_input_character} == -1) {
1384 !!!parse-error (type => 'unclosed PUBLIC literal');
1385
1386 $self->{state} = 'data';
1387 ## reconsume
1388
1389 delete $self->{current_token}->{correct};
1390 !!!emit ($self->{current_token}); # DOCTYPE
1391
1392 redo A;
1393 } else {
1394 $self->{current_token}->{public_identifier} # DOCTYPE
1395 .= chr $self->{next_input_character};
1396 ## Stay in the state
1397 !!!next-input-character;
1398 redo A;
1399 }
1400 } elsif ($self->{state} eq 'DOCTYPE public identifier (single-quoted)') {
1401 if ($self->{next_input_character} == 0x0027) { # '
1402 $self->{state} = 'after DOCTYPE public identifier';
1403 !!!next-input-character;
1404 redo A;
1405 } elsif ($self->{next_input_character} == -1) {
1406 !!!parse-error (type => 'unclosed PUBLIC literal');
1407
1408 $self->{state} = 'data';
1409 ## reconsume
1410
1411 delete $self->{current_token}->{correct};
1412 !!!emit ($self->{current_token}); # DOCTYPE
1413
1414 redo A;
1415 } else {
1416 $self->{current_token}->{public_identifier} # DOCTYPE
1417 .= chr $self->{next_input_character};
1418 ## Stay in the state
1419 !!!next-input-character;
1420 redo A;
1421 }
1422 } elsif ($self->{state} eq 'after DOCTYPE public identifier') {
1423 if ({
1424 0x0009 => 1, 0x000A => 1, 0x000B => 1, 0x000C => 1, 0x0020 => 1,
1425 #0x000D => 1, # HT, LF, VT, FF, SP, CR
1426 }->{$self->{next_input_character}}) {
1427 ## Stay in the state
1428 !!!next-input-character;
1429 redo A;
1430 } elsif ($self->{next_input_character} == 0x0022) { # "
1431 $self->{current_token}->{system_identifier} = ''; # DOCTYPE
1432 $self->{state} = 'DOCTYPE system identifier (double-quoted)';
1433 !!!next-input-character;
1434 redo A;
1435 } elsif ($self->{next_input_character} == 0x0027) { # '
1436 $self->{current_token}->{system_identifier} = ''; # DOCTYPE
1437 $self->{state} = 'DOCTYPE system identifier (single-quoted)';
1438 !!!next-input-character;
1439 redo A;
1440 } elsif ($self->{next_input_character} == 0x003E) { # >
1441 $self->{state} = 'data';
1442 !!!next-input-character;
1443
1444 !!!emit ($self->{current_token}); # DOCTYPE
1445
1446 redo A;
1447 } elsif ($self->{next_input_character} == -1) {
1448 !!!parse-error (type => 'unclosed DOCTYPE');
1449
1450 $self->{state} = 'data';
1451 ## reconsume
1452
1453 delete $self->{current_token}->{correct};
1454 !!!emit ($self->{current_token}); # DOCTYPE
1455
1456 redo A;
1457 } else {
1458 !!!parse-error (type => 'string after PUBLIC literal');
1459 $self->{state} = 'bogus DOCTYPE';
1460 !!!next-input-character;
1461 redo A;
1462 }
1463 } elsif ($self->{state} eq 'before DOCTYPE system identifier') {
1464 if ({
1465 0x0009 => 1, 0x000A => 1, 0x000B => 1, 0x000C => 1, 0x0020 => 1,
1466 #0x000D => 1, # HT, LF, VT, FF, SP, CR
1467 }->{$self->{next_input_character}}) {
1468 ## Stay in the state
1469 !!!next-input-character;
1470 redo A;
1471 } elsif ($self->{next_input_character} == 0x0022) { # "
1472 $self->{current_token}->{system_identifier} = ''; # DOCTYPE
1473 $self->{state} = 'DOCTYPE system identifier (double-quoted)';
1474 !!!next-input-character;
1475 redo A;
1476 } elsif ($self->{next_input_character} == 0x0027) { # '
1477 $self->{current_token}->{system_identifier} = ''; # DOCTYPE
1478 $self->{state} = 'DOCTYPE system identifier (single-quoted)';
1479 !!!next-input-character;
1480 redo A;
1481 } elsif ($self->{next_input_character} == 0x003E) { # >
1482 !!!parse-error (type => 'no SYSTEM literal');
1483 $self->{state} = 'data';
1484 !!!next-input-character;
1485
1486 delete $self->{current_token}->{correct};
1487 !!!emit ($self->{current_token}); # DOCTYPE
1488
1489 redo A;
1490 } elsif ($self->{next_input_character} == -1) {
1491 !!!parse-error (type => 'unclosed DOCTYPE');
1492
1493 $self->{state} = 'data';
1494 ## reconsume
1495
1496 delete $self->{current_token}->{correct};
1497 !!!emit ($self->{current_token}); # DOCTYPE
1498
1499 redo A;
1500 } else {
1501 !!!parse-error (type => 'string after SYSTEM');
1502 $self->{state} = 'bogus DOCTYPE';
1503 !!!next-input-character;
1504 redo A;
1505 }
1506 } elsif ($self->{state} eq 'DOCTYPE system identifier (double-quoted)') {
1507 if ($self->{next_input_character} == 0x0022) { # "
1508 $self->{state} = 'after DOCTYPE system identifier';
1509 !!!next-input-character;
1510 redo A;
1511 } elsif ($self->{next_input_character} == -1) {
1512 !!!parse-error (type => 'unclosed SYSTEM literal');
1513
1514 $self->{state} = 'data';
1515 ## reconsume
1516
1517 delete $self->{current_token}->{correct};
1518 !!!emit ($self->{current_token}); # DOCTYPE
1519
1520 redo A;
1521 } else {
1522 $self->{current_token}->{system_identifier} # DOCTYPE
1523 .= chr $self->{next_input_character};
1524 ## Stay in the state
1525 !!!next-input-character;
1526 redo A;
1527 }
1528 } elsif ($self->{state} eq 'DOCTYPE system identifier (single-quoted)') {
1529 if ($self->{next_input_character} == 0x0027) { # '
1530 $self->{state} = 'after DOCTYPE system identifier';
1531 !!!next-input-character;
1532 redo A;
1533 } elsif ($self->{next_input_character} == -1) {
1534 !!!parse-error (type => 'unclosed SYSTEM literal');
1535
1536 $self->{state} = 'data';
1537 ## reconsume
1538
1539 delete $self->{current_token}->{correct};
1540 !!!emit ($self->{current_token}); # DOCTYPE
1541
1542 redo A;
1543 } else {
1544 $self->{current_token}->{system_identifier} # DOCTYPE
1545 .= chr $self->{next_input_character};
1546 ## Stay in the state
1547 !!!next-input-character;
1548 redo A;
1549 }
1550 } elsif ($self->{state} eq 'after DOCTYPE system identifier') {
1551 if ({
1552 0x0009 => 1, 0x000A => 1, 0x000B => 1, 0x000C => 1, 0x0020 => 1,
1553 #0x000D => 1, # HT, LF, VT, FF, SP, CR
1554 }->{$self->{next_input_character}}) {
1555 ## Stay in the state
1556 !!!next-input-character;
1557 redo A;
1558 } elsif ($self->{next_input_character} == 0x003E) { # >
1559 $self->{state} = 'data';
1560 !!!next-input-character;
1561
1562 !!!emit ($self->{current_token}); # DOCTYPE
1563
1564 redo A;
1565 } elsif ($self->{next_input_character} == -1) {
1566 !!!parse-error (type => 'unclosed DOCTYPE');
1567
1568 $self->{state} = 'data';
1569 ## reconsume
1570
1571 delete $self->{current_token}->{correct};
1572 !!!emit ($self->{current_token}); # DOCTYPE
1573
1574 redo A;
1575 } else {
1576 !!!parse-error (type => 'string after SYSTEM literal');
1577 $self->{state} = 'bogus DOCTYPE';
1578 !!!next-input-character;
1579 redo A;
1580 }
1581 } elsif ($self->{state} eq 'bogus DOCTYPE') {
1582 if ($self->{next_input_character} == 0x003E) { # >
1583 $self->{state} = 'data';
1584 !!!next-input-character;
1585
1586 delete $self->{current_token}->{correct};
1587 !!!emit ($self->{current_token}); # DOCTYPE
1588
1589 redo A;
1590 } elsif ($self->{next_input_character} == -1) {
1591 !!!parse-error (type => 'unclosed DOCTYPE');
1592 $self->{state} = 'data';
1593 ## reconsume
1594
1595 delete $self->{current_token}->{correct};
1596 !!!emit ($self->{current_token}); # DOCTYPE
1597
1598 redo A;
1599 } else {
1600 ## Stay in the state
1601 !!!next-input-character;
1602 redo A;
1603 }
1604 } else {
1605 die "$0: $self->{state}: Unknown state";
1606 }
1607 } # A
1608
1609 die "$0: _get_next_token: unexpected case";
1610 } # _get_next_token
1611
1612 sub _tokenize_attempt_to_consume_an_entity ($$) {
1613 my ($self, $in_attr) = @_;
1614
1615 if ({
1616 0x0009 => 1, 0x000A => 1, 0x000B => 1, 0x000C => 1, # HT, LF, VT, FF,
1617 0x0020 => 1, 0x003C => 1, 0x0026 => 1, -1 => 1, # SP, <, & # 0x000D # CR
1618 }->{$self->{next_input_character}}) {
1619 ## Don't consume
1620 ## No error
1621 return undef;
1622 } elsif ($self->{next_input_character} == 0x0023) { # #
1623 !!!next-input-character;
1624 if ($self->{next_input_character} == 0x0078 or # x
1625 $self->{next_input_character} == 0x0058) { # X
1626 my $code;
1627 X: {
1628 my $x_char = $self->{next_input_character};
1629 !!!next-input-character;
1630 if (0x0030 <= $self->{next_input_character} and
1631 $self->{next_input_character} <= 0x0039) { # 0..9
1632 $code ||= 0;
1633 $code *= 0x10;
1634 $code += $self->{next_input_character} - 0x0030;
1635 redo X;
1636 } elsif (0x0061 <= $self->{next_input_character} and
1637 $self->{next_input_character} <= 0x0066) { # a..f
1638 $code ||= 0;
1639 $code *= 0x10;
1640 $code += $self->{next_input_character} - 0x0060 + 9;
1641 redo X;
1642 } elsif (0x0041 <= $self->{next_input_character} and
1643 $self->{next_input_character} <= 0x0046) { # A..F
1644 $code ||= 0;
1645 $code *= 0x10;
1646 $code += $self->{next_input_character} - 0x0040 + 9;
1647 redo X;
1648 } elsif (not defined $code) { # no hexadecimal digit
1649 !!!parse-error (type => 'bare hcro');
1650 !!!back-next-input-character ($x_char, $self->{next_input_character});
1651 $self->{next_input_character} = 0x0023; # #
1652 return undef;
1653 } elsif ($self->{next_input_character} == 0x003B) { # ;
1654 !!!next-input-character;
1655 } else {
1656 !!!parse-error (type => 'no refc');
1657 }
1658
1659 if ($code == 0 or (0xD800 <= $code and $code <= 0xDFFF)) {
1660 !!!parse-error (type => sprintf 'invalid character reference:U+%04X', $code);
1661 $code = 0xFFFD;
1662 } elsif ($code > 0x10FFFF) {
1663 !!!parse-error (type => sprintf 'invalid character reference:U-%08X', $code);
1664 $code = 0xFFFD;
1665 } elsif ($code == 0x000D) {
1666 !!!parse-error (type => 'CR character reference');
1667 $code = 0x000A;
1668 } elsif (0x80 <= $code and $code <= 0x9F) {
1669 !!!parse-error (type => sprintf 'C1 character reference:U+%04X', $code);
1670 $code = $c1_entity_char->{$code};
1671 }
1672
1673 return {type => 'character', data => chr $code};
1674 } # X
1675 } elsif (0x0030 <= $self->{next_input_character} and
1676 $self->{next_input_character} <= 0x0039) { # 0..9
1677 my $code = $self->{next_input_character} - 0x0030;
1678 !!!next-input-character;
1679
1680 while (0x0030 <= $self->{next_input_character} and
1681 $self->{next_input_character} <= 0x0039) { # 0..9
1682 $code *= 10;
1683 $code += $self->{next_input_character} - 0x0030;
1684
1685 !!!next-input-character;
1686 }
1687
1688 if ($self->{next_input_character} == 0x003B) { # ;
1689 !!!next-input-character;
1690 } else {
1691 !!!parse-error (type => 'no refc');
1692 }
1693
1694 if ($code == 0 or (0xD800 <= $code and $code <= 0xDFFF)) {
1695 !!!parse-error (type => sprintf 'invalid character reference:U+%04X', $code);
1696 $code = 0xFFFD;
1697 } elsif ($code > 0x10FFFF) {
1698 !!!parse-error (type => sprintf 'invalid character reference:U-%08X', $code);
1699 $code = 0xFFFD;
1700 } elsif ($code == 0x000D) {
1701 !!!parse-error (type => 'CR character reference');
1702 $code = 0x000A;
1703 } elsif (0x80 <= $code and $code <= 0x9F) {
1704 !!!parse-error (type => sprintf 'C1 character reference:U+%04X', $code);
1705 $code = $c1_entity_char->{$code};
1706 }
1707
1708 return {type => 'character', data => chr $code};
1709 } else {
1710 !!!parse-error (type => 'bare nero');
1711 !!!back-next-input-character ($self->{next_input_character});
1712 $self->{next_input_character} = 0x0023; # #
1713 return undef;
1714 }
1715 } elsif ((0x0041 <= $self->{next_input_character} and
1716 $self->{next_input_character} <= 0x005A) or
1717 (0x0061 <= $self->{next_input_character} and
1718 $self->{next_input_character} <= 0x007A)) {
1719 my $entity_name = chr $self->{next_input_character};
1720 !!!next-input-character;
1721
1722 my $value = $entity_name;
1723 my $match = 0;
1724 require Whatpm::_NamedEntityList;
1725 our $EntityChar;
1726
1727 while (length $entity_name < 10 and
1728 ## NOTE: Some number greater than the maximum length of entity name
1729 ((0x0041 <= $self->{next_input_character} and # a
1730 $self->{next_input_character} <= 0x005A) or # x
1731 (0x0061 <= $self->{next_input_character} and # a
1732 $self->{next_input_character} <= 0x007A) or # z
1733 (0x0030 <= $self->{next_input_character} and # 0
1734 $self->{next_input_character} <= 0x0039) or # 9
1735 $self->{next_input_character} == 0x003B)) { # ;
1736 $entity_name .= chr $self->{next_input_character};
1737 if (defined $EntityChar->{$entity_name}) {
1738 if ($self->{next_input_character} == 0x003B) { # ;
1739 $value = $EntityChar->{$entity_name};
1740 $match = 1;
1741 !!!next-input-character;
1742 last;
1743 } else {
1744 $value = $EntityChar->{$entity_name};
1745 $match = -1;
1746 !!!next-input-character;
1747 }
1748 } else {
1749 $value .= chr $self->{next_input_character};
1750 $match *= 2;
1751 !!!next-input-character;
1752 }
1753 }
1754
1755 if ($match > 0) {
1756 return {type => 'character', data => $value};
1757 } elsif ($match < 0) {
1758 !!!parse-error (type => 'no refc');
1759 if ($in_attr and $match < -1) {
1760 return {type => 'character', data => '&'.$entity_name};
1761 } else {
1762 return {type => 'character', data => $value};
1763 }
1764 } else {
1765 !!!parse-error (type => 'bare ero');
1766 ## NOTE: No characters are consumed in the spec.
1767 return {type => 'character', data => '&'.$value};
1768 }
1769 } else {
1770 ## no characters are consumed
1771 !!!parse-error (type => 'bare ero');
1772 return undef;
1773 }
1774 } # _tokenize_attempt_to_consume_an_entity
1775
1776 sub _initialize_tree_constructor ($) {
1777 my $self = shift;
1778 ## NOTE: $self->{document} MUST be specified before this method is called
1779 $self->{document}->strict_error_checking (0);
1780 ## TODO: Turn mutation events off # MUST
1781 ## TODO: Turn loose Document option (manakai extension) on
1782 $self->{document}->manakai_is_html (1); # MUST
1783 } # _initialize_tree_constructor
1784
1785 sub _terminate_tree_constructor ($) {
1786 my $self = shift;
1787 $self->{document}->strict_error_checking (1);
1788 ## TODO: Turn mutation events on
1789 } # _terminate_tree_constructor
1790
1791 ## ISSUE: Should append_child (for example) in script executed in tree construction stage fire mutation events?
1792
1793 { # tree construction stage
1794 my $token;
1795
1796 sub _construct_tree ($) {
1797 my ($self) = @_;
1798
1799 ## When an interactive UA render the $self->{document} available
1800 ## to the user, or when it begin accepting user input, are
1801 ## not defined.
1802
1803 ## Append a character: collect it and all subsequent consecutive
1804 ## characters and insert one Text node whose data is concatenation
1805 ## of all those characters. # MUST
1806
1807 !!!next-token;
1808
1809 $self->{insertion_mode} = 'before head';
1810 undef $self->{form_element};
1811 undef $self->{head_element};
1812 $self->{open_elements} = [];
1813 undef $self->{inner_html_node};
1814
1815 $self->_tree_construction_initial; # MUST
1816 $self->_tree_construction_root_element;
1817 $self->_tree_construction_main;
1818 } # _construct_tree
1819
1820 sub _tree_construction_initial ($) {
1821 my $self = shift;
1822 INITIAL: {
1823 if ($token->{type} eq 'DOCTYPE') {
1824 ## NOTE: Conformance checkers MAY, instead of reporting "not HTML5"
1825 ## error, switch to a conformance checking mode for another
1826 ## language.
1827 my $doctype_name = $token->{name};
1828 $doctype_name = '' unless defined $doctype_name;
1829 $doctype_name =~ tr/a-z/A-Z/;
1830 if (not defined $token->{name} or # <!DOCTYPE>
1831 defined $token->{public_identifier} or
1832 defined $token->{system_identifier}) {
1833 !!!parse-error (type => 'not HTML5');
1834 } elsif ($doctype_name ne 'HTML') {
1835 ## ISSUE: ASCII case-insensitive? (in fact it does not matter)
1836 !!!parse-error (type => 'not HTML5');
1837 }
1838
1839 my $doctype = $self->{document}->create_document_type_definition
1840 ($token->{name}); ## ISSUE: If name is missing (e.g. <!DOCTYPE>)?
1841 $doctype->public_id ($token->{public_identifier})
1842 if defined $token->{public_identifier};
1843 $doctype->system_id ($token->{system_identifier})
1844 if defined $token->{system_identifier};
1845 ## NOTE: Other DocumentType attributes are null or empty lists.
1846 ## ISSUE: internalSubset = null??
1847 $self->{document}->append_child ($doctype);
1848
1849 if (not $token->{correct} or $doctype_name ne 'HTML') {
1850 $self->{document}->manakai_compat_mode ('quirks');
1851 } elsif (defined $token->{public_identifier}) {
1852 my $pubid = $token->{public_identifier};
1853 $pubid =~ tr/a-z/A-z/;
1854 if ({
1855 "+//SILMARIL//DTD HTML PRO V0R11 19970101//EN" => 1,
1856 "-//ADVASOFT LTD//DTD HTML 3.0 ASWEDIT + EXTENSIONS//EN" => 1,
1857 "-//AS//DTD HTML 3.0 ASWEDIT + EXTENSIONS//EN" => 1,
1858 "-//IETF//DTD HTML 2.0 LEVEL 1//EN" => 1,
1859 "-//IETF//DTD HTML 2.0 LEVEL 2//EN" => 1,
1860 "-//IETF//DTD HTML 2.0 STRICT LEVEL 1//EN" => 1,
1861 "-//IETF//DTD HTML 2.0 STRICT LEVEL 2//EN" => 1,
1862 "-//IETF//DTD HTML 2.0 STRICT//EN" => 1,
1863 "-//IETF//DTD HTML 2.0//EN" => 1,
1864 "-//IETF//DTD HTML 2.1E//EN" => 1,
1865 "-//IETF//DTD HTML 3.0//EN" => 1,
1866 "-//IETF//DTD HTML 3.0//EN//" => 1,
1867 "-//IETF//DTD HTML 3.2 FINAL//EN" => 1,
1868 "-//IETF//DTD HTML 3.2//EN" => 1,
1869 "-//IETF//DTD HTML 3//EN" => 1,
1870 "-//IETF//DTD HTML LEVEL 0//EN" => 1,
1871 "-//IETF//DTD HTML LEVEL 0//EN//2.0" => 1,
1872 "-//IETF//DTD HTML LEVEL 1//EN" => 1,
1873 "-//IETF//DTD HTML LEVEL 1//EN//2.0" => 1,
1874 "-//IETF//DTD HTML LEVEL 2//EN" => 1,
1875 "-//IETF//DTD HTML LEVEL 2//EN//2.0" => 1,
1876 "-//IETF//DTD HTML LEVEL 3//EN" => 1,
1877 "-//IETF//DTD HTML LEVEL 3//EN//3.0" => 1,
1878 "-//IETF//DTD HTML STRICT LEVEL 0//EN" => 1,
1879 "-//IETF//DTD HTML STRICT LEVEL 0//EN//2.0" => 1,
1880 "-//IETF//DTD HTML STRICT LEVEL 1//EN" => 1,
1881 "-//IETF//DTD HTML STRICT LEVEL 1//EN//2.0" => 1,
1882 "-//IETF//DTD HTML STRICT LEVEL 2//EN" => 1,
1883 "-//IETF//DTD HTML STRICT LEVEL 2//EN//2.0" => 1,
1884 "-//IETF//DTD HTML STRICT LEVEL 3//EN" => 1,
1885 "-//IETF//DTD HTML STRICT LEVEL 3//EN//3.0" => 1,
1886 "-//IETF//DTD HTML STRICT//EN" => 1,
1887 "-//IETF//DTD HTML STRICT//EN//2.0" => 1,
1888 "-//IETF//DTD HTML STRICT//EN//3.0" => 1,
1889 "-//IETF//DTD HTML//EN" => 1,
1890 "-//IETF//DTD HTML//EN//2.0" => 1,
1891 "-//IETF//DTD HTML//EN//3.0" => 1,
1892 "-//METRIUS//DTD METRIUS PRESENTATIONAL//EN" => 1,
1893 "-//MICROSOFT//DTD INTERNET EXPLORER 2.0 HTML STRICT//EN" => 1,
1894 "-//MICROSOFT//DTD INTERNET EXPLORER 2.0 HTML//EN" => 1,
1895 "-//MICROSOFT//DTD INTERNET EXPLORER 2.0 TABLES//EN" => 1,
1896 "-//MICROSOFT//DTD INTERNET EXPLORER 3.0 HTML STRICT//EN" => 1,
1897 "-//MICROSOFT//DTD INTERNET EXPLORER 3.0 HTML//EN" => 1,
1898 "-//MICROSOFT//DTD INTERNET EXPLORER 3.0 TABLES//EN" => 1,
1899 "-//NETSCAPE COMM. CORP.//DTD HTML//EN" => 1,
1900 "-//NETSCAPE COMM. CORP.//DTD STRICT HTML//EN" => 1,
1901 "-//O'REILLY AND ASSOCIATES//DTD HTML 2.0//EN" => 1,
1902 "-//O'REILLY AND ASSOCIATES//DTD HTML EXTENDED 1.0//EN" => 1,
1903 "-//SPYGLASS//DTD HTML 2.0 EXTENDED//EN" => 1,
1904 "-//SQ//DTD HTML 2.0 HOTMETAL + EXTENSIONS//EN" => 1,
1905 "-//SUN MICROSYSTEMS CORP.//DTD HOTJAVA HTML//EN" => 1,
1906 "-//SUN MICROSYSTEMS CORP.//DTD HOTJAVA STRICT HTML//EN" => 1,
1907 "-//W3C//DTD HTML 3 1995-03-24//EN" => 1,
1908 "-//W3C//DTD HTML 3.2 DRAFT//EN" => 1,
1909 "-//W3C//DTD HTML 3.2 FINAL//EN" => 1,
1910 "-//W3C//DTD HTML 3.2//EN" => 1,
1911 "-//W3C//DTD HTML 3.2S DRAFT//EN" => 1,
1912 "-//W3C//DTD HTML 4.0 FRAMESET//EN" => 1,
1913 "-//W3C//DTD HTML 4.0 TRANSITIONAL//EN" => 1,
1914 "-//W3C//DTD HTML EXPERIMETNAL 19960712//EN" => 1,
1915 "-//W3C//DTD HTML EXPERIMENTAL 970421//EN" => 1,
1916 "-//W3C//DTD W3 HTML//EN" => 1,
1917 "-//W3O//DTD W3 HTML 3.0//EN" => 1,
1918 "-//W3O//DTD W3 HTML 3.0//EN//" => 1,
1919 "-//W3O//DTD W3 HTML STRICT 3.0//EN//" => 1,
1920 "-//WEBTECHS//DTD MOZILLA HTML 2.0//EN" => 1,
1921 "-//WEBTECHS//DTD MOZILLA HTML//EN" => 1,
1922 "-/W3C/DTD HTML 4.0 TRANSITIONAL/EN" => 1,
1923 "HTML" => 1,
1924 }->{$pubid}) {
1925 $self->{document}->manakai_compat_mode ('quirks');
1926 } elsif ($pubid eq "-//W3C//DTD HTML 4.01 FRAMESET//EN" or
1927 $pubid eq "-//W3C//DTD HTML 4.01 TRANSITIONAL//EN") {
1928 if (defined $token->{system_identifier}) {
1929 $self->{document}->manakai_compat_mode ('quirks');
1930 } else {
1931 $self->{document}->manakai_compat_mode ('limited quirks');
1932 }
1933 } elsif ($pubid eq "-//W3C//DTD XHTML 1.0 Frameset//EN" or
1934 $pubid eq "-//W3C//DTD XHTML 1.0 Transitional//EN") {
1935 $self->{document}->manakai_compat_mode ('limited quirks');
1936 }
1937 }
1938 if (defined $token->{system_identifier}) {
1939 my $sysid = $token->{system_identifier};
1940 $sysid =~ tr/A-Z/a-z/;
1941 if ($sysid eq "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd") {
1942 $self->{document}->manakai_compat_mode ('quirks');
1943 }
1944 }
1945
1946 ## Go to the root element phase.
1947 !!!next-token;
1948 return;
1949 } elsif ({
1950 'start tag' => 1,
1951 'end tag' => 1,
1952 'end-of-file' => 1,
1953 }->{$token->{type}}) {
1954 !!!parse-error (type => 'no DOCTYPE');
1955 $self->{document}->manakai_compat_mode ('quirks');
1956 ## Go to the root element phase
1957 ## reprocess
1958 return;
1959 } elsif ($token->{type} eq 'character') {
1960 if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) { # \x0D
1961 ## Ignore the token
1962
1963 unless (length $token->{data}) {
1964 ## Stay in the phase
1965 !!!next-token;
1966 redo INITIAL;
1967 }
1968 }
1969
1970 !!!parse-error (type => 'no DOCTYPE');
1971 $self->{document}->manakai_compat_mode ('quirks');
1972 ## Go to the root element phase
1973 ## reprocess
1974 return;
1975 } elsif ($token->{type} eq 'comment') {
1976 my $comment = $self->{document}->create_comment ($token->{data});
1977 $self->{document}->append_child ($comment);
1978
1979 ## Stay in the phase.
1980 !!!next-token;
1981 redo INITIAL;
1982 } else {
1983 die "$0: $token->{type}: Unknown token";
1984 }
1985 } # INITIAL
1986 } # _tree_construction_initial
1987
1988 sub _tree_construction_root_element ($) {
1989 my $self = shift;
1990
1991 B: {
1992 if ($token->{type} eq 'DOCTYPE') {
1993 !!!parse-error (type => 'in html:#DOCTYPE');
1994 ## Ignore the token
1995 ## Stay in the phase
1996 !!!next-token;
1997 redo B;
1998 } elsif ($token->{type} eq 'comment') {
1999 my $comment = $self->{document}->create_comment ($token->{data});
2000 $self->{document}->append_child ($comment);
2001 ## Stay in the phase
2002 !!!next-token;
2003 redo B;
2004 } elsif ($token->{type} eq 'character') {
2005 if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) { # \x0D
2006 ## Ignore the token.
2007
2008 unless (length $token->{data}) {
2009 ## Stay in the phase
2010 !!!next-token;
2011 redo B;
2012 }
2013 }
2014 #
2015 } elsif ({
2016 'start tag' => 1,
2017 'end tag' => 1,
2018 'end-of-file' => 1,
2019 }->{$token->{type}}) {
2020 ## ISSUE: There is an issue in the spec
2021 #
2022 } else {
2023 die "$0: $token->{type}: Unknown token";
2024 }
2025 my $root_element; !!!create-element ($root_element, 'html');
2026 $self->{document}->append_child ($root_element);
2027 push @{$self->{open_elements}}, [$root_element, 'html'];
2028 ## reprocess
2029 #redo B;
2030 return; ## Go to the main phase.
2031 } # B
2032 } # _tree_construction_root_element
2033
2034 sub _reset_insertion_mode ($) {
2035 my $self = shift;
2036
2037 ## Step 1
2038 my $last;
2039
2040 ## Step 2
2041 my $i = -1;
2042 my $node = $self->{open_elements}->[$i];
2043
2044 ## Step 3
2045 S3: {
2046 ## ISSUE: Oops! "If node is the first node in the stack of open
2047 ## elements, then set last to true. If the context element of the
2048 ## HTML fragment parsing algorithm is neither a td element nor a
2049 ## th element, then set node to the context element. (fragment case)":
2050 ## The second "if" is in the scope of the first "if"!?
2051 if ($self->{open_elements}->[0]->[0] eq $node->[0]) {
2052 $last = 1;
2053 if (defined $self->{inner_html_node}) {
2054 if ($self->{inner_html_node}->[1] eq 'td' or
2055 $self->{inner_html_node}->[1] eq 'th') {
2056 #
2057 } else {
2058 $node = $self->{inner_html_node};
2059 }
2060 }
2061 }
2062
2063 ## Step 4..13
2064 my $new_mode = {
2065 select => 'in select',
2066 td => 'in cell',
2067 th => 'in cell',
2068 tr => 'in row',
2069 tbody => 'in table body',
2070 thead => 'in table body',
2071 tfoot => 'in table body',
2072 caption => 'in caption',
2073 colgroup => 'in column group',
2074 table => 'in table',
2075 head => 'in body', # not in head!
2076 body => 'in body',
2077 frameset => 'in frameset',
2078 }->{$node->[1]};
2079 $self->{insertion_mode} = $new_mode and return if defined $new_mode;
2080
2081 ## Step 14
2082 if ($node->[1] eq 'html') {
2083 unless (defined $self->{head_element}) {
2084 $self->{insertion_mode} = 'before head';
2085 } else {
2086 $self->{insertion_mode} = 'after head';
2087 }
2088 return;
2089 }
2090
2091 ## Step 15
2092 $self->{insertion_mode} = 'in body' and return if $last;
2093
2094 ## Step 16
2095 $i--;
2096 $node = $self->{open_elements}->[$i];
2097
2098 ## Step 17
2099 redo S3;
2100 } # S3
2101 } # _reset_insertion_mode
2102
2103 sub _tree_construction_main ($) {
2104 my $self = shift;
2105
2106 my $previous_insertion_mode;
2107
2108 my $active_formatting_elements = [];
2109
2110 my $reconstruct_active_formatting_elements = sub { # MUST
2111 my $insert = shift;
2112
2113 ## Step 1
2114 return unless @$active_formatting_elements;
2115
2116 ## Step 3
2117 my $i = -1;
2118 my $entry = $active_formatting_elements->[$i];
2119
2120 ## Step 2
2121 return if $entry->[0] eq '#marker';
2122 for (@{$self->{open_elements}}) {
2123 if ($entry->[0] eq $_->[0]) {
2124 return;
2125 }
2126 }
2127
2128 S4: {
2129 ## Step 4
2130 last S4 if $active_formatting_elements->[0]->[0] eq $entry->[0];
2131
2132 ## Step 5
2133 $i--;
2134 $entry = $active_formatting_elements->[$i];
2135
2136 ## Step 6
2137 if ($entry->[0] eq '#marker') {
2138 #
2139 } else {
2140 my $in_open_elements;
2141 OE: for (@{$self->{open_elements}}) {
2142 if ($entry->[0] eq $_->[0]) {
2143 $in_open_elements = 1;
2144 last OE;
2145 }
2146 }
2147 if ($in_open_elements) {
2148 #
2149 } else {
2150 redo S4;
2151 }
2152 }
2153
2154 ## Step 7
2155 $i++;
2156 $entry = $active_formatting_elements->[$i];
2157 } # S4
2158
2159 S7: {
2160 ## Step 8
2161 my $clone = [$entry->[0]->clone_node (0), $entry->[1]];
2162
2163 ## Step 9
2164 $insert->($clone->[0]);
2165 push @{$self->{open_elements}}, $clone;
2166
2167 ## Step 10
2168 $active_formatting_elements->[$i] = $self->{open_elements}->[-1];
2169
2170 ## Step 11
2171 unless ($clone->[0] eq $active_formatting_elements->[-1]->[0]) {
2172 ## Step 7'
2173 $i++;
2174 $entry = $active_formatting_elements->[$i];
2175
2176 redo S7;
2177 }
2178 } # S7
2179 }; # $reconstruct_active_formatting_elements
2180
2181 my $clear_up_to_marker = sub {
2182 for (reverse 0..$#$active_formatting_elements) {
2183 if ($active_formatting_elements->[$_]->[0] eq '#marker') {
2184 splice @$active_formatting_elements, $_;
2185 return;
2186 }
2187 }
2188 }; # $clear_up_to_marker
2189
2190 my $parse_rcdata = sub ($$) {
2191 my ($content_model_flag, $insert) = @_;
2192
2193 ## Step 1
2194 my $start_tag_name = $token->{tag_name};
2195 my $el;
2196 !!!create-element ($el, $start_tag_name, $token->{attributes});
2197
2198 ## Step 2
2199 $insert->($el); # /context node/->append_child ($el)
2200
2201 ## Step 3
2202 $self->{content_model} = $content_model_flag; # CDATA or RCDATA
2203 delete $self->{escape}; # MUST
2204
2205 ## Step 4
2206 my $text = '';
2207 !!!next-token;
2208 while ($token->{type} eq 'character') { # or until stop tokenizing
2209 $text .= $token->{data};
2210 !!!next-token;
2211 }
2212
2213 ## Step 5
2214 if (length $text) {
2215 my $text = $self->{document}->create_text_node ($text);
2216 $el->append_child ($text);
2217 }
2218
2219 ## Step 6
2220 $self->{content_model} = PCDATA_CONTENT_MODEL;
2221
2222 ## Step 7
2223 if ($token->{type} eq 'end tag' and $token->{tag_name} eq $start_tag_name) {
2224 ## Ignore the token
2225 } elsif ($content_model_flag == CDATA_CONTENT_MODEL) {
2226 !!!parse-error (type => 'in CDATA:#'.$token->{type});
2227 } elsif ($content_model_flag == RCDATA_CONTENT_MODEL) {
2228 !!!parse-error (type => 'in RCDATA:#'.$token->{type});
2229 } else {
2230 die "$0: $content_model_flag in parse_rcdata";
2231 }
2232 !!!next-token;
2233 }; # $parse_rcdata
2234
2235 my $script_start_tag = sub ($) {
2236 my $insert = $_[0];
2237 my $script_el;
2238 !!!create-element ($script_el, 'script', $token->{attributes});
2239 ## TODO: mark as "parser-inserted"
2240
2241 $self->{content_model} = CDATA_CONTENT_MODEL;
2242 delete $self->{escape}; # MUST
2243
2244 my $text = '';
2245 !!!next-token;
2246 while ($token->{type} eq 'character') {
2247 $text .= $token->{data};
2248 !!!next-token;
2249 } # stop if non-character token or tokenizer stops tokenising
2250 if (length $text) {
2251 $script_el->manakai_append_text ($text);
2252 }
2253
2254 $self->{content_model} = PCDATA_CONTENT_MODEL;
2255
2256 if ($token->{type} eq 'end tag' and
2257 $token->{tag_name} eq 'script') {
2258 ## Ignore the token
2259 } else {
2260 !!!parse-error (type => 'in CDATA:#'.$token->{type});
2261 ## ISSUE: And ignore?
2262 ## TODO: mark as "already executed"
2263 }
2264
2265 if (defined $self->{inner_html_node}) {
2266 ## TODO: mark as "already executed"
2267 } else {
2268 ## TODO: $old_insertion_point = current insertion point
2269 ## TODO: insertion point = just before the next input character
2270
2271 $insert->($script_el);
2272
2273 ## TODO: insertion point = $old_insertion_point (might be "undefined")
2274
2275 ## TODO: if there is a script that will execute as soon as the parser resume, then...
2276 }
2277
2278 !!!next-token;
2279 }; # $script_start_tag
2280
2281 my $formatting_end_tag = sub {
2282 my $tag_name = shift;
2283
2284 FET: {
2285 ## Step 1
2286 my $formatting_element;
2287 my $formatting_element_i_in_active;
2288 AFE: for (reverse 0..$#$active_formatting_elements) {
2289 if ($active_formatting_elements->[$_]->[1] eq $tag_name) {
2290 $formatting_element = $active_formatting_elements->[$_];
2291 $formatting_element_i_in_active = $_;
2292 last AFE;
2293 } elsif ($active_formatting_elements->[$_]->[0] eq '#marker') {
2294 last AFE;
2295 }
2296 } # AFE
2297 unless (defined $formatting_element) {
2298 !!!parse-error (type => 'unmatched end tag:'.$tag_name);
2299 ## Ignore the token
2300 !!!next-token;
2301 return;
2302 }
2303 ## has an element in scope
2304 my $in_scope = 1;
2305 my $formatting_element_i_in_open;
2306 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
2307 my $node = $self->{open_elements}->[$_];
2308 if ($node->[0] eq $formatting_element->[0]) {
2309 if ($in_scope) {
2310 $formatting_element_i_in_open = $_;
2311 last INSCOPE;
2312 } else { # in open elements but not in scope
2313 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
2314 ## Ignore the token
2315 !!!next-token;
2316 return;
2317 }
2318 } elsif ({
2319 table => 1, caption => 1, td => 1, th => 1,
2320 button => 1, marquee => 1, object => 1, html => 1,
2321 }->{$node->[1]}) {
2322 $in_scope = 0;
2323 }
2324 } # INSCOPE
2325 unless (defined $formatting_element_i_in_open) {
2326 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
2327 pop @$active_formatting_elements; # $formatting_element
2328 !!!next-token; ## TODO: ok?
2329 return;
2330 }
2331 if (not $self->{open_elements}->[-1]->[0] eq $formatting_element->[0]) {
2332 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
2333 }
2334
2335 ## Step 2
2336 my $furthest_block;
2337 my $furthest_block_i_in_open;
2338 OE: for (reverse 0..$#{$self->{open_elements}}) {
2339 my $node = $self->{open_elements}->[$_];
2340 if (not $formatting_category->{$node->[1]} and
2341 #not $phrasing_category->{$node->[1]} and
2342 ($special_category->{$node->[1]} or
2343 $scoping_category->{$node->[1]})) {
2344 $furthest_block = $node;
2345 $furthest_block_i_in_open = $_;
2346 } elsif ($node->[0] eq $formatting_element->[0]) {
2347 last OE;
2348 }
2349 } # OE
2350
2351 ## Step 3
2352 unless (defined $furthest_block) { # MUST
2353 splice @{$self->{open_elements}}, $formatting_element_i_in_open;
2354 splice @$active_formatting_elements, $formatting_element_i_in_active, 1;
2355 !!!next-token;
2356 return;
2357 }
2358
2359 ## Step 4
2360 my $common_ancestor_node = $self->{open_elements}->[$formatting_element_i_in_open - 1];
2361
2362 ## Step 5
2363 my $furthest_block_parent = $furthest_block->[0]->parent_node;
2364 if (defined $furthest_block_parent) {
2365 $furthest_block_parent->remove_child ($furthest_block->[0]);
2366 }
2367
2368 ## Step 6
2369 my $bookmark_prev_el
2370 = $active_formatting_elements->[$formatting_element_i_in_active - 1]
2371 ->[0];
2372
2373 ## Step 7
2374 my $node = $furthest_block;
2375 my $node_i_in_open = $furthest_block_i_in_open;
2376 my $last_node = $furthest_block;
2377 S7: {
2378 ## Step 1
2379 $node_i_in_open--;
2380 $node = $self->{open_elements}->[$node_i_in_open];
2381
2382 ## Step 2
2383 my $node_i_in_active;
2384 S7S2: {
2385 for (reverse 0..$#$active_formatting_elements) {
2386 if ($active_formatting_elements->[$_]->[0] eq $node->[0]) {
2387 $node_i_in_active = $_;
2388 last S7S2;
2389 }
2390 }
2391 splice @{$self->{open_elements}}, $node_i_in_open, 1;
2392 redo S7;
2393 } # S7S2
2394
2395 ## Step 3
2396 last S7 if $node->[0] eq $formatting_element->[0];
2397
2398 ## Step 4
2399 if ($last_node->[0] eq $furthest_block->[0]) {
2400 $bookmark_prev_el = $node->[0];
2401 }
2402
2403 ## Step 5
2404 if ($node->[0]->has_child_nodes ()) {
2405 my $clone = [$node->[0]->clone_node (0), $node->[1]];
2406 $active_formatting_elements->[$node_i_in_active] = $clone;
2407 $self->{open_elements}->[$node_i_in_open] = $clone;
2408 $node = $clone;
2409 }
2410
2411 ## Step 6
2412 $node->[0]->append_child ($last_node->[0]);
2413
2414 ## Step 7
2415 $last_node = $node;
2416
2417 ## Step 8
2418 redo S7;
2419 } # S7
2420
2421 ## Step 8
2422 $common_ancestor_node->[0]->append_child ($last_node->[0]);
2423
2424 ## Step 9
2425 my $clone = [$formatting_element->[0]->clone_node (0),
2426 $formatting_element->[1]];
2427
2428 ## Step 10
2429 my @cn = @{$furthest_block->[0]->child_nodes};
2430 $clone->[0]->append_child ($_) for @cn;
2431
2432 ## Step 11
2433 $furthest_block->[0]->append_child ($clone->[0]);
2434
2435 ## Step 12
2436 my $i;
2437 AFE: for (reverse 0..$#$active_formatting_elements) {
2438 if ($active_formatting_elements->[$_]->[0] eq $formatting_element->[0]) {
2439 splice @$active_formatting_elements, $_, 1;
2440 $i-- and last AFE if defined $i;
2441 } elsif ($active_formatting_elements->[$_]->[0] eq $bookmark_prev_el) {
2442 $i = $_;
2443 }
2444 } # AFE
2445 splice @$active_formatting_elements, $i + 1, 0, $clone;
2446
2447 ## Step 13
2448 undef $i;
2449 OE: for (reverse 0..$#{$self->{open_elements}}) {
2450 if ($self->{open_elements}->[$_]->[0] eq $formatting_element->[0]) {
2451 splice @{$self->{open_elements}}, $_, 1;
2452 $i-- and last OE if defined $i;
2453 } elsif ($self->{open_elements}->[$_]->[0] eq $furthest_block->[0]) {
2454 $i = $_;
2455 }
2456 } # OE
2457 splice @{$self->{open_elements}}, $i + 1, 1, $clone;
2458
2459 ## Step 14
2460 redo FET;
2461 } # FET
2462 }; # $formatting_end_tag
2463
2464 my $insert_to_current = sub {
2465 $self->{open_elements}->[-1]->[0]->append_child ($_[0]);
2466 }; # $insert_to_current
2467
2468 my $insert_to_foster = sub {
2469 my $child = shift;
2470 if ({
2471 table => 1, tbody => 1, tfoot => 1,
2472 thead => 1, tr => 1,
2473 }->{$self->{open_elements}->[-1]->[1]}) {
2474 # MUST
2475 my $foster_parent_element;
2476 my $next_sibling;
2477 OE: for (reverse 0..$#{$self->{open_elements}}) {
2478 if ($self->{open_elements}->[$_]->[1] eq 'table') {
2479 my $parent = $self->{open_elements}->[$_]->[0]->parent_node;
2480 if (defined $parent and $parent->node_type == 1) {
2481 $foster_parent_element = $parent;
2482 $next_sibling = $self->{open_elements}->[$_]->[0];
2483 } else {
2484 $foster_parent_element
2485 = $self->{open_elements}->[$_ - 1]->[0];
2486 }
2487 last OE;
2488 }
2489 } # OE
2490 $foster_parent_element = $self->{open_elements}->[0]->[0]
2491 unless defined $foster_parent_element;
2492 $foster_parent_element->insert_before
2493 ($child, $next_sibling);
2494 } else {
2495 $self->{open_elements}->[-1]->[0]->append_child ($child);
2496 }
2497 }; # $insert_to_foster
2498
2499 my $in_body = sub {
2500 my $insert = shift;
2501 if ($token->{type} eq 'start tag') {
2502 if ($token->{tag_name} eq 'script') {
2503 ## NOTE: This is an "as if in head" code clone
2504 $script_start_tag->($insert);
2505 return;
2506 } elsif ($token->{tag_name} eq 'style') {
2507 ## NOTE: This is an "as if in head" code clone
2508 $parse_rcdata->(CDATA_CONTENT_MODEL, $insert);
2509 return;
2510 } elsif ({
2511 base => 1, link => 1,
2512 }->{$token->{tag_name}}) {
2513 ## NOTE: This is an "as if in head" code clone, only "-t" differs
2514 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2515 pop @{$self->{open_elements}}; ## ISSUE: This step is missing in the spec.
2516 !!!next-token;
2517 return;
2518 } elsif ($token->{tag_name} eq 'meta') {
2519 ## NOTE: This is an "as if in head" code clone, only "-t" differs
2520 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2521 pop @{$self->{open_elements}}; ## ISSUE: This step is missing in the spec.
2522
2523 unless ($self->{confident}) {
2524 my $charset;
2525 if ($token->{attributes}->{charset}) { ## TODO: And if supported
2526 $charset = $token->{attributes}->{charset}->{value};
2527 }
2528 if ($token->{attributes}->{'http-equiv'}) {
2529 ## ISSUE: Algorithm name in the spec was incorrect so that not linked to the definition.
2530 if ($token->{attributes}->{'http-equiv'}->{value}
2531 =~ /\A[^;]*;[\x09-\x0D\x20]*charset[\x09-\x0D\x20]*=
2532 [\x09-\x0D\x20]*(?>"([^"]*)"|'([^']*)'|
2533 ([^"'\x09-\x0D\x20][^\x09-\x0D\x20]*))/x) {
2534 $charset = defined $1 ? $1 : defined $2 ? $2 : $3;
2535 } ## TODO: And if supported
2536 }
2537 ## TODO: Change the encoding
2538 }
2539
2540 !!!next-token;
2541 return;
2542 } elsif ($token->{tag_name} eq 'title') {
2543 !!!parse-error (type => 'in body:title');
2544 ## NOTE: This is an "as if in head" code clone
2545 $parse_rcdata->(RCDATA_CONTENT_MODEL, sub {
2546 if (defined $self->{head_element}) {
2547 $self->{head_element}->append_child ($_[0]);
2548 } else {
2549 $insert->($_[0]);
2550 }
2551 });
2552 return;
2553 } elsif ($token->{tag_name} eq 'body') {
2554 !!!parse-error (type => 'in body:body');
2555
2556 if (@{$self->{open_elements}} == 1 or
2557 $self->{open_elements}->[1]->[1] ne 'body') {
2558 ## Ignore the token
2559 } else {
2560 my $body_el = $self->{open_elements}->[1]->[0];
2561 for my $attr_name (keys %{$token->{attributes}}) {
2562 unless ($body_el->has_attribute_ns (undef, $attr_name)) {
2563 $body_el->set_attribute_ns
2564 (undef, [undef, $attr_name],
2565 $token->{attributes}->{$attr_name}->{value});
2566 }
2567 }
2568 }
2569 !!!next-token;
2570 return;
2571 } elsif ({
2572 address => 1, blockquote => 1, center => 1, dir => 1,
2573 div => 1, dl => 1, fieldset => 1, listing => 1,
2574 menu => 1, ol => 1, p => 1, ul => 1,
2575 pre => 1,
2576 }->{$token->{tag_name}}) {
2577 ## has a p element in scope
2578 INSCOPE: for (reverse @{$self->{open_elements}}) {
2579 if ($_->[1] eq 'p') {
2580 !!!back-token;
2581 $token = {type => 'end tag', tag_name => 'p'};
2582 return;
2583 } elsif ({
2584 table => 1, caption => 1, td => 1, th => 1,
2585 button => 1, marquee => 1, object => 1, html => 1,
2586 }->{$_->[1]}) {
2587 last INSCOPE;
2588 }
2589 } # INSCOPE
2590
2591 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2592 if ($token->{tag_name} eq 'pre') {
2593 !!!next-token;
2594 if ($token->{type} eq 'character') {
2595 $token->{data} =~ s/^\x0A//;
2596 unless (length $token->{data}) {
2597 !!!next-token;
2598 }
2599 }
2600 } else {
2601 !!!next-token;
2602 }
2603 return;
2604 } elsif ($token->{tag_name} eq 'form') {
2605 if (defined $self->{form_element}) {
2606 !!!parse-error (type => 'in form:form');
2607 ## Ignore the token
2608 !!!next-token;
2609 return;
2610 } else {
2611 ## has a p element in scope
2612 INSCOPE: for (reverse @{$self->{open_elements}}) {
2613 if ($_->[1] eq 'p') {
2614 !!!back-token;
2615 $token = {type => 'end tag', tag_name => 'p'};
2616 return;
2617 } elsif ({
2618 table => 1, caption => 1, td => 1, th => 1,
2619 button => 1, marquee => 1, object => 1, html => 1,
2620 }->{$_->[1]}) {
2621 last INSCOPE;
2622 }
2623 } # INSCOPE
2624
2625 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2626 $self->{form_element} = $self->{open_elements}->[-1]->[0];
2627 !!!next-token;
2628 return;
2629 }
2630 } elsif ($token->{tag_name} eq 'li') {
2631 ## has a p element in scope
2632 INSCOPE: for (reverse @{$self->{open_elements}}) {
2633 if ($_->[1] eq 'p') {
2634 !!!back-token;
2635 $token = {type => 'end tag', tag_name => 'p'};
2636 return;
2637 } elsif ({
2638 table => 1, caption => 1, td => 1, th => 1,
2639 button => 1, marquee => 1, object => 1, html => 1,
2640 }->{$_->[1]}) {
2641 last INSCOPE;
2642 }
2643 } # INSCOPE
2644
2645 ## Step 1
2646 my $i = -1;
2647 my $node = $self->{open_elements}->[$i];
2648 LI: {
2649 ## Step 2
2650 if ($node->[1] eq 'li') {
2651 if ($i != -1) {
2652 !!!parse-error (type => 'end tag missing:'.
2653 $self->{open_elements}->[-1]->[1]);
2654 }
2655 splice @{$self->{open_elements}}, $i;
2656 last LI;
2657 }
2658
2659 ## Step 3
2660 if (not $formatting_category->{$node->[1]} and
2661 #not $phrasing_category->{$node->[1]} and
2662 ($special_category->{$node->[1]} or
2663 $scoping_category->{$node->[1]}) and
2664 $node->[1] ne 'address' and $node->[1] ne 'div') {
2665 last LI;
2666 }
2667
2668 ## Step 4
2669 $i--;
2670 $node = $self->{open_elements}->[$i];
2671 redo LI;
2672 } # LI
2673
2674 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2675 !!!next-token;
2676 return;
2677 } elsif ($token->{tag_name} eq 'dd' or $token->{tag_name} eq 'dt') {
2678 ## has a p element in scope
2679 INSCOPE: for (reverse @{$self->{open_elements}}) {
2680 if ($_->[1] eq 'p') {
2681 !!!back-token;
2682 $token = {type => 'end tag', tag_name => 'p'};
2683 return;
2684 } elsif ({
2685 table => 1, caption => 1, td => 1, th => 1,
2686 button => 1, marquee => 1, object => 1, html => 1,
2687 }->{$_->[1]}) {
2688 last INSCOPE;
2689 }
2690 } # INSCOPE
2691
2692 ## Step 1
2693 my $i = -1;
2694 my $node = $self->{open_elements}->[$i];
2695 LI: {
2696 ## Step 2
2697 if ($node->[1] eq 'dt' or $node->[1] eq 'dd') {
2698 if ($i != -1) {
2699 !!!parse-error (type => 'end tag missing:'.
2700 $self->{open_elements}->[-1]->[1]);
2701 }
2702 splice @{$self->{open_elements}}, $i;
2703 last LI;
2704 }
2705
2706 ## Step 3
2707 if (not $formatting_category->{$node->[1]} and
2708 #not $phrasing_category->{$node->[1]} and
2709 ($special_category->{$node->[1]} or
2710 $scoping_category->{$node->[1]}) and
2711 $node->[1] ne 'address' and $node->[1] ne 'div') {
2712 last LI;
2713 }
2714
2715 ## Step 4
2716 $i--;
2717 $node = $self->{open_elements}->[$i];
2718 redo LI;
2719 } # LI
2720
2721 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2722 !!!next-token;
2723 return;
2724 } elsif ($token->{tag_name} eq 'plaintext') {
2725 ## has a p element in scope
2726 INSCOPE: for (reverse @{$self->{open_elements}}) {
2727 if ($_->[1] eq 'p') {
2728 !!!back-token;
2729 $token = {type => 'end tag', tag_name => 'p'};
2730 return;
2731 } elsif ({
2732 table => 1, caption => 1, td => 1, th => 1,
2733 button => 1, marquee => 1, object => 1, html => 1,
2734 }->{$_->[1]}) {
2735 last INSCOPE;
2736 }
2737 } # INSCOPE
2738
2739 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2740
2741 $self->{content_model} = PLAINTEXT_CONTENT_MODEL;
2742
2743 !!!next-token;
2744 return;
2745 } elsif ({
2746 h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,
2747 }->{$token->{tag_name}}) {
2748 ## has a p element in scope
2749 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
2750 my $node = $self->{open_elements}->[$_];
2751 if ($node->[1] eq 'p') {
2752 !!!back-token;
2753 $token = {type => 'end tag', tag_name => 'p'};
2754 return;
2755 } elsif ({
2756 table => 1, caption => 1, td => 1, th => 1,
2757 button => 1, marquee => 1, object => 1, html => 1,
2758 }->{$node->[1]}) {
2759 last INSCOPE;
2760 }
2761 } # INSCOPE
2762
2763 ## NOTE: See <http://html5.org/tools/web-apps-tracker?from=925&to=926>
2764 ## has an element in scope
2765 #my $i;
2766 #INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
2767 # my $node = $self->{open_elements}->[$_];
2768 # if ({
2769 # h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,
2770 # }->{$node->[1]}) {
2771 # $i = $_;
2772 # last INSCOPE;
2773 # } elsif ({
2774 # table => 1, caption => 1, td => 1, th => 1,
2775 # button => 1, marquee => 1, object => 1, html => 1,
2776 # }->{$node->[1]}) {
2777 # last INSCOPE;
2778 # }
2779 #} # INSCOPE
2780 #
2781 #if (defined $i) {
2782 # !!! parse-error (type => 'in hn:hn');
2783 # splice @{$self->{open_elements}}, $i;
2784 #}
2785
2786 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2787
2788 !!!next-token;
2789 return;
2790 } elsif ($token->{tag_name} eq 'a') {
2791 AFE: for my $i (reverse 0..$#$active_formatting_elements) {
2792 my $node = $active_formatting_elements->[$i];
2793 if ($node->[1] eq 'a') {
2794 !!!parse-error (type => 'in a:a');
2795
2796 !!!back-token;
2797 $token = {type => 'end tag', tag_name => 'a'};
2798 $formatting_end_tag->($token->{tag_name});
2799
2800 AFE2: for (reverse 0..$#$active_formatting_elements) {
2801 if ($active_formatting_elements->[$_]->[0] eq $node->[0]) {
2802 splice @$active_formatting_elements, $_, 1;
2803 last AFE2;
2804 }
2805 } # AFE2
2806 OE: for (reverse 0..$#{$self->{open_elements}}) {
2807 if ($self->{open_elements}->[$_]->[0] eq $node->[0]) {
2808 splice @{$self->{open_elements}}, $_, 1;
2809 last OE;
2810 }
2811 } # OE
2812 last AFE;
2813 } elsif ($node->[0] eq '#marker') {
2814 last AFE;
2815 }
2816 } # AFE
2817
2818 $reconstruct_active_formatting_elements->($insert_to_current);
2819
2820 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2821 push @$active_formatting_elements, $self->{open_elements}->[-1];
2822
2823 !!!next-token;
2824 return;
2825 } elsif ({
2826 b => 1, big => 1, em => 1, font => 1, i => 1,
2827 s => 1, small => 1, strile => 1,
2828 strong => 1, tt => 1, u => 1,
2829 }->{$token->{tag_name}}) {
2830 $reconstruct_active_formatting_elements->($insert_to_current);
2831
2832 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2833 push @$active_formatting_elements, $self->{open_elements}->[-1];
2834
2835 !!!next-token;
2836 return;
2837 } elsif ($token->{tag_name} eq 'nobr') {
2838 $reconstruct_active_formatting_elements->($insert_to_current);
2839
2840 ## has a |nobr| element in scope
2841 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
2842 my $node = $self->{open_elements}->[$_];
2843 if ($node->[1] eq 'nobr') {
2844 !!!parse-error (type => 'not closed:nobr');
2845 !!!back-token;
2846 $token = {type => 'end tag', tag_name => 'nobr'};
2847 return;
2848 } elsif ({
2849 table => 1, caption => 1, td => 1, th => 1,
2850 button => 1, marquee => 1, object => 1, html => 1,
2851 }->{$node->[1]}) {
2852 last INSCOPE;
2853 }
2854 } # INSCOPE
2855
2856 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2857 push @$active_formatting_elements, $self->{open_elements}->[-1];
2858
2859 !!!next-token;
2860 return;
2861 } elsif ($token->{tag_name} eq 'button') {
2862 ## has a button element in scope
2863 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
2864 my $node = $self->{open_elements}->[$_];
2865 if ($node->[1] eq 'button') {
2866 !!!parse-error (type => 'in button:button');
2867 !!!back-token;
2868 $token = {type => 'end tag', tag_name => 'button'};
2869 return;
2870 } elsif ({
2871 table => 1, caption => 1, td => 1, th => 1,
2872 button => 1, marquee => 1, object => 1, html => 1,
2873 }->{$node->[1]}) {
2874 last INSCOPE;
2875 }
2876 } # INSCOPE
2877
2878 $reconstruct_active_formatting_elements->($insert_to_current);
2879
2880 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2881 push @$active_formatting_elements, ['#marker', ''];
2882
2883 !!!next-token;
2884 return;
2885 } elsif ($token->{tag_name} eq 'marquee' or
2886 $token->{tag_name} eq 'object') {
2887 $reconstruct_active_formatting_elements->($insert_to_current);
2888
2889 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2890 push @$active_formatting_elements, ['#marker', ''];
2891
2892 !!!next-token;
2893 return;
2894 } elsif ($token->{tag_name} eq 'xmp') {
2895 $reconstruct_active_formatting_elements->($insert_to_current);
2896 $parse_rcdata->(CDATA_CONTENT_MODEL, $insert);
2897 return;
2898 } elsif ($token->{tag_name} eq 'table') {
2899 ## has a p element in scope
2900 INSCOPE: for (reverse @{$self->{open_elements}}) {
2901 if ($_->[1] eq 'p') {
2902 !!!back-token;
2903 $token = {type => 'end tag', tag_name => 'p'};
2904 return;
2905 } elsif ({
2906 table => 1, caption => 1, td => 1, th => 1,
2907 button => 1, marquee => 1, object => 1, html => 1,
2908 }->{$_->[1]}) {
2909 last INSCOPE;
2910 }
2911 } # INSCOPE
2912
2913 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2914
2915 $self->{insertion_mode} = 'in table';
2916
2917 !!!next-token;
2918 return;
2919 } elsif ({
2920 area => 1, basefont => 1, bgsound => 1, br => 1,
2921 embed => 1, img => 1, param => 1, spacer => 1, wbr => 1,
2922 image => 1,
2923 }->{$token->{tag_name}}) {
2924 if ($token->{tag_name} eq 'image') {
2925 !!!parse-error (type => 'image');
2926 $token->{tag_name} = 'img';
2927 }
2928
2929 ## NOTE: There is an "as if <br>" code clone.
2930 $reconstruct_active_formatting_elements->($insert_to_current);
2931
2932 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2933 pop @{$self->{open_elements}};
2934
2935 !!!next-token;
2936 return;
2937 } elsif ($token->{tag_name} eq 'hr') {
2938 ## has a p element in scope
2939 INSCOPE: for (reverse @{$self->{open_elements}}) {
2940 if ($_->[1] eq 'p') {
2941 !!!back-token;
2942 $token = {type => 'end tag', tag_name => 'p'};
2943 return;
2944 } elsif ({
2945 table => 1, caption => 1, td => 1, th => 1,
2946 button => 1, marquee => 1, object => 1, html => 1,
2947 }->{$_->[1]}) {
2948 last INSCOPE;
2949 }
2950 } # INSCOPE
2951
2952 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2953 pop @{$self->{open_elements}};
2954
2955 !!!next-token;
2956 return;
2957 } elsif ($token->{tag_name} eq 'input') {
2958 $reconstruct_active_formatting_elements->($insert_to_current);
2959
2960 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2961 ## TODO: associate with $self->{form_element} if defined
2962 pop @{$self->{open_elements}};
2963
2964 !!!next-token;
2965 return;
2966 } elsif ($token->{tag_name} eq 'isindex') {
2967 !!!parse-error (type => 'isindex');
2968
2969 if (defined $self->{form_element}) {
2970 ## Ignore the token
2971 !!!next-token;
2972 return;
2973 } else {
2974 my $at = $token->{attributes};
2975 my $form_attrs;
2976 $form_attrs->{action} = $at->{action} if $at->{action};
2977 my $prompt_attr = $at->{prompt};
2978 $at->{name} = {name => 'name', value => 'isindex'};
2979 delete $at->{action};
2980 delete $at->{prompt};
2981 my @tokens = (
2982 {type => 'start tag', tag_name => 'form',
2983 attributes => $form_attrs},
2984 {type => 'start tag', tag_name => 'hr'},
2985 {type => 'start tag', tag_name => 'p'},
2986 {type => 'start tag', tag_name => 'label'},
2987 );
2988 if ($prompt_attr) {
2989 push @tokens, {type => 'character', data => $prompt_attr->{value}};
2990 } else {
2991 push @tokens, {type => 'character',
2992 data => 'This is a searchable index. Insert your search keywords here: '}; # SHOULD
2993 ## TODO: make this configurable
2994 }
2995 push @tokens,
2996 {type => 'start tag', tag_name => 'input', attributes => $at},
2997 #{type => 'character', data => ''}, # SHOULD
2998 {type => 'end tag', tag_name => 'label'},
2999 {type => 'end tag', tag_name => 'p'},
3000 {type => 'start tag', tag_name => 'hr'},
3001 {type => 'end tag', tag_name => 'form'};
3002 $token = shift @tokens;
3003 !!!back-token (@tokens);
3004 return;
3005 }
3006 } elsif ($token->{tag_name} eq 'textarea') {
3007 my $tag_name = $token->{tag_name};
3008 my $el;
3009 !!!create-element ($el, $token->{tag_name}, $token->{attributes});
3010
3011 ## TODO: $self->{form_element} if defined
3012 $self->{content_model} = RCDATA_CONTENT_MODEL;
3013 delete $self->{escape}; # MUST
3014
3015 $insert->($el);
3016
3017 my $text = '';
3018 !!!next-token;
3019 if ($token->{type} eq 'character') {
3020 $token->{data} =~ s/^\x0A//;
3021 unless (length $token->{data}) {
3022 !!!next-token;
3023 }
3024 }
3025 while ($token->{type} eq 'character') {
3026 $text .= $token->{data};
3027 !!!next-token;
3028 }
3029 if (length $text) {
3030 $el->manakai_append_text ($text);
3031 }
3032
3033 $self->{content_model} = PCDATA_CONTENT_MODEL;
3034
3035 if ($token->{type} eq 'end tag' and
3036 $token->{tag_name} eq $tag_name) {
3037 ## Ignore the token
3038 } else {
3039 !!!parse-error (type => 'in RCDATA:#'.$token->{type});
3040 }
3041 !!!next-token;
3042 return;
3043 } elsif ({
3044 iframe => 1,
3045 noembed => 1,
3046 noframes => 1,
3047 noscript => 0, ## TODO: 1 if scripting is enabled
3048 }->{$token->{tag_name}}) {
3049 ## NOTE: There are two "as if in body" code clones.
3050 $parse_rcdata->(CDATA_CONTENT_MODEL, $insert);
3051 return;
3052 } elsif ($token->{tag_name} eq 'select') {
3053 $reconstruct_active_formatting_elements->($insert_to_current);
3054
3055 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
3056
3057 $self->{insertion_mode} = 'in select';
3058 !!!next-token;
3059 return;
3060 } elsif ({
3061 caption => 1, col => 1, colgroup => 1, frame => 1,
3062 frameset => 1, head => 1, option => 1, optgroup => 1,
3063 tbody => 1, td => 1, tfoot => 1, th => 1,
3064 thead => 1, tr => 1,
3065 }->{$token->{tag_name}}) {
3066 !!!parse-error (type => 'in body:'.$token->{tag_name});
3067 ## Ignore the token
3068 !!!next-token;
3069 return;
3070
3071 ## ISSUE: An issue on HTML5 new elements in the spec.
3072 } else {
3073 $reconstruct_active_formatting_elements->($insert_to_current);
3074
3075 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
3076
3077 !!!next-token;
3078 return;
3079 }
3080 } elsif ($token->{type} eq 'end tag') {
3081 if ($token->{tag_name} eq 'body') {
3082 if (@{$self->{open_elements}} > 1 and
3083 $self->{open_elements}->[1]->[1] eq 'body') {
3084 for (@{$self->{open_elements}}) {
3085 unless ({
3086 dd => 1, dt => 1, li => 1, p => 1, td => 1,
3087 th => 1, tr => 1, body => 1, html => 1,
3088 tbody => 1, tfoot => 1, thead => 1,
3089 }->{$_->[1]}) {
3090 !!!parse-error (type => 'not closed:'.$_->[1]);
3091 }
3092 }
3093
3094 $self->{insertion_mode} = 'after body';
3095 !!!next-token;
3096 return;
3097 } else {
3098 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3099 ## Ignore the token
3100 !!!next-token;
3101 return;
3102 }
3103 } elsif ($token->{tag_name} eq 'html') {
3104 if (@{$self->{open_elements}} > 1 and $self->{open_elements}->[1]->[1] eq 'body') {
3105 ## ISSUE: There is an issue in the spec.
3106 if ($self->{open_elements}->[-1]->[1] ne 'body') {
3107 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[1]->[1]);
3108 }
3109 $self->{insertion_mode} = 'after body';
3110 ## reprocess
3111 return;
3112 } else {
3113 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3114 ## Ignore the token
3115 !!!next-token;
3116 return;
3117 }
3118 } elsif ({
3119 address => 1, blockquote => 1, center => 1, dir => 1,
3120 div => 1, dl => 1, fieldset => 1, listing => 1,
3121 menu => 1, ol => 1, pre => 1, ul => 1,
3122 p => 1,
3123 dd => 1, dt => 1, li => 1,
3124 button => 1, marquee => 1, object => 1,
3125 }->{$token->{tag_name}}) {
3126 ## has an element in scope
3127 my $i;
3128 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3129 my $node = $self->{open_elements}->[$_];
3130 if ($node->[1] eq $token->{tag_name}) {
3131 ## generate implied end tags
3132 if ({
3133 dd => ($token->{tag_name} ne 'dd'),
3134 dt => ($token->{tag_name} ne 'dt'),
3135 li => ($token->{tag_name} ne 'li'),
3136 p => ($token->{tag_name} ne 'p'),
3137 td => 1, th => 1, tr => 1,
3138 tbody => 1, tfoot=> 1, thead => 1,
3139 }->{$self->{open_elements}->[-1]->[1]}) {
3140 !!!back-token;
3141 $token = {type => 'end tag',
3142 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3143 return;
3144 }
3145 $i = $_;
3146 last INSCOPE unless $token->{tag_name} eq 'p';
3147 } elsif ({
3148 table => 1, caption => 1, td => 1, th => 1,
3149 button => 1, marquee => 1, object => 1, html => 1,
3150 }->{$node->[1]}) {
3151 last INSCOPE;
3152 }
3153 } # INSCOPE
3154
3155 if ($self->{open_elements}->[-1]->[1] ne $token->{tag_name}) {
3156 if (defined $i) {
3157 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3158 } else {
3159 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3160 }
3161 }
3162
3163 if (defined $i) {
3164 splice @{$self->{open_elements}}, $i;
3165 } elsif ($token->{tag_name} eq 'p') {
3166 ## As if <p>, then reprocess the current token
3167 my $el;
3168 !!!create-element ($el, 'p');
3169 $insert->($el);
3170 }
3171 $clear_up_to_marker->()
3172 if {
3173 button => 1, marquee => 1, object => 1,
3174 }->{$token->{tag_name}};
3175 !!!next-token;
3176 return;
3177 } elsif ($token->{tag_name} eq 'form') {
3178 ## has an element in scope
3179 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3180 my $node = $self->{open_elements}->[$_];
3181 if ($node->[1] eq $token->{tag_name}) {
3182 ## generate implied end tags
3183 if ({
3184 dd => 1, dt => 1, li => 1, p => 1,
3185 td => 1, th => 1, tr => 1,
3186 tbody => 1, tfoot=> 1, thead => 1,
3187 }->{$self->{open_elements}->[-1]->[1]}) {
3188 !!!back-token;
3189 $token = {type => 'end tag',
3190 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3191 return;
3192 }
3193 last INSCOPE;
3194 } elsif ({
3195 table => 1, caption => 1, td => 1, th => 1,
3196 button => 1, marquee => 1, object => 1, html => 1,
3197 }->{$node->[1]}) {
3198 last INSCOPE;
3199 }
3200 } # INSCOPE
3201
3202 if ($self->{open_elements}->[-1]->[1] eq $token->{tag_name}) {
3203 pop @{$self->{open_elements}};
3204 } else {
3205 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3206 }
3207
3208 undef $self->{form_element};
3209 !!!next-token;
3210 return;
3211 } elsif ({
3212 h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,
3213 }->{$token->{tag_name}}) {
3214 ## has an element in scope
3215 my $i;
3216 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3217 my $node = $self->{open_elements}->[$_];
3218 if ({
3219 h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,
3220 }->{$node->[1]}) {
3221 ## generate implied end tags
3222 if ({
3223 dd => 1, dt => 1, li => 1, p => 1,
3224 td => 1, th => 1, tr => 1,
3225 tbody => 1, tfoot=> 1, thead => 1,
3226 }->{$self->{open_elements}->[-1]->[1]}) {
3227 !!!back-token;
3228 $token = {type => 'end tag',
3229 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3230 return;
3231 }
3232 $i = $_;
3233 last INSCOPE;
3234 } elsif ({
3235 table => 1, caption => 1, td => 1, th => 1,
3236 button => 1, marquee => 1, object => 1, html => 1,
3237 }->{$node->[1]}) {
3238 last INSCOPE;
3239 }
3240 } # INSCOPE
3241
3242 if ($self->{open_elements}->[-1]->[1] ne $token->{tag_name}) {
3243 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3244 }
3245
3246 splice @{$self->{open_elements}}, $i if defined $i;
3247 !!!next-token;
3248 return;
3249 } elsif ({
3250 a => 1,
3251 b => 1, big => 1, em => 1, font => 1, i => 1,
3252 nobr => 1, s => 1, small => 1, strile => 1,
3253 strong => 1, tt => 1, u => 1,
3254 }->{$token->{tag_name}}) {
3255 $formatting_end_tag->($token->{tag_name});
3256 return;
3257 } elsif ($token->{tag_name} eq 'br') {
3258 !!!parse-error (type => 'unmatched end tag:br');
3259
3260 ## As if <br>
3261 $reconstruct_active_formatting_elements->($insert_to_current);
3262
3263 my $el;
3264 !!!create-element ($el, 'br');
3265 $insert->($el);
3266
3267 ## Ignore the token.
3268 !!!next-token;
3269 return;
3270 } elsif ({
3271 caption => 1, col => 1, colgroup => 1, frame => 1,
3272 frameset => 1, head => 1, option => 1, optgroup => 1,
3273 tbody => 1, td => 1, tfoot => 1, th => 1,
3274 thead => 1, tr => 1,
3275 area => 1, basefont => 1, bgsound => 1,
3276 embed => 1, hr => 1, iframe => 1, image => 1,
3277 img => 1, input => 1, isindex => 1, noembed => 1,
3278 noframes => 1, param => 1, select => 1, spacer => 1,
3279 table => 1, textarea => 1, wbr => 1,
3280 noscript => 0, ## TODO: if scripting is enabled
3281 }->{$token->{tag_name}}) {
3282 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3283 ## Ignore the token
3284 !!!next-token;
3285 return;
3286
3287 ## ISSUE: Issue on HTML5 new elements in spec
3288
3289 } else {
3290 ## Step 1
3291 my $node_i = -1;
3292 my $node = $self->{open_elements}->[$node_i];
3293
3294 ## Step 2
3295 S2: {
3296 if ($node->[1] eq $token->{tag_name}) {
3297 ## Step 1
3298 ## generate implied end tags
3299 if ({
3300 dd => 1, dt => 1, li => 1, p => 1,
3301 td => 1, th => 1, tr => 1,
3302 tbody => 1, tfoot=> 1, thead => 1,
3303 }->{$self->{open_elements}->[-1]->[1]}) {
3304 !!!back-token;
3305 $token = {type => 'end tag',
3306 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3307 return;
3308 }
3309
3310 ## Step 2
3311 if ($token->{tag_name} ne $self->{open_elements}->[-1]->[1]) {
3312 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3313 }
3314
3315 ## Step 3
3316 splice @{$self->{open_elements}}, $node_i;
3317
3318 !!!next-token;
3319 last S2;
3320 } else {
3321 ## Step 3
3322 if (not $formatting_category->{$node->[1]} and
3323 #not $phrasing_category->{$node->[1]} and
3324 ($special_category->{$node->[1]} or
3325 $scoping_category->{$node->[1]})) {
3326 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3327 ## Ignore the token
3328 !!!next-token;
3329 last S2;
3330 }
3331 }
3332
3333 ## Step 4
3334 $node_i--;
3335 $node = $self->{open_elements}->[$node_i];
3336
3337 ## Step 5;
3338 redo S2;
3339 } # S2
3340 return;
3341 }
3342 }
3343 }; # $in_body
3344
3345 B: {
3346 if ($token->{type} eq 'DOCTYPE') {
3347 !!!parse-error (type => 'DOCTYPE in the middle');
3348 ## Ignore the token
3349 ## Stay in the phase
3350 !!!next-token;
3351 redo B;
3352 } elsif ($token->{type} eq 'end-of-file') {
3353 if ($token->{insertion_mode} ne 'trailing end') {
3354 ## Generate implied end tags
3355 if ({
3356 dd => 1, dt => 1, li => 1, p => 1, td => 1, th => 1, tr => 1,
3357 tbody => 1, tfoot=> 1, thead => 1,
3358 }->{$self->{open_elements}->[-1]->[1]}) {
3359 !!!back-token;
3360 $token = {type => 'end tag', tag_name => $self->{open_elements}->[-1]->[1]};
3361 redo B;
3362 }
3363
3364 if (@{$self->{open_elements}} > 2 or
3365 (@{$self->{open_elements}} == 2 and $self->{open_elements}->[1]->[1] ne 'body')) {
3366 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3367 } elsif (defined $self->{inner_html_node} and
3368 @{$self->{open_elements}} > 1 and
3369 $self->{open_elements}->[1]->[1] ne 'body') {
3370 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3371 }
3372
3373 ## ISSUE: There is an issue in the spec.
3374 }
3375
3376 ## Stop parsing
3377 last B;
3378 } elsif ($token->{type} eq 'start tag' and
3379 $token->{tag_name} eq 'html') {
3380 if ($self->{insertion_mode} eq 'trailing end') {
3381 ## Turn into the main phase
3382 !!!parse-error (type => 'after html:html');
3383 $self->{insertion_mode} = $previous_insertion_mode;
3384 }
3385
3386 ## ISSUE: "aa<html>" is not a parse error.
3387 ## ISSUE: "<html>" in fragment is not a parse error.
3388 unless ($token->{first_start_tag}) {
3389 !!!parse-error (type => 'not first start tag');
3390 }
3391 my $top_el = $self->{open_elements}->[0]->[0];
3392 for my $attr_name (keys %{$token->{attributes}}) {
3393 unless ($top_el->has_attribute_ns (undef, $attr_name)) {
3394 $top_el->set_attribute_ns
3395 (undef, [undef, $attr_name],
3396 $token->{attributes}->{$attr_name}->{value});
3397 }
3398 }
3399 !!!next-token;
3400 redo B;
3401 } elsif ($token->{type} eq 'comment') {
3402 my $comment = $self->{document}->create_comment ($token->{data});
3403 if ($self->{insertion_mode} eq 'trailing end') {
3404 $self->{document}->append_child ($comment);
3405 } elsif ($self->{insertion_mode} eq 'after body') {
3406 $self->{open_elements}->[0]->[0]->append_child ($comment);
3407 } else {
3408 $self->{open_elements}->[-1]->[0]->append_child ($comment);
3409 }
3410 !!!next-token;
3411 redo B;
3412 } elsif ($self->{insertion_mode} eq 'in head' or
3413 $self->{insertion_mode} eq 'in head noscript' or
3414 $self->{insertion_mode} eq 'after head' or
3415 $self->{insertion_mode} eq 'before head') {
3416 if ($token->{type} eq 'character') {
3417 if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
3418 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
3419 unless (length $token->{data}) {
3420 !!!next-token;
3421 redo B;
3422 }
3423 }
3424
3425 if ($self->{insertion_mode} eq 'before head') {
3426 ## As if <head>
3427 !!!create-element ($self->{head_element}, 'head');
3428 $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
3429 push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3430
3431 ## Reprocess in the "in head" insertion mode...
3432 pop @{$self->{open_elements}};
3433
3434 ## Reprocess in the "after head" insertion mode...
3435 } elsif ($self->{insertion_mode} eq 'in head noscript') {
3436 ## As if </noscript>
3437 pop @{$self->{open_elements}};
3438 !!!parse-error (type => 'in noscript:#character');
3439
3440 ## Reprocess in the "in head" insertion mode...
3441 ## As if </head>
3442 pop @{$self->{open_elements}};
3443
3444 ## Reprocess in the "after head" insertion mode...
3445 } elsif ($self->{insertion_mode} eq 'in head') {
3446 pop @{$self->{open_elements}};
3447
3448 ## Reprocess in the "after head" insertion mode...
3449 }
3450
3451 ## "after head" insertion mode
3452 ## As if <body>
3453 !!!insert-element ('body');
3454 $self->{insertion_mode} = 'in body';
3455 ## reprocess
3456 redo B;
3457 } elsif ($token->{type} eq 'start tag') {
3458 if ($token->{tag_name} eq 'head') {
3459 if ($self->{insertion_mode} eq 'before head') {
3460 !!!create-element ($self->{head_element}, $token->{tag_name}, $token->{attributes});
3461 $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
3462 push @{$self->{open_elements}}, [$self->{head_element}, $token->{tag_name}];
3463 $self->{insertion_mode} = 'in head';
3464 !!!next-token;
3465 redo B;
3466 } elsif ($self->{insertion_mode} ne 'after head') {
3467 !!!parse-error (type => 'in head:head'); # or in head noscript
3468 ## Ignore the token
3469 !!!next-token;
3470 redo B;
3471 } else {
3472 #
3473 }
3474 } elsif ($self->{insertion_mode} eq 'before head') {
3475 ## As if <head>
3476 !!!create-element ($self->{head_element}, 'head');
3477 $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
3478 push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3479
3480 $self->{insertion_mode} = 'in head';
3481 ## Reprocess in the "in head" insertion mode...
3482 }
3483
3484 if ($token->{tag_name} eq 'base') {
3485 if ($self->{insertion_mode} eq 'in head noscript') {
3486 ## As if </noscript>
3487 pop @{$self->{open_elements}};
3488 !!!parse-error (type => 'in noscript:base');
3489
3490 $self->{insertion_mode} = 'in head';
3491 ## Reprocess in the "in head" insertion mode...
3492 }
3493
3494 ## NOTE: There is a "as if in head" code clone.
3495 if ($self->{insertion_mode} eq 'after head') {
3496 !!!parse-error (type => 'after head:'.$token->{tag_name});
3497 push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3498 }
3499 !!!insert-element ($token->{tag_name}, $token->{attributes});
3500 pop @{$self->{open_elements}}; ## ISSUE: This step is missing in the spec.
3501 pop @{$self->{open_elements}}
3502 if $self->{insertion_mode} eq 'after head';
3503 !!!next-token;
3504 redo B;
3505 } elsif ($token->{tag_name} eq 'link') {
3506 ## NOTE: There is a "as if in head" code clone.
3507 if ($self->{insertion_mode} eq 'after head') {
3508 !!!parse-error (type => 'after head:'.$token->{tag_name});
3509 push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3510 }
3511 !!!insert-element ($token->{tag_name}, $token->{attributes});
3512 pop @{$self->{open_elements}}; ## ISSUE: This step is missing in the spec.
3513 pop @{$self->{open_elements}}
3514 if $self->{insertion_mode} eq 'after head';
3515 !!!next-token;
3516 redo B;
3517 } elsif ($token->{tag_name} eq 'meta') {
3518 ## NOTE: There is a "as if in head" code clone.
3519 if ($self->{insertion_mode} eq 'after head') {
3520 !!!parse-error (type => 'after head:'.$token->{tag_name});
3521 push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3522 }
3523 !!!insert-element ($token->{tag_name}, $token->{attributes});
3524 pop @{$self->{open_elements}}; ## ISSUE: This step is missing in the spec.
3525
3526 unless ($self->{confident}) {
3527 my $charset;
3528 if ($token->{attributes}->{charset}) { ## TODO: And if supported
3529 $charset = $token->{attributes}->{charset}->{value};
3530 }
3531 if ($token->{attributes}->{'http-equiv'}) {
3532 ## ISSUE: Algorithm name in the spec was incorrect so that not linked to the definition.
3533 if ($token->{attributes}->{'http-equiv'}->{value}
3534 =~ /\A[^;]*;[\x09-\x0D\x20]*charset[\x09-\x0D\x20]*=
3535 [\x09-\x0D\x20]*(?>"([^"]*)"|'([^']*)'|
3536 ([^"'\x09-\x0D\x20][^\x09-\x0D\x20]*))/x) {
3537 $charset = defined $1 ? $1 : defined $2 ? $2 : $3;
3538 } ## TODO: And if supported
3539 }
3540 ## TODO: Change the encoding
3541 }
3542
3543 ## TODO: Extracting |charset| from |meta|.
3544 pop @{$self->{open_elements}}
3545 if $self->{insertion_mode} eq 'after head';
3546 !!!next-token;
3547 redo B;
3548 } elsif ($token->{tag_name} eq 'title') {
3549 if ($self->{insertion_mode} eq 'in head noscript') {
3550 ## As if </noscript>
3551 pop @{$self->{open_elements}};
3552 !!!parse-error (type => 'in noscript:title');
3553
3554 $self->{insertion_mode} = 'in head';
3555 ## Reprocess in the "in head" insertion mode...
3556 } elsif ($self->{insertion_mode} eq 'after head') {
3557 !!!parse-error (type => 'after head:'.$token->{tag_name});
3558 push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3559 }
3560
3561 ## NOTE: There is a "as if in head" code clone.
3562 my $parent = defined $self->{head_element} ? $self->{head_element}
3563 : $self->{open_elements}->[-1]->[0];
3564 $parse_rcdata->(RCDATA_CONTENT_MODEL,
3565 sub { $parent->append_child ($_[0]) });
3566 pop @{$self->{open_elements}}
3567 if $self->{insertion_mode} eq 'after head';
3568 redo B;
3569 } elsif ($token->{tag_name} eq 'style') {
3570 ## NOTE: Or (scripting is enabled and tag_name eq 'noscript' and
3571 ## insertion mode 'in head')
3572 ## NOTE: There is a "as if in head" code clone.
3573 if ($self->{insertion_mode} eq 'after head') {
3574 !!!parse-error (type => 'after head:'.$token->{tag_name});
3575 push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3576 }
3577 $parse_rcdata->(CDATA_CONTENT_MODEL, $insert_to_current);
3578 pop @{$self->{open_elements}}
3579 if $self->{insertion_mode} eq 'after head';
3580 redo B;
3581 } elsif ($token->{tag_name} eq 'noscript') {
3582 if ($self->{insertion_mode} eq 'in head') {
3583 ## NOTE: and scripting is disalbed
3584 !!!insert-element ($token->{tag_name}, $token->{attributes});
3585 $self->{insertion_mode} = 'in head noscript';
3586 !!!next-token;
3587 redo B;
3588 } elsif ($self->{insertion_mode} eq 'in head noscript') {
3589 !!!parse-error (type => 'in noscript:noscript');
3590 ## Ignore the token
3591 !!!next-token;
3592 redo B;
3593 } else {
3594 #
3595 }
3596 } elsif ($token->{tag_name} eq 'script') {
3597 if ($self->{insertion_mode} eq 'in head noscript') {
3598 ## As if </noscript>
3599 pop @{$self->{open_elements}};
3600 !!!parse-error (type => 'in noscript:script');
3601
3602 $self->{insertion_mode} = 'in head';
3603 ## Reprocess in the "in head" insertion mode...
3604 } elsif ($self->{insertion_mode} eq 'after head') {
3605 !!!parse-error (type => 'after head:'.$token->{tag_name});
3606 push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3607 }
3608
3609 ## NOTE: There is a "as if in head" code clone.
3610 $script_start_tag->($insert_to_current);
3611 pop @{$self->{open_elements}}
3612 if $self->{insertion_mode} eq 'after head';
3613 redo B;
3614 } elsif ($token->{tag_name} eq 'body' or
3615 $token->{tag_name} eq 'frameset') {
3616 if ($self->{insertion_mode} eq 'in head noscript') {
3617 ## As if </noscript>
3618 pop @{$self->{open_elements}};
3619 !!!parse-error (type => 'in noscript:'.$token->{tag_name});
3620
3621 ## Reprocess in the "in head" insertion mode...
3622 ## As if </head>
3623 pop @{$self->{open_elements}};
3624
3625 ## Reprocess in the "after head" insertion mode...
3626 } elsif ($self->{insertion_mode} eq 'in head') {
3627 pop @{$self->{open_elements}};
3628
3629 ## Reprocess in the "after head" insertion mode...
3630 }
3631
3632 ## "after head" insertion mode
3633 !!!insert-element ($token->{tag_name}, $token->{attributes});
3634 $self->{insertion_mode} = 'in '.$token->{tag_name};
3635 !!!next-token;
3636 redo B;
3637 } else {
3638 #
3639 }
3640
3641 if ($self->{insertion_mode} eq 'in head noscript') {
3642 ## As if </noscript>
3643 pop @{$self->{open_elements}};
3644 !!!parse-error (type => 'in noscript:/'.$token->{tag_name});
3645
3646 ## Reprocess in the "in head" insertion mode...
3647 ## As if </head>
3648 pop @{$self->{open_elements}};
3649
3650 ## Reprocess in the "after head" insertion mode...
3651 } elsif ($self->{insertion_mode} eq 'in head') {
3652 ## As if </head>
3653 pop @{$self->{open_elements}};
3654
3655 ## Reprocess in the "after head" insertion mode...
3656 }
3657
3658 ## "after head" insertion mode
3659 ## As if <body>
3660 !!!insert-element ('body');
3661 $self->{insertion_mode} = 'in body';
3662 ## reprocess
3663 redo B;
3664 } elsif ($token->{type} eq 'end tag') {
3665 if ($token->{tag_name} eq 'head') {
3666 if ($self->{insertion_mode} eq 'before head') {
3667 ## As if <head>
3668 !!!create-element ($self->{head_element}, 'head');
3669 $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
3670 push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3671
3672 ## Reprocess in the "in head" insertion mode...
3673 pop @{$self->{open_elements}};
3674 $self->{insertion_mode} = 'after head';
3675 !!!next-token;
3676 redo B;
3677 } elsif ($self->{insertion_mode} eq 'in head noscript') {
3678 ## As if </noscript>
3679 pop @{$self->{open_elements}};
3680 !!!parse-error (type => 'in noscript:script');
3681
3682 ## Reprocess in the "in head" insertion mode...
3683 pop @{$self->{open_elements}};
3684 $self->{insertion_mode} = 'after head';
3685 !!!next-token;
3686 redo B;
3687 } elsif ($self->{insertion_mode} eq 'in head') {
3688 pop @{$self->{open_elements}};
3689 $self->{insertion_mode} = 'after head';
3690 !!!next-token;
3691 redo B;
3692 } else {
3693 #
3694 }
3695 } elsif ($token->{tag_name} eq 'noscript') {
3696 if ($self->{insertion_mode} eq 'in head noscript') {
3697 pop @{$self->{open_elements}};
3698 $self->{insertion_mode} = 'in head';
3699 !!!next-token;
3700 redo B;
3701 } elsif ($self->{insertion_mode} eq 'before head') {
3702 !!!parse-error (type => 'unmatched end tag:noscript');
3703 ## Ignore the token ## ISSUE: An issue in the spec.
3704 !!!next-token;
3705 redo B;
3706 } else {
3707 #
3708 }
3709 } elsif ({
3710 body => 1, html => 1,
3711 }->{$token->{tag_name}}) {
3712 if ($self->{insertion_mode} eq 'before head') {
3713 ## As if <head>
3714 !!!create-element ($self->{head_element}, 'head');
3715 $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
3716 push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3717
3718 $self->{insertion_mode} = 'in head';
3719 ## Reprocess in the "in head" insertion mode...
3720 } elsif ($self->{insertion_mode} eq 'in head noscript') {
3721 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3722 ## Ignore the token
3723 !!!next-token;
3724 redo B;
3725 }
3726
3727 #
3728 } elsif ({
3729 p => 1, br => 1,
3730 }->{$token->{tag_name}}) {
3731 if ($self->{insertion_mode} eq 'before head') {
3732 ## As if <head>
3733 !!!create-element ($self->{head_element}, 'head');
3734 $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
3735 push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3736
3737 $self->{insertion_mode} = 'in head';
3738 ## Reprocess in the "in head" insertion mode...
3739 }
3740
3741 #
3742 } else {
3743 if ($self->{insertion_mode} ne 'after head') {
3744 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3745 ## Ignore the token
3746 !!!next-token;
3747 redo B;
3748 } else {
3749 #
3750 }
3751 }
3752
3753 if ($self->{insertion_mode} eq 'in head noscript') {
3754 ## As if </noscript>
3755 pop @{$self->{open_elements}};
3756 !!!parse-error (type => 'in noscript:/'.$token->{tag_name});
3757
3758 ## Reprocess in the "in head" insertion mode...
3759 ## As if </head>
3760 pop @{$self->{open_elements}};
3761
3762 ## Reprocess in the "after head" insertion mode...
3763 } elsif ($self->{insertion_mode} eq 'in head') {
3764 ## As if </head>
3765 pop @{$self->{open_elements}};
3766
3767 ## Reprocess in the "after head" insertion mode...
3768 } elsif ($self->{insertion_mode} eq 'before head') {
3769 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3770 ## Ignore the token ## ISSUE: An issue in the spec.
3771 !!!next-token;
3772 redo B;
3773 }
3774
3775 ## "after head" insertion mode
3776 ## As if <body>
3777 !!!insert-element ('body');
3778 $self->{insertion_mode} = 'in body';
3779 ## reprocess
3780 redo B;
3781 } else {
3782 die "$0: $token->{type}: Unknown token type";
3783 }
3784
3785 ## ISSUE: An issue in the spec.
3786 } elsif ($self->{insertion_mode} eq 'in body' or
3787 $self->{insertion_mode} eq 'in cell' or
3788 $self->{insertion_mode} eq 'in caption') {
3789 if ($token->{type} eq 'character') {
3790 ## NOTE: There is a code clone of "character in body".
3791 $reconstruct_active_formatting_elements->($insert_to_current);
3792
3793 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
3794
3795 !!!next-token;
3796 redo B;
3797 } elsif ($token->{type} eq 'start tag') {
3798 if ({
3799 caption => 1, col => 1, colgroup => 1, tbody => 1,
3800 td => 1, tfoot => 1, th => 1, thead => 1, tr => 1,
3801 }->{$token->{tag_name}}) {
3802 if ($self->{insertion_mode} eq 'in cell') {
3803 ## have an element in table scope
3804 my $tn;
3805 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3806 my $node = $self->{open_elements}->[$_];
3807 if ($node->[1] eq 'td' or $node->[1] eq 'th') {
3808 $tn = $node->[1];
3809 last INSCOPE;
3810 } elsif ({
3811 table => 1, html => 1,
3812 }->{$node->[1]}) {
3813 last INSCOPE;
3814 }
3815 } # INSCOPE
3816 unless (defined $tn) {
3817 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3818 ## Ignore the token
3819 !!!next-token;
3820 redo B;
3821 }
3822
3823 ## Close the cell
3824 !!!back-token; # <?>
3825 $token = {type => 'end tag', tag_name => $tn};
3826 redo B;
3827 } elsif ($self->{insertion_mode} eq 'in caption') {
3828 !!!parse-error (type => 'not closed:caption');
3829
3830 ## As if </caption>
3831 ## have a table element in table scope
3832 my $i;
3833 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3834 my $node = $self->{open_elements}->[$_];
3835 if ($node->[1] eq 'caption') {
3836 $i = $_;
3837 last INSCOPE;
3838 } elsif ({
3839 table => 1, html => 1,
3840 }->{$node->[1]}) {
3841 last INSCOPE;
3842 }
3843 } # INSCOPE
3844 unless (defined $i) {
3845 !!!parse-error (type => 'unmatched end tag:caption');
3846 ## Ignore the token
3847 !!!next-token;
3848 redo B;
3849 }
3850
3851 ## generate implied end tags
3852 if ({
3853 dd => 1, dt => 1, li => 1, p => 1,
3854 td => 1, th => 1, tr => 1,
3855 tbody => 1, tfoot=> 1, thead => 1,
3856 }->{$self->{open_elements}->[-1]->[1]}) {
3857 !!!back-token; # <?>
3858 $token = {type => 'end tag', tag_name => 'caption'};
3859 !!!back-token;
3860 $token = {type => 'end tag',
3861 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3862 redo B;
3863 }
3864
3865 if ($self->{open_elements}->[-1]->[1] ne 'caption') {
3866 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3867 }
3868
3869 splice @{$self->{open_elements}}, $i;
3870
3871 $clear_up_to_marker->();
3872
3873 $self->{insertion_mode} = 'in table';
3874
3875 ## reprocess
3876 redo B;
3877 } else {
3878 #
3879 }
3880 } else {
3881 #
3882 }
3883 } elsif ($token->{type} eq 'end tag') {
3884 if ($token->{tag_name} eq 'td' or $token->{tag_name} eq 'th') {
3885 if ($self->{insertion_mode} eq 'in cell') {
3886 ## have an element in table scope
3887 my $i;
3888 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3889 my $node = $self->{open_elements}->[$_];
3890 if ($node->[1] eq $token->{tag_name}) {
3891 $i = $_;
3892 last INSCOPE;
3893 } elsif ({
3894 table => 1, html => 1,
3895 }->{$node->[1]}) {
3896 last INSCOPE;
3897 }
3898 } # INSCOPE
3899 unless (defined $i) {
3900 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3901 ## Ignore the token
3902 !!!next-token;
3903 redo B;
3904 }
3905
3906 ## generate implied end tags
3907 if ({
3908 dd => 1, dt => 1, li => 1, p => 1,
3909 td => ($token->{tag_name} eq 'th'),
3910 th => ($token->{tag_name} eq 'td'),
3911 tr => 1,
3912 tbody => 1, tfoot=> 1, thead => 1,
3913 }->{$self->{open_elements}->[-1]->[1]}) {
3914 !!!back-token;
3915 $token = {type => 'end tag',
3916 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3917 redo B;
3918 }
3919
3920 if ($self->{open_elements}->[-1]->[1] ne $token->{tag_name}) {
3921 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3922 }
3923
3924 splice @{$self->{open_elements}}, $i;
3925
3926 $clear_up_to_marker->();
3927
3928 $self->{insertion_mode} = 'in row';
3929
3930 !!!next-token;
3931 redo B;
3932 } elsif ($self->{insertion_mode} eq 'in caption') {
3933 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3934 ## Ignore the token
3935 !!!next-token;
3936 redo B;
3937 } else {
3938 #
3939 }
3940 } elsif ($token->{tag_name} eq 'caption') {
3941 if ($self->{insertion_mode} eq 'in caption') {
3942 ## have a table element in table scope
3943 my $i;
3944 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3945 my $node = $self->{open_elements}->[$_];
3946 if ($node->[1] eq $token->{tag_name}) {
3947 $i = $_;
3948 last INSCOPE;
3949 } elsif ({
3950 table => 1, html => 1,
3951 }->{$node->[1]}) {
3952 last INSCOPE;
3953 }
3954 } # INSCOPE
3955 unless (defined $i) {
3956 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3957 ## Ignore the token
3958 !!!next-token;
3959 redo B;
3960 }
3961
3962 ## generate implied end tags
3963 if ({
3964 dd => 1, dt => 1, li => 1, p => 1,
3965 td => 1, th => 1, tr => 1,
3966 tbody => 1, tfoot=> 1, thead => 1,
3967 }->{$self->{open_elements}->[-1]->[1]}) {
3968 !!!back-token;
3969 $token = {type => 'end tag',
3970 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3971 redo B;
3972 }
3973
3974 if ($self->{open_elements}->[-1]->[1] ne 'caption') {
3975 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3976 }
3977
3978 splice @{$self->{open_elements}}, $i;
3979
3980 $clear_up_to_marker->();
3981
3982 $self->{insertion_mode} = 'in table';
3983
3984 !!!next-token;
3985 redo B;
3986 } elsif ($self->{insertion_mode} eq 'in cell') {
3987 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3988 ## Ignore the token
3989 !!!next-token;
3990 redo B;
3991 } else {
3992 #
3993 }
3994 } elsif ({
3995 table => 1, tbody => 1, tfoot => 1,
3996 thead => 1, tr => 1,
3997 }->{$token->{tag_name}} and
3998 $self->{insertion_mode} eq 'in cell') {
3999 ## have an element in table scope
4000 my $i;
4001 my $tn;
4002 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4003 my $node = $self->{open_elements}->[$_];
4004 if ($node->[1] eq $token->{tag_name}) {
4005 $i = $_;
4006 last INSCOPE;
4007 } elsif ($node->[1] eq 'td' or $node->[1] eq 'th') {
4008 $tn = $node->[1];
4009 ## NOTE: There is exactly one |td| or |th| element
4010 ## in scope in the stack of open elements by definition.
4011 } elsif ({
4012 table => 1, html => 1,
4013 }->{$node->[1]}) {
4014 last INSCOPE;
4015 }
4016 } # INSCOPE
4017 unless (defined $i) {
4018 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4019 ## Ignore the token
4020 !!!next-token;
4021 redo B;
4022 }
4023
4024 ## Close the cell
4025 !!!back-token; # </?>
4026 $token = {type => 'end tag', tag_name => $tn};
4027 redo B;
4028 } elsif ($token->{tag_name} eq 'table' and
4029 $self->{insertion_mode} eq 'in caption') {
4030 !!!parse-error (type => 'not closed:caption');
4031
4032 ## As if </caption>
4033 ## have a table element in table scope
4034 my $i;
4035 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4036 my $node = $self->{open_elements}->[$_];
4037 if ($node->[1] eq 'caption') {
4038 $i = $_;
4039 last INSCOPE;
4040 } elsif ({
4041 table => 1, html => 1,
4042 }->{$node->[1]}) {
4043 last INSCOPE;
4044 }
4045 } # INSCOPE
4046 unless (defined $i) {
4047 !!!parse-error (type => 'unmatched end tag:caption');
4048 ## Ignore the token
4049 !!!next-token;
4050 redo B;
4051 }
4052
4053 ## generate implied end tags
4054 if ({
4055 dd => 1, dt => 1, li => 1, p => 1,
4056 td => 1, th => 1, tr => 1,
4057 tbody => 1, tfoot=> 1, thead => 1,
4058 }->{$self->{open_elements}->[-1]->[1]}) {
4059 !!!back-token; # </table>
4060 $token = {type => 'end tag', tag_name => 'caption'};
4061 !!!back-token;
4062 $token = {type => 'end tag',
4063 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
4064 redo B;
4065 }
4066
4067 if ($self->{open_elements}->[-1]->[1] ne 'caption') {
4068 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4069 }
4070
4071 splice @{$self->{open_elements}}, $i;
4072
4073 $clear_up_to_marker->();
4074
4075 $self->{insertion_mode} = 'in table';
4076
4077 ## reprocess
4078 redo B;
4079 } elsif ({
4080 body => 1, col => 1, colgroup => 1, html => 1,
4081 }->{$token->{tag_name}}) {
4082 if ($self->{insertion_mode} eq 'in cell' or
4083 $self->{insertion_mode} eq 'in caption') {
4084 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4085 ## Ignore the token
4086 !!!next-token;
4087 redo B;
4088 } else {
4089 #
4090 }
4091 } elsif ({
4092 tbody => 1, tfoot => 1,
4093 thead => 1, tr => 1,
4094 }->{$token->{tag_name}} and
4095 $self->{insertion_mode} eq 'in caption') {
4096 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4097 ## Ignore the token
4098 !!!next-token;
4099 redo B;
4100 } else {
4101 #
4102 }
4103 } else {
4104 #
4105 }
4106
4107 $in_body->($insert_to_current);
4108 redo B;
4109 } elsif ($self->{insertion_mode} eq 'in row' or
4110 $self->{insertion_mode} eq 'in table body' or
4111 $self->{insertion_mode} eq 'in table') {
4112 if ($token->{type} eq 'character') {
4113 ## NOTE: There are "character in table" code clones.
4114 if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
4115 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
4116
4117 unless (length $token->{data}) {
4118 !!!next-token;
4119 redo B;
4120 }
4121 }
4122
4123 !!!parse-error (type => 'in table:#character');
4124
4125 ## As if in body, but insert into foster parent element
4126 ## ISSUE: Spec says that "whenever a node would be inserted
4127 ## into the current node" while characters might not be
4128 ## result in a new Text node.
4129 $reconstruct_active_formatting_elements->($insert_to_foster);
4130
4131 if ({
4132 table => 1, tbody => 1, tfoot => 1,
4133 thead => 1, tr => 1,
4134 }->{$self->{open_elements}->[-1]->[1]}) {
4135 # MUST
4136 my $foster_parent_element;
4137 my $next_sibling;
4138 my $prev_sibling;
4139 OE: for (reverse 0..$#{$self->{open_elements}}) {
4140 if ($self->{open_elements}->[$_]->[1] eq 'table') {
4141 my $parent = $self->{open_elements}->[$_]->[0]->parent_node;
4142 if (defined $parent and $parent->node_type == 1) {
4143 $foster_parent_element = $parent;
4144 $next_sibling = $self->{open_elements}->[$_]->[0];
4145 $prev_sibling = $next_sibling->previous_sibling;
4146 } else {
4147 $foster_parent_element = $self->{open_elements}->[$_ - 1]->[0];
4148 $prev_sibling = $foster_parent_element->last_child;
4149 }
4150 last OE;
4151 }
4152 } # OE
4153 $foster_parent_element = $self->{open_elements}->[0]->[0] and
4154 $prev_sibling = $foster_parent_element->last_child
4155 unless defined $foster_parent_element;
4156 if (defined $prev_sibling and
4157 $prev_sibling->node_type == 3) {
4158 $prev_sibling->manakai_append_text ($token->{data});
4159 } else {
4160 $foster_parent_element->insert_before
4161 ($self->{document}->create_text_node ($token->{data}),
4162 $next_sibling);
4163 }
4164 } else {
4165 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
4166 }
4167
4168 !!!next-token;
4169 redo B;
4170 } elsif ($token->{type} eq 'start tag') {
4171 if ({
4172 tr => ($self->{insertion_mode} ne 'in row'),
4173 th => 1, td => 1,
4174 }->{$token->{tag_name}}) {
4175 if ($self->{insertion_mode} eq 'in table') {
4176 ## Clear back to table context
4177 while ($self->{open_elements}->[-1]->[1] ne 'table' and
4178 $self->{open_elements}->[-1]->[1] ne 'html') {
4179 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4180 pop @{$self->{open_elements}};
4181 }
4182
4183 !!!insert-element ('tbody');
4184 $self->{insertion_mode} = 'in table body';
4185 ## reprocess in the "in table body" insertion mode...
4186 }
4187
4188 if ($self->{insertion_mode} eq 'in table body') {
4189 unless ($token->{tag_name} eq 'tr') {
4190 !!!parse-error (type => 'missing start tag:tr');
4191 }
4192
4193 ## Clear back to table body context
4194 while (not {
4195 tbody => 1, tfoot => 1, thead => 1, html => 1,
4196 }->{$self->{open_elements}->[-1]->[1]}) {
4197 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4198 pop @{$self->{open_elements}};
4199 }
4200
4201 $self->{insertion_mode} = 'in row';
4202 if ($token->{tag_name} eq 'tr') {
4203 !!!insert-element ($token->{tag_name}, $token->{attributes});
4204 !!!next-token;
4205 redo B;
4206 } else {
4207 !!!insert-element ('tr');
4208 ## reprocess in the "in row" insertion mode
4209 }
4210 }
4211
4212 ## Clear back to table row context
4213 while (not {
4214 tr => 1, html => 1,
4215 }->{$self->{open_elements}->[-1]->[1]}) {
4216 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4217 pop @{$self->{open_elements}};
4218 }
4219
4220 !!!insert-element ($token->{tag_name}, $token->{attributes});
4221 $self->{insertion_mode} = 'in cell';
4222
4223 push @$active_formatting_elements, ['#marker', ''];
4224
4225 !!!next-token;
4226 redo B;
4227 } elsif ({
4228 caption => 1, col => 1, colgroup => 1,
4229 tbody => 1, tfoot => 1, thead => 1,
4230 tr => 1, # $self->{insertion_mode} eq 'in row'
4231 }->{$token->{tag_name}}) {
4232 if ($self->{insertion_mode} eq 'in row') {
4233 ## As if </tr>
4234 ## have an element in table scope
4235 my $i;
4236 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4237 my $node = $self->{open_elements}->[$_];
4238 if ($node->[1] eq 'tr') {
4239 $i = $_;
4240 last INSCOPE;
4241 } elsif ({
4242 table => 1, html => 1,
4243 }->{$node->[1]}) {
4244 last INSCOPE;
4245 }
4246 } # INSCOPE
4247 unless (defined $i) {
4248 !!!parse-error (type => 'unmacthed end tag:'.$token->{tag_name});
4249 ## Ignore the token
4250 !!!next-token;
4251 redo B;
4252 }
4253
4254 ## Clear back to table row context
4255 while (not {
4256 tr => 1, html => 1,
4257 }->{$self->{open_elements}->[-1]->[1]}) {
4258 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4259 pop @{$self->{open_elements}};
4260 }
4261
4262 pop @{$self->{open_elements}}; # tr
4263 $self->{insertion_mode} = 'in table body';
4264 if ($token->{tag_name} eq 'tr') {
4265 ## reprocess
4266 redo B;
4267 } else {
4268 ## reprocess in the "in table body" insertion mode...
4269 }
4270 }
4271
4272 if ($self->{insertion_mode} eq 'in table body') {
4273 ## have an element in table scope
4274 my $i;
4275 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4276 my $node = $self->{open_elements}->[$_];
4277 if ({
4278 tbody => 1, thead => 1, tfoot => 1,
4279 }->{$node->[1]}) {
4280 $i = $_;
4281 last INSCOPE;
4282 } elsif ({
4283 table => 1, html => 1,
4284 }->{$node->[1]}) {
4285 last INSCOPE;
4286 }
4287 } # INSCOPE
4288 unless (defined $i) {
4289 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4290 ## Ignore the token
4291 !!!next-token;
4292 redo B;
4293 }
4294
4295 ## Clear back to table body context
4296 while (not {
4297 tbody => 1, tfoot => 1, thead => 1, html => 1,
4298 }->{$self->{open_elements}->[-1]->[1]}) {
4299 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4300 pop @{$self->{open_elements}};
4301 }
4302
4303 ## As if <{current node}>
4304 ## have an element in table scope
4305 ## true by definition
4306
4307 ## Clear back to table body context
4308 ## nop by definition
4309
4310 pop @{$self->{open_elements}};
4311 $self->{insertion_mode} = 'in table';
4312 ## reprocess in "in table" insertion mode...
4313 }
4314
4315 if ($token->{tag_name} eq 'col') {
4316 ## Clear back to table context
4317 while ($self->{open_elements}->[-1]->[1] ne 'table' and
4318 $self->{open_elements}->[-1]->[1] ne 'html') {
4319 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4320 pop @{$self->{open_elements}};
4321 }
4322
4323 !!!insert-element ('colgroup');
4324 $self->{insertion_mode} = 'in column group';
4325 ## reprocess
4326 redo B;
4327 } elsif ({
4328 caption => 1,
4329 colgroup => 1,
4330 tbody => 1, tfoot => 1, thead => 1,
4331 }->{$token->{tag_name}}) {
4332 ## Clear back to table context
4333 while ($self->{open_elements}->[-1]->[1] ne 'table' and
4334 $self->{open_elements}->[-1]->[1] ne 'html') {
4335 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4336 pop @{$self->{open_elements}};
4337 }
4338
4339 push @$active_formatting_elements, ['#marker', '']
4340 if $token->{tag_name} eq 'caption';
4341
4342 !!!insert-element ($token->{tag_name}, $token->{attributes});
4343 $self->{insertion_mode} = {
4344 caption => 'in caption',
4345 colgroup => 'in column group',
4346 tbody => 'in table body',
4347 tfoot => 'in table body',
4348 thead => 'in table body',
4349 }->{$token->{tag_name}};
4350 !!!next-token;
4351 redo B;
4352 } else {
4353 die "$0: in table: <>: $token->{tag_name}";
4354 }
4355 } elsif ($token->{tag_name} eq 'table') {
4356 ## NOTE: There are code clones for this "table in table"
4357 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4358
4359 ## As if </table>
4360 ## have a table element in table scope
4361 my $i;
4362 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4363 my $node = $self->{open_elements}->[$_];
4364 if ($node->[1] eq 'table') {
4365 $i = $_;
4366 last INSCOPE;
4367 } elsif ({
4368 table => 1, html => 1,
4369 }->{$node->[1]}) {
4370 last INSCOPE;
4371 }
4372 } # INSCOPE
4373 unless (defined $i) {
4374 !!!parse-error (type => 'unmatched end tag:table');
4375 ## Ignore tokens </table><table>
4376 !!!next-token;
4377 redo B;
4378 }
4379
4380 ## generate implied end tags
4381 if ({
4382 dd => 1, dt => 1, li => 1, p => 1,
4383 td => 1, th => 1, tr => 1,
4384 tbody => 1, tfoot=> 1, thead => 1,
4385 }->{$self->{open_elements}->[-1]->[1]}) {
4386 !!!back-token; # <table>
4387 $token = {type => 'end tag', tag_name => 'table'};
4388 !!!back-token;
4389 $token = {type => 'end tag',
4390 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
4391 redo B;
4392 }
4393
4394 if ($self->{open_elements}->[-1]->[1] ne 'table') {
4395 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4396 }
4397
4398 splice @{$self->{open_elements}}, $i;
4399
4400 $self->_reset_insertion_mode;
4401
4402 ## reprocess
4403 redo B;
4404 } else {
4405 #
4406 }
4407 } elsif ($token->{type} eq 'end tag') {
4408 if ($token->{tag_name} eq 'tr' and
4409 $self->{insertion_mode} eq 'in row') {
4410 ## have an element in table scope
4411 my $i;
4412 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4413 my $node = $self->{open_elements}->[$_];
4414 if ($node->[1] eq $token->{tag_name}) {
4415 $i = $_;
4416 last INSCOPE;
4417 } elsif ({
4418 table => 1, html => 1,
4419 }->{$node->[1]}) {
4420 last INSCOPE;
4421 }
4422 } # INSCOPE
4423 unless (defined $i) {
4424 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4425 ## Ignore the token
4426 !!!next-token;
4427 redo B;
4428 }
4429
4430 ## Clear back to table row context
4431 while (not {
4432 tr => 1, html => 1,
4433 }->{$self->{open_elements}->[-1]->[1]}) {
4434 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4435 pop @{$self->{open_elements}};
4436 }
4437
4438 pop @{$self->{open_elements}}; # tr
4439 $self->{insertion_mode} = 'in table body';
4440 !!!next-token;
4441 redo B;
4442 } elsif ($token->{tag_name} eq 'table') {
4443 if ($self->{insertion_mode} eq 'in row') {
4444 ## As if </tr>
4445 ## have an element in table scope
4446 my $i;
4447 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4448 my $node = $self->{open_elements}->[$_];
4449 if ($node->[1] eq 'tr') {
4450 $i = $_;
4451 last INSCOPE;
4452 } elsif ({
4453 table => 1, html => 1,
4454 }->{$node->[1]}) {
4455 last INSCOPE;
4456 }
4457 } # INSCOPE
4458 unless (defined $i) {
4459 !!!parse-error (type => 'unmatched end tag:'.$token->{type});
4460 ## Ignore the token
4461 !!!next-token;
4462 redo B;
4463 }
4464
4465 ## Clear back to table row context
4466 while (not {
4467 tr => 1, html => 1,
4468 }->{$self->{open_elements}->[-1]->[1]}) {
4469 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4470 pop @{$self->{open_elements}};
4471 }
4472
4473 pop @{$self->{open_elements}}; # tr
4474 $self->{insertion_mode} = 'in table body';
4475 ## reprocess in the "in table body" insertion mode...
4476 }
4477
4478 if ($self->{insertion_mode} eq 'in table body') {
4479 ## have an element in table scope
4480 my $i;
4481 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4482 my $node = $self->{open_elements}->[$_];
4483 if ({
4484 tbody => 1, thead => 1, tfoot => 1,
4485 }->{$node->[1]}) {
4486 $i = $_;
4487 last INSCOPE;
4488 } elsif ({
4489 table => 1, html => 1,
4490 }->{$node->[1]}) {
4491 last INSCOPE;
4492 }
4493 } # INSCOPE
4494 unless (defined $i) {
4495 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4496 ## Ignore the token
4497 !!!next-token;
4498 redo B;
4499 }
4500
4501 ## Clear back to table body context
4502 while (not {
4503 tbody => 1, tfoot => 1, thead => 1, html => 1,
4504 }->{$self->{open_elements}->[-1]->[1]}) {
4505 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4506 pop @{$self->{open_elements}};
4507 }
4508
4509 ## As if <{current node}>
4510 ## have an element in table scope
4511 ## true by definition
4512
4513 ## Clear back to table body context
4514 ## nop by definition
4515
4516 pop @{$self->{open_elements}};
4517 $self->{insertion_mode} = 'in table';
4518 ## reprocess in the "in table" insertion mode...
4519 }
4520
4521 ## have a table element in table scope
4522 my $i;
4523 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4524 my $node = $self->{open_elements}->[$_];
4525 if ($node->[1] eq $token->{tag_name}) {
4526 $i = $_;
4527 last INSCOPE;
4528 } elsif ({
4529 table => 1, html => 1,
4530 }->{$node->[1]}) {
4531 last INSCOPE;
4532 }
4533 } # INSCOPE
4534 unless (defined $i) {
4535 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4536 ## Ignore the token
4537 !!!next-token;
4538 redo B;
4539 }
4540
4541 ## generate implied end tags
4542 if ({
4543 dd => 1, dt => 1, li => 1, p => 1,
4544 td => 1, th => 1, tr => 1,
4545 tbody => 1, tfoot=> 1, thead => 1,
4546 }->{$self->{open_elements}->[-1]->[1]}) {
4547 !!!back-token;
4548 $token = {type => 'end tag',
4549 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
4550 redo B;
4551 }
4552
4553 if ($self->{open_elements}->[-1]->[1] ne 'table') {
4554 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4555 }
4556
4557 splice @{$self->{open_elements}}, $i;
4558
4559 $self->_reset_insertion_mode;
4560
4561 !!!next-token;
4562 redo B;
4563 } elsif ({
4564 tbody => 1, tfoot => 1, thead => 1,
4565 }->{$token->{tag_name}} and
4566 ($self->{insertion_mode} eq 'in row' or
4567 $self->{insertion_mode} eq 'in table body')) {
4568 if ($self->{insertion_mode} eq 'in row') {
4569 ## have an element in table scope
4570 my $i;
4571 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4572 my $node = $self->{open_elements}->[$_];
4573 if ($node->[1] eq $token->{tag_name}) {
4574 $i = $_;
4575 last INSCOPE;
4576 } elsif ({
4577 table => 1, html => 1,
4578 }->{$node->[1]}) {
4579 last INSCOPE;
4580 }
4581 } # INSCOPE
4582 unless (defined $i) {
4583 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4584 ## Ignore the token
4585 !!!next-token;
4586 redo B;
4587 }
4588
4589 ## As if </tr>
4590 ## have an element in table scope
4591 my $i;
4592 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4593 my $node = $self->{open_elements}->[$_];
4594 if ($node->[1] eq 'tr') {
4595 $i = $_;
4596 last INSCOPE;
4597 } elsif ({
4598 table => 1, html => 1,
4599 }->{$node->[1]}) {
4600 last INSCOPE;
4601 }
4602 } # INSCOPE
4603 unless (defined $i) {
4604 !!!parse-error (type => 'unmatched end tag:tr');
4605 ## Ignore the token
4606 !!!next-token;
4607 redo B;
4608 }
4609
4610 ## Clear back to table row context
4611 while (not {
4612 tr => 1, html => 1,
4613 }->{$self->{open_elements}->[-1]->[1]}) {
4614 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4615 pop @{$self->{open_elements}};
4616 }
4617
4618 pop @{$self->{open_elements}}; # tr
4619 $self->{insertion_mode} = 'in table body';
4620 ## reprocess in the "in table body" insertion mode...
4621 }
4622
4623 ## have an 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 $i = $_;
4629 last INSCOPE;
4630 } elsif ({
4631 table => 1, html => 1,
4632 }->{$node->[1]}) {
4633 last INSCOPE;
4634 }
4635 } # INSCOPE
4636 unless (defined $i) {
4637 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4638 ## Ignore the token
4639 !!!next-token;
4640 redo B;
4641 }
4642
4643 ## Clear back to table body context
4644 while (not {
4645 tbody => 1, tfoot => 1, thead => 1, html => 1,
4646 }->{$self->{open_elements}->[-1]->[1]}) {
4647 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4648 pop @{$self->{open_elements}};
4649 }
4650
4651 pop @{$self->{open_elements}};
4652 $self->{insertion_mode} = 'in table';
4653 !!!next-token;
4654 redo B;
4655 } elsif ({
4656 body => 1, caption => 1, col => 1, colgroup => 1,
4657 html => 1, td => 1, th => 1,
4658 tr => 1, # $self->{insertion_mode} eq 'in row'
4659 tbody => 1, tfoot => 1, thead => 1, # $self->{insertion_mode} eq 'in table'
4660 }->{$token->{tag_name}}) {
4661 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4662 ## Ignore the token
4663 !!!next-token;
4664 redo B;
4665 } else {
4666 #
4667 }
4668 } else {
4669 die "$0: $token->{type}: Unknown token type";
4670 }
4671
4672 !!!parse-error (type => 'in table:'.$token->{tag_name});
4673 $in_body->($insert_to_foster);
4674 redo B;
4675 } elsif ($self->{insertion_mode} eq 'in column group') {
4676 if ($token->{type} eq 'character') {
4677 if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
4678 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
4679 unless (length $token->{data}) {
4680 !!!next-token;
4681 redo B;
4682 }
4683 }
4684
4685 #
4686 } elsif ($token->{type} eq 'start tag') {
4687 if ($token->{tag_name} eq 'col') {
4688 !!!insert-element ($token->{tag_name}, $token->{attributes});
4689 pop @{$self->{open_elements}};
4690 !!!next-token;
4691 redo B;
4692 } else {
4693 #
4694 }
4695 } elsif ($token->{type} eq 'end tag') {
4696 if ($token->{tag_name} eq 'colgroup') {
4697 if ($self->{open_elements}->[-1]->[1] eq 'html') {
4698 !!!parse-error (type => 'unmatched end tag:colgroup');
4699 ## Ignore the token
4700 !!!next-token;
4701 redo B;
4702 } else {
4703 pop @{$self->{open_elements}}; # colgroup
4704 $self->{insertion_mode} = 'in table';
4705 !!!next-token;
4706 redo B;
4707 }
4708 } elsif ($token->{tag_name} eq 'col') {
4709 !!!parse-error (type => 'unmatched end tag:col');
4710 ## Ignore the token
4711 !!!next-token;
4712 redo B;
4713 } else {
4714 #
4715 }
4716 } else {
4717 #
4718 }
4719
4720 ## As if </colgroup>
4721 if ($self->{open_elements}->[-1]->[1] eq 'html') {
4722 !!!parse-error (type => 'unmatched end tag:colgroup');
4723 ## Ignore the token
4724 !!!next-token;
4725 redo B;
4726 } else {
4727 pop @{$self->{open_elements}}; # colgroup
4728 $self->{insertion_mode} = 'in table';
4729 ## reprocess
4730 redo B;
4731 }
4732 } elsif ($self->{insertion_mode} eq 'in select') {
4733 if ($token->{type} eq 'character') {
4734 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
4735 !!!next-token;
4736 redo B;
4737 } elsif ($token->{type} eq 'start tag') {
4738 if ($token->{tag_name} eq 'option') {
4739 if ($self->{open_elements}->[-1]->[1] eq 'option') {
4740 ## As if </option>
4741 pop @{$self->{open_elements}};
4742 }
4743
4744 !!!insert-element ($token->{tag_name}, $token->{attributes});
4745 !!!next-token;
4746 redo B;
4747 } elsif ($token->{tag_name} eq 'optgroup') {
4748 if ($self->{open_elements}->[-1]->[1] eq 'option') {
4749 ## As if </option>
4750 pop @{$self->{open_elements}};
4751 }
4752
4753 if ($self->{open_elements}->[-1]->[1] eq 'optgroup') {
4754 ## As if </optgroup>
4755 pop @{$self->{open_elements}};
4756 }
4757
4758 !!!insert-element ($token->{tag_name}, $token->{attributes});
4759 !!!next-token;
4760 redo B;
4761 } elsif ($token->{tag_name} eq 'select') {
4762 !!!parse-error (type => 'not closed:select');
4763 ## As if </select> instead
4764 ## have an element in table scope
4765 my $i;
4766 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4767 my $node = $self->{open_elements}->[$_];
4768 if ($node->[1] eq $token->{tag_name}) {
4769 $i = $_;
4770 last INSCOPE;
4771 } elsif ({
4772 table => 1, html => 1,
4773 }->{$node->[1]}) {
4774 last INSCOPE;
4775 }
4776 } # INSCOPE
4777 unless (defined $i) {
4778 !!!parse-error (type => 'unmatched end tag:select');
4779 ## Ignore the token
4780 !!!next-token;
4781 redo B;
4782 }
4783
4784 splice @{$self->{open_elements}}, $i;
4785
4786 $self->_reset_insertion_mode;
4787
4788 !!!next-token;
4789 redo B;
4790 } else {
4791 #
4792 }
4793 } elsif ($token->{type} eq 'end tag') {
4794 if ($token->{tag_name} eq 'optgroup') {
4795 if ($self->{open_elements}->[-1]->[1] eq 'option' and
4796 $self->{open_elements}->[-2]->[1] eq 'optgroup') {
4797 ## As if </option>
4798 splice @{$self->{open_elements}}, -2;
4799 } elsif ($self->{open_elements}->[-1]->[1] eq 'optgroup') {
4800 pop @{$self->{open_elements}};
4801 } else {
4802 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4803 ## Ignore the token
4804 }
4805 !!!next-token;
4806 redo B;
4807 } elsif ($token->{tag_name} eq 'option') {
4808 if ($self->{open_elements}->[-1]->[1] eq 'option') {
4809 pop @{$self->{open_elements}};
4810 } else {
4811 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4812 ## Ignore the token
4813 }
4814 !!!next-token;
4815 redo B;
4816 } elsif ($token->{tag_name} eq 'select') {
4817 ## have an element in table scope
4818 my $i;
4819 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4820 my $node = $self->{open_elements}->[$_];
4821 if ($node->[1] eq $token->{tag_name}) {
4822 $i = $_;
4823 last INSCOPE;
4824 } elsif ({
4825 table => 1, html => 1,
4826 }->{$node->[1]}) {
4827 last INSCOPE;
4828 }
4829 } # INSCOPE
4830 unless (defined $i) {
4831 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4832 ## Ignore the token
4833 !!!next-token;
4834 redo B;
4835 }
4836
4837 splice @{$self->{open_elements}}, $i;
4838
4839 $self->_reset_insertion_mode;
4840
4841 !!!next-token;
4842 redo B;
4843 } elsif ({
4844 caption => 1, table => 1, tbody => 1,
4845 tfoot => 1, thead => 1, tr => 1, td => 1, th => 1,
4846 }->{$token->{tag_name}}) {
4847 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4848
4849 ## have an element in table scope
4850 my $i;
4851 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4852 my $node = $self->{open_elements}->[$_];
4853 if ($node->[1] eq $token->{tag_name}) {
4854 $i = $_;
4855 last INSCOPE;
4856 } elsif ({
4857 table => 1, html => 1,
4858 }->{$node->[1]}) {
4859 last INSCOPE;
4860 }
4861 } # INSCOPE
4862 unless (defined $i) {
4863 ## Ignore the token
4864 !!!next-token;
4865 redo B;
4866 }
4867
4868 ## As if </select>
4869 ## have an element in table scope
4870 undef $i;
4871 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4872 my $node = $self->{open_elements}->[$_];
4873 if ($node->[1] eq 'select') {
4874 $i = $_;
4875 last INSCOPE;
4876 } elsif ({
4877 table => 1, html => 1,
4878 }->{$node->[1]}) {
4879 last INSCOPE;
4880 }
4881 } # INSCOPE
4882 unless (defined $i) {
4883 !!!parse-error (type => 'unmatched end tag:select');
4884 ## Ignore the </select> token
4885 !!!next-token; ## TODO: ok?
4886 redo B;
4887 }
4888
4889 splice @{$self->{open_elements}}, $i;
4890
4891 $self->_reset_insertion_mode;
4892
4893 ## reprocess
4894 redo B;
4895 } else {
4896 #
4897 }
4898 } else {
4899 #
4900 }
4901
4902 !!!parse-error (type => 'in select:'.$token->{tag_name});
4903 ## Ignore the token
4904 !!!next-token;
4905 redo B;
4906 } elsif ($self->{insertion_mode} eq 'after body') {
4907 if ($token->{type} eq 'character') {
4908 if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
4909 my $data = $1;
4910 ## As if in body
4911 $reconstruct_active_formatting_elements->($insert_to_current);
4912
4913 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
4914
4915 unless (length $token->{data}) {
4916 !!!next-token;
4917 redo B;
4918 }
4919 }
4920
4921 #
4922 !!!parse-error (type => 'after body:#character');
4923 } elsif ($token->{type} eq 'start tag') {
4924 !!!parse-error (type => 'after body:'.$token->{tag_name});
4925 #
4926 } elsif ($token->{type} eq 'end tag') {
4927 if ($token->{tag_name} eq 'html') {
4928 if (defined $self->{inner_html_node}) {
4929 !!!parse-error (type => 'unmatched end tag:html');
4930 ## Ignore the token
4931 !!!next-token;
4932 redo B;
4933 } else {
4934 $previous_insertion_mode = $self->{insertion_mode};
4935 $self->{insertion_mode} = 'trailing end';
4936 !!!next-token;
4937 redo B;
4938 }
4939 } else {
4940 !!!parse-error (type => 'after body:/'.$token->{tag_name});
4941 }
4942 } else {
4943 die "$0: $token->{type}: Unknown token type";
4944 }
4945
4946 $self->{insertion_mode} = 'in body';
4947 ## reprocess
4948 redo B;
4949 } elsif ($self->{insertion_mode} eq 'in frameset') {
4950 if ($token->{type} eq 'character') {
4951 if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
4952 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
4953
4954 unless (length $token->{data}) {
4955 !!!next-token;
4956 redo B;
4957 }
4958 }
4959
4960 !!!parse-error (type => 'in frameset:#character');
4961 ## Ignore the token
4962 !!!next-token;
4963 redo B;
4964 } elsif ($token->{type} eq 'start tag') {
4965 if ($token->{tag_name} eq 'frameset') {
4966 !!!insert-element ($token->{tag_name}, $token->{attributes});
4967 !!!next-token;
4968 redo B;
4969 } elsif ($token->{tag_name} eq 'frame') {
4970 !!!insert-element ($token->{tag_name}, $token->{attributes});
4971 pop @{$self->{open_elements}};
4972 !!!next-token;
4973 redo B;
4974 } elsif ($token->{tag_name} eq 'noframes') {
4975 ## NOTE: As if in body.
4976 $parse_rcdata->(CDATA_CONTENT_MODEL, $insert_to_current);
4977 redo B;
4978 } else {
4979 !!!parse-error (type => 'in frameset:'.$token->{tag_name});
4980 ## Ignore the token
4981 !!!next-token;
4982 redo B;
4983 }
4984 } elsif ($token->{type} eq 'end tag') {
4985 if ($token->{tag_name} eq 'frameset') {
4986 if ($self->{open_elements}->[-1]->[1] eq 'html' and
4987 @{$self->{open_elements}} == 1) {
4988 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4989 ## Ignore the token
4990 !!!next-token;
4991 } else {
4992 pop @{$self->{open_elements}};
4993 !!!next-token;
4994 }
4995
4996 if (not defined $self->{inner_html_node} and
4997 $self->{open_elements}->[-1]->[1] ne 'frameset') {
4998 $self->{insertion_mode} = 'after frameset';
4999 }
5000 redo B;
5001 } else {
5002 !!!parse-error (type => 'in frameset:/'.$token->{tag_name});
5003 ## Ignore the token
5004 !!!next-token;
5005 redo B;
5006 }
5007 } else {
5008 die "$0: $token->{type}: Unknown token type";
5009 }
5010 } elsif ($self->{insertion_mode} eq 'after frameset') {
5011 if ($token->{type} eq 'character') {
5012 if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
5013 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
5014
5015 unless (length $token->{data}) {
5016 !!!next-token;
5017 redo B;
5018 }
5019 }
5020
5021 if ($token->{data} =~ s/^[^\x09\x0A\x0B\x0C\x20]+//) {
5022 !!!parse-error (type => 'after frameset:#character');
5023
5024 ## Ignore the token.
5025 if (length $token->{data}) {
5026 ## reprocess the rest of characters
5027 } else {
5028 !!!next-token;
5029 }
5030 redo B;
5031 }
5032
5033 die qq[$0: Character "$token->{data}"];
5034 } elsif ($token->{type} eq 'start tag') {
5035 if ($token->{tag_name} eq 'noframes') {
5036 ## NOTE: As if in body.
5037 $parse_rcdata->(CDATA_CONTENT_MODEL, $insert_to_current);
5038 redo B;
5039 } else {
5040 !!!parse-error (type => 'after frameset:'.$token->{tag_name});
5041 ## Ignore the token
5042 !!!next-token;
5043 redo B;
5044 }
5045 } elsif ($token->{type} eq 'end tag') {
5046 if ($token->{tag_name} eq 'html') {
5047 $previous_insertion_mode = $self->{insertion_mode};
5048 $self->{insertion_mode} = 'trailing end';
5049 !!!next-token;
5050 redo B;
5051 } else {
5052 !!!parse-error (type => 'after frameset:/'.$token->{tag_name});
5053 ## Ignore the token
5054 !!!next-token;
5055 redo B;
5056 }
5057 } else {
5058 die "$0: $token->{type}: Unknown token type";
5059 }
5060
5061 ## ISSUE: An issue in spec here
5062 } elsif ($self->{insertion_mode} eq 'trailing end') {
5063 ## states in the main stage is preserved yet # MUST
5064
5065 if ($token->{type} eq 'character') {
5066 if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
5067 my $data = $1;
5068 ## As if in the main phase.
5069 ## NOTE: The insertion mode in the main phase
5070 ## just before the phase has been changed to the trailing
5071 ## end phase is either "after body" or "after frameset".
5072 $reconstruct_active_formatting_elements->($insert_to_current);
5073
5074 $self->{open_elements}->[-1]->[0]->manakai_append_text ($data);
5075
5076 unless (length $token->{data}) {
5077 !!!next-token;
5078 redo B;
5079 }
5080 }
5081
5082 !!!parse-error (type => 'after html:#character');
5083 $self->{insertion_mode} = $previous_insertion_mode;
5084 ## reprocess
5085 redo B;
5086 } elsif ($token->{type} eq 'start tag') {
5087 !!!parse-error (type => 'after html:'.$token->{tag_name});
5088 $self->{insertion_mode} = $previous_insertion_mode;
5089 ## reprocess
5090 redo B;
5091 } elsif ($token->{type} eq 'end tag') {
5092 !!!parse-error (type => 'after html:/'.$token->{tag_name});
5093 $self->{insertion_mode} = $previous_insertion_mode;
5094 ## reprocess
5095 redo B;
5096 } else {
5097 die "$0: $token->{type}: Unknown token";
5098 }
5099 } else {
5100 die "$0: $self->{insertion_mode}: Unknown insertion mode";
5101 }
5102 } # B
5103
5104 ## Stop parsing # MUST
5105
5106 ## TODO: script stuffs
5107 } # _tree_construct_main
5108
5109 sub set_inner_html ($$$) {
5110 my $class = shift;
5111 my $node = shift;
5112 my $s = \$_[0];
5113 my $onerror = $_[1];
5114
5115 my $nt = $node->node_type;
5116 if ($nt == 9) {
5117 # MUST
5118
5119 ## Step 1 # MUST
5120 ## TODO: If the document has an active parser, ...
5121 ## ISSUE: There is an issue in the spec.
5122
5123 ## Step 2 # MUST
5124 my @cn = @{$node->child_nodes};
5125 for (@cn) {
5126 $node->remove_child ($_);
5127 }
5128
5129 ## Step 3, 4, 5 # MUST
5130 $class->parse_string ($$s => $node, $onerror);
5131 } elsif ($nt == 1) {
5132 ## TODO: If non-html element
5133
5134 ## NOTE: Most of this code is copied from |parse_string|
5135
5136 ## Step 1 # MUST
5137 my $this_doc = $node->owner_document;
5138 my $doc = $this_doc->implementation->create_document;
5139 $doc->manakai_is_html (1);
5140 my $p = $class->new;
5141 $p->{document} = $doc;
5142
5143 ## Step 9 # MUST
5144 my $i = 0;
5145 my $line = 1;
5146 my $column = 0;
5147 $p->{set_next_input_character} = sub {
5148 my $self = shift;
5149
5150 pop @{$self->{prev_input_character}};
5151 unshift @{$self->{prev_input_character}}, $self->{next_input_character};
5152
5153 $self->{next_input_character} = -1 and return if $i >= length $$s;
5154 $self->{next_input_character} = ord substr $$s, $i++, 1;
5155 $column++;
5156
5157 if ($self->{next_input_character} == 0x000A) { # LF
5158 $line++;
5159 $column = 0;
5160 } elsif ($self->{next_input_character} == 0x000D) { # CR
5161 $i++ if substr ($$s, $i, 1) eq "\x0A";
5162 $self->{next_input_character} = 0x000A; # LF # MUST
5163 $line++;
5164 $column = 0;
5165 } elsif ($self->{next_input_character} > 0x10FFFF) {
5166 $self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
5167 } elsif ($self->{next_input_character} == 0x0000) { # NULL
5168 !!!parse-error (type => 'NULL');
5169 $self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
5170 }
5171 };
5172 $p->{prev_input_character} = [-1, -1, -1];
5173 $p->{next_input_character} = -1;
5174
5175 my $ponerror = $onerror || sub {
5176 my (%opt) = @_;
5177 warn "Parse error ($opt{type}) at line $opt{line} column $opt{column}\n";
5178 };
5179 $p->{parse_error} = sub {
5180 $ponerror->(@_, line => $line, column => $column);
5181 };
5182
5183 $p->_initialize_tokenizer;
5184 $p->_initialize_tree_constructor;
5185
5186 ## Step 2
5187 my $node_ln = $node->local_name;
5188 $p->{content_model} = {
5189 title => RCDATA_CONTENT_MODEL,
5190 textarea => RCDATA_CONTENT_MODEL,
5191 style => CDATA_CONTENT_MODEL,
5192 script => CDATA_CONTENT_MODEL,
5193 xmp => CDATA_CONTENT_MODEL,
5194 iframe => CDATA_CONTENT_MODEL,
5195 noembed => CDATA_CONTENT_MODEL,
5196 noframes => CDATA_CONTENT_MODEL,
5197 noscript => CDATA_CONTENT_MODEL,
5198 plaintext => PLAINTEXT_CONTENT_MODEL,
5199 }->{$node_ln};
5200 $p->{content_model} = PCDATA_CONTENT_MODEL
5201 unless defined $p->{content_model};
5202 ## ISSUE: What is "the name of the element"? local name?
5203
5204 $p->{inner_html_node} = [$node, $node_ln];
5205
5206 ## Step 4
5207 my $root = $doc->create_element_ns
5208 ('http://www.w3.org/1999/xhtml', [undef, 'html']);
5209
5210 ## Step 5 # MUST
5211 $doc->append_child ($root);
5212
5213 ## Step 6 # MUST
5214 push @{$p->{open_elements}}, [$root, 'html'];
5215
5216 undef $p->{head_element};
5217
5218 ## Step 7 # MUST
5219 $p->_reset_insertion_mode;
5220
5221 ## Step 8 # MUST
5222 my $anode = $node;
5223 AN: while (defined $anode) {
5224 if ($anode->node_type == 1) {
5225 my $nsuri = $anode->namespace_uri;
5226 if (defined $nsuri and $nsuri eq 'http://www.w3.org/1999/xhtml') {
5227 if ($anode->local_name eq 'form') { ## TODO: case?
5228 $p->{form_element} = $anode;
5229 last AN;
5230 }
5231 }
5232 }
5233 $anode = $anode->parent_node;
5234 } # AN
5235
5236 ## Step 3 # MUST
5237 ## Step 10 # MUST
5238 {
5239 my $self = $p;
5240 !!!next-token;
5241 }
5242 $p->_tree_construction_main;
5243
5244 ## Step 11 # MUST
5245 my @cn = @{$node->child_nodes};
5246 for (@cn) {
5247 $node->remove_child ($_);
5248 }
5249 ## ISSUE: mutation events? read-only?
5250
5251 ## Step 12 # MUST
5252 @cn = @{$root->child_nodes};
5253 for (@cn) {
5254 $this_doc->adopt_node ($_);
5255 $node->append_child ($_);
5256 }
5257 ## ISSUE: mutation events?
5258
5259 $p->_terminate_tree_constructor;
5260 } else {
5261 die "$0: |set_inner_html| is not defined for node of type $nt";
5262 }
5263 } # set_inner_html
5264
5265 } # tree construction stage
5266
5267 sub get_inner_html ($$$) {
5268 my (undef, $node, $on_error) = @_;
5269
5270 ## Step 1
5271 my $s = '';
5272
5273 my $in_cdata;
5274 my $parent = $node;
5275 while (defined $parent) {
5276 if ($parent->node_type == 1 and
5277 $parent->namespace_uri eq 'http://www.w3.org/1999/xhtml' and
5278 {
5279 style => 1, script => 1, xmp => 1, iframe => 1,
5280 noembed => 1, noframes => 1, noscript => 1,
5281 }->{$parent->local_name}) { ## TODO: case thingy
5282 $in_cdata = 1;
5283 }
5284 $parent = $parent->parent_node;
5285 }
5286
5287 ## Step 2
5288 my @node = @{$node->child_nodes};
5289 C: while (@node) {
5290 my $child = shift @node;
5291 unless (ref $child) {
5292 if ($child eq 'cdata-out') {
5293 $in_cdata = 0;
5294 } else {
5295 $s .= $child; # end tag
5296 }
5297 next C;
5298 }
5299
5300 my $nt = $child->node_type;
5301 if ($nt == 1) { # Element
5302 my $tag_name = $child->tag_name; ## TODO: manakai_tag_name
5303 $s .= '<' . $tag_name;
5304 ## NOTE: Non-HTML case:
5305 ## <http://permalink.gmane.org/gmane.org.w3c.whatwg.discuss/11191>
5306
5307 my @attrs = @{$child->attributes}; # sort order MUST be stable
5308 for my $attr (@attrs) { # order is implementation dependent
5309 my $attr_name = $attr->name; ## TODO: manakai_name
5310 $s .= ' ' . $attr_name . '="';
5311 my $attr_value = $attr->value;
5312 ## escape
5313 $attr_value =~ s/&/&amp;/g;
5314 $attr_value =~ s/</&lt;/g;
5315 $attr_value =~ s/>/&gt;/g;
5316 $attr_value =~ s/"/&quot;/g;
5317 $s .= $attr_value . '"';
5318 }
5319 $s .= '>';
5320
5321 next C if {
5322 area => 1, base => 1, basefont => 1, bgsound => 1,
5323 br => 1, col => 1, embed => 1, frame => 1, hr => 1,
5324 img => 1, input => 1, link => 1, meta => 1, param => 1,
5325 spacer => 1, wbr => 1,
5326 }->{$tag_name};
5327
5328 $s .= "\x0A" if $tag_name eq 'pre' or $tag_name eq 'textarea';
5329
5330 if (not $in_cdata and {
5331 style => 1, script => 1, xmp => 1, iframe => 1,
5332 noembed => 1, noframes => 1, noscript => 1,
5333 plaintext => 1,
5334 }->{$tag_name}) {
5335 unshift @node, 'cdata-out';
5336 $in_cdata = 1;
5337 }
5338
5339 unshift @node, @{$child->child_nodes}, '</' . $tag_name . '>';
5340 } elsif ($nt == 3 or $nt == 4) {
5341 if ($in_cdata) {
5342 $s .= $child->data;
5343 } else {
5344 my $value = $child->data;
5345 $value =~ s/&/&amp;/g;
5346 $value =~ s/</&lt;/g;
5347 $value =~ s/>/&gt;/g;
5348 $value =~ s/"/&quot;/g;
5349 $s .= $value;
5350 }
5351 } elsif ($nt == 8) {
5352 $s .= '<!--' . $child->data . '-->';
5353 } elsif ($nt == 10) {
5354 $s .= '<!DOCTYPE ' . $child->name . '>';
5355 } elsif ($nt == 5) { # entrefs
5356 push @node, @{$child->child_nodes};
5357 } else {
5358 $on_error->($child) if defined $on_error;
5359 }
5360 ## ISSUE: This code does not support PIs.
5361 } # C
5362
5363 ## Step 3
5364 return \$s;
5365 } # get_inner_html
5366
5367 1;
5368 # $Date: 2007/07/21 10:39:45 $

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24