/[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.39 - (show annotations) (download) (as text)
Sat Jul 21 04:55:20 2007 UTC (19 years, 2 months ago) by wakaba
Branch: MAIN
Changes since 1.38: +3 -3 lines
File MIME type: application/x-wais-source
++ whatpm/Whatpm/ChangeLog	21 Jul 2007 04:51:33 -0000
2007-07-21  Wakaba  <wakaba@suika.fam.cx>

	* HTML.pm.src: Add the name of the attribute
	to the "duplicate attribute" error.

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24