/[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.33 - (show annotations) (download) (as text)
Sat Jul 7 13:41:05 2007 UTC (19 years, 3 months ago) by wakaba
Branch: MAIN
Changes since 1.32: +4 -3 lines
File MIME type: application/x-wais-source
++ whatpm/t/ChangeLog	7 Jul 2007 13:35:29 -0000
2007-07-07  Wakaba  <wakaba@suika.fam.cx>

	* tokenizer-test-1.test: A comment test is added.

++ whatpm/Whatpm/ChangeLog	7 Jul 2007 13:41:02 -0000
2007-07-07  Wakaba  <wakaba@suika.fam.cx>

	* HTML.pm.src: |<!---x-->| was not processed correctly.

1 package Whatpm::HTML;
2 use strict;
3 our $VERSION=do{my @r=(q$Revision: 1.32 $=~/\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 => 'dupulicate attribute');
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 $self->{next_input_character} = 0x0023; # #
1648 !!!back-next-input-character ($x_char);
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;
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 } elsif (not $in_attr) {
1741 $value = $EntityChar->{$entity_name};
1742 $match = -1;
1743 } else {
1744 $value .= chr $self->{next_input_character};
1745 }
1746 } else {
1747 $value .= chr $self->{next_input_character};
1748 }
1749 !!!next-input-character;
1750 }
1751
1752 if ($match > 0) {
1753 return {type => 'character', data => $value};
1754 } elsif ($match < 0) {
1755 !!!parse-error (type => 'no refc');
1756 return {type => 'character', data => $value};
1757 } else {
1758 !!!parse-error (type => 'bare ero');
1759 ## NOTE: No characters are consumed in the spec.
1760 return {type => 'character', data => '&'.$value};
1761 }
1762 } else {
1763 ## no characters are consumed
1764 !!!parse-error (type => 'bare ero');
1765 return undef;
1766 }
1767 } # _tokenize_attempt_to_consume_an_entity
1768
1769 sub _initialize_tree_constructor ($) {
1770 my $self = shift;
1771 ## NOTE: $self->{document} MUST be specified before this method is called
1772 $self->{document}->strict_error_checking (0);
1773 ## TODO: Turn mutation events off # MUST
1774 ## TODO: Turn loose Document option (manakai extension) on
1775 $self->{document}->manakai_is_html (1); # MUST
1776 } # _initialize_tree_constructor
1777
1778 sub _terminate_tree_constructor ($) {
1779 my $self = shift;
1780 $self->{document}->strict_error_checking (1);
1781 ## TODO: Turn mutation events on
1782 } # _terminate_tree_constructor
1783
1784 ## ISSUE: Should append_child (for example) in script executed in tree construction stage fire mutation events?
1785
1786 { # tree construction stage
1787 my $token;
1788
1789 sub _construct_tree ($) {
1790 my ($self) = @_;
1791
1792 ## When an interactive UA render the $self->{document} available
1793 ## to the user, or when it begin accepting user input, are
1794 ## not defined.
1795
1796 ## Append a character: collect it and all subsequent consecutive
1797 ## characters and insert one Text node whose data is concatenation
1798 ## of all those characters. # MUST
1799
1800 !!!next-token;
1801
1802 $self->{insertion_mode} = 'before head';
1803 undef $self->{form_element};
1804 undef $self->{head_element};
1805 $self->{open_elements} = [];
1806 undef $self->{inner_html_node};
1807
1808 $self->_tree_construction_initial; # MUST
1809 $self->_tree_construction_root_element;
1810 $self->_tree_construction_main;
1811 } # _construct_tree
1812
1813 sub _tree_construction_initial ($) {
1814 my $self = shift;
1815 INITIAL: {
1816 if ($token->{type} eq 'DOCTYPE') {
1817 ## NOTE: Conformance checkers MAY, instead of reporting "not HTML5"
1818 ## error, switch to a conformance checking mode for another
1819 ## language.
1820 my $doctype_name = $token->{name};
1821 $doctype_name = '' unless defined $doctype_name;
1822 $doctype_name =~ tr/a-z/A-Z/;
1823 if (not defined $token->{name} or # <!DOCTYPE>
1824 defined $token->{public_identifier} or
1825 defined $token->{system_identifier}) {
1826 !!!parse-error (type => 'not HTML5');
1827 } elsif ($doctype_name ne 'HTML') {
1828 ## ISSUE: ASCII case-insensitive? (in fact it does not matter)
1829 !!!parse-error (type => 'not HTML5');
1830 }
1831
1832 my $doctype = $self->{document}->create_document_type_definition
1833 ($token->{name}); ## ISSUE: If name is missing (e.g. <!DOCTYPE>)?
1834 $doctype->public_id ($token->{public_identifier})
1835 if defined $token->{public_identifier};
1836 $doctype->system_id ($token->{system_identifier})
1837 if defined $token->{system_identifier};
1838 ## NOTE: Other DocumentType attributes are null or empty lists.
1839 ## ISSUE: internalSubset = null??
1840 $self->{document}->append_child ($doctype);
1841
1842 if (not $token->{correct} or $doctype_name ne 'HTML') {
1843 $self->{document}->manakai_compat_mode ('quirks');
1844 } elsif (defined $token->{public_identifier}) {
1845 my $pubid = $token->{public_identifier};
1846 $pubid =~ tr/a-z/A-z/;
1847 if ({
1848 "+//SILMARIL//DTD HTML PRO V0R11 19970101//EN" => 1,
1849 "-//ADVASOFT LTD//DTD HTML 3.0 ASWEDIT + EXTENSIONS//EN" => 1,
1850 "-//AS//DTD HTML 3.0 ASWEDIT + EXTENSIONS//EN" => 1,
1851 "-//IETF//DTD HTML 2.0 LEVEL 1//EN" => 1,
1852 "-//IETF//DTD HTML 2.0 LEVEL 2//EN" => 1,
1853 "-//IETF//DTD HTML 2.0 STRICT LEVEL 1//EN" => 1,
1854 "-//IETF//DTD HTML 2.0 STRICT LEVEL 2//EN" => 1,
1855 "-//IETF//DTD HTML 2.0 STRICT//EN" => 1,
1856 "-//IETF//DTD HTML 2.0//EN" => 1,
1857 "-//IETF//DTD HTML 2.1E//EN" => 1,
1858 "-//IETF//DTD HTML 3.0//EN" => 1,
1859 "-//IETF//DTD HTML 3.0//EN//" => 1,
1860 "-//IETF//DTD HTML 3.2 FINAL//EN" => 1,
1861 "-//IETF//DTD HTML 3.2//EN" => 1,
1862 "-//IETF//DTD HTML 3//EN" => 1,
1863 "-//IETF//DTD HTML LEVEL 0//EN" => 1,
1864 "-//IETF//DTD HTML LEVEL 0//EN//2.0" => 1,
1865 "-//IETF//DTD HTML LEVEL 1//EN" => 1,
1866 "-//IETF//DTD HTML LEVEL 1//EN//2.0" => 1,
1867 "-//IETF//DTD HTML LEVEL 2//EN" => 1,
1868 "-//IETF//DTD HTML LEVEL 2//EN//2.0" => 1,
1869 "-//IETF//DTD HTML LEVEL 3//EN" => 1,
1870 "-//IETF//DTD HTML LEVEL 3//EN//3.0" => 1,
1871 "-//IETF//DTD HTML STRICT LEVEL 0//EN" => 1,
1872 "-//IETF//DTD HTML STRICT LEVEL 0//EN//2.0" => 1,
1873 "-//IETF//DTD HTML STRICT LEVEL 1//EN" => 1,
1874 "-//IETF//DTD HTML STRICT LEVEL 1//EN//2.0" => 1,
1875 "-//IETF//DTD HTML STRICT LEVEL 2//EN" => 1,
1876 "-//IETF//DTD HTML STRICT LEVEL 2//EN//2.0" => 1,
1877 "-//IETF//DTD HTML STRICT LEVEL 3//EN" => 1,
1878 "-//IETF//DTD HTML STRICT LEVEL 3//EN//3.0" => 1,
1879 "-//IETF//DTD HTML STRICT//EN" => 1,
1880 "-//IETF//DTD HTML STRICT//EN//2.0" => 1,
1881 "-//IETF//DTD HTML STRICT//EN//3.0" => 1,
1882 "-//IETF//DTD HTML//EN" => 1,
1883 "-//IETF//DTD HTML//EN//2.0" => 1,
1884 "-//IETF//DTD HTML//EN//3.0" => 1,
1885 "-//METRIUS//DTD METRIUS PRESENTATIONAL//EN" => 1,
1886 "-//MICROSOFT//DTD INTERNET EXPLORER 2.0 HTML STRICT//EN" => 1,
1887 "-//MICROSOFT//DTD INTERNET EXPLORER 2.0 HTML//EN" => 1,
1888 "-//MICROSOFT//DTD INTERNET EXPLORER 2.0 TABLES//EN" => 1,
1889 "-//MICROSOFT//DTD INTERNET EXPLORER 3.0 HTML STRICT//EN" => 1,
1890 "-//MICROSOFT//DTD INTERNET EXPLORER 3.0 HTML//EN" => 1,
1891 "-//MICROSOFT//DTD INTERNET EXPLORER 3.0 TABLES//EN" => 1,
1892 "-//NETSCAPE COMM. CORP.//DTD HTML//EN" => 1,
1893 "-//NETSCAPE COMM. CORP.//DTD STRICT HTML//EN" => 1,
1894 "-//O'REILLY AND ASSOCIATES//DTD HTML 2.0//EN" => 1,
1895 "-//O'REILLY AND ASSOCIATES//DTD HTML EXTENDED 1.0//EN" => 1,
1896 "-//SPYGLASS//DTD HTML 2.0 EXTENDED//EN" => 1,
1897 "-//SQ//DTD HTML 2.0 HOTMETAL + EXTENSIONS//EN" => 1,
1898 "-//SUN MICROSYSTEMS CORP.//DTD HOTJAVA HTML//EN" => 1,
1899 "-//SUN MICROSYSTEMS CORP.//DTD HOTJAVA STRICT HTML//EN" => 1,
1900 "-//W3C//DTD HTML 3 1995-03-24//EN" => 1,
1901 "-//W3C//DTD HTML 3.2 DRAFT//EN" => 1,
1902 "-//W3C//DTD HTML 3.2 FINAL//EN" => 1,
1903 "-//W3C//DTD HTML 3.2//EN" => 1,
1904 "-//W3C//DTD HTML 3.2S DRAFT//EN" => 1,
1905 "-//W3C//DTD HTML 4.0 FRAMESET//EN" => 1,
1906 "-//W3C//DTD HTML 4.0 TRANSITIONAL//EN" => 1,
1907 "-//W3C//DTD HTML EXPERIMETNAL 19960712//EN" => 1,
1908 "-//W3C//DTD HTML EXPERIMENTAL 970421//EN" => 1,
1909 "-//W3C//DTD W3 HTML//EN" => 1,
1910 "-//W3O//DTD W3 HTML 3.0//EN" => 1,
1911 "-//W3O//DTD W3 HTML 3.0//EN//" => 1,
1912 "-//W3O//DTD W3 HTML STRICT 3.0//EN//" => 1,
1913 "-//WEBTECHS//DTD MOZILLA HTML 2.0//EN" => 1,
1914 "-//WEBTECHS//DTD MOZILLA HTML//EN" => 1,
1915 "-/W3C/DTD HTML 4.0 TRANSITIONAL/EN" => 1,
1916 "HTML" => 1,
1917 }->{$pubid}) {
1918 $self->{document}->manakai_compat_mode ('quirks');
1919 } elsif ($pubid eq "-//W3C//DTD HTML 4.01 FRAMESET//EN" or
1920 $pubid eq "-//W3C//DTD HTML 4.01 TRANSITIONAL//EN") {
1921 if (defined $token->{system_identifier}) {
1922 $self->{document}->manakai_compat_mode ('quirks');
1923 } else {
1924 $self->{document}->manakai_compat_mode ('limited quirks');
1925 }
1926 } elsif ($pubid eq "-//W3C//DTD XHTML 1.0 Frameset//EN" or
1927 $pubid eq "-//W3C//DTD XHTML 1.0 Transitional//EN") {
1928 $self->{document}->manakai_compat_mode ('limited quirks');
1929 }
1930 }
1931 if (defined $token->{system_identifier}) {
1932 my $sysid = $token->{system_identifier};
1933 $sysid =~ tr/A-Z/a-z/;
1934 if ($sysid eq "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd") {
1935 $self->{document}->manakai_compat_mode ('quirks');
1936 }
1937 }
1938
1939 ## Go to the root element phase.
1940 !!!next-token;
1941 return;
1942 } elsif ({
1943 'start tag' => 1,
1944 'end tag' => 1,
1945 'end-of-file' => 1,
1946 }->{$token->{type}}) {
1947 !!!parse-error (type => 'no DOCTYPE');
1948 $self->{document}->manakai_compat_mode ('quirks');
1949 ## Go to the root element phase
1950 ## reprocess
1951 return;
1952 } elsif ($token->{type} eq 'character') {
1953 if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) { # \x0D
1954 ## Ignore the token
1955
1956 unless (length $token->{data}) {
1957 ## Stay in the phase
1958 !!!next-token;
1959 redo INITIAL;
1960 }
1961 }
1962
1963 !!!parse-error (type => 'no DOCTYPE');
1964 $self->{document}->manakai_compat_mode ('quirks');
1965 ## Go to the root element phase
1966 ## reprocess
1967 return;
1968 } elsif ($token->{type} eq 'comment') {
1969 my $comment = $self->{document}->create_comment ($token->{data});
1970 $self->{document}->append_child ($comment);
1971
1972 ## Stay in the phase.
1973 !!!next-token;
1974 redo INITIAL;
1975 } else {
1976 die "$0: $token->{type}: Unknown token";
1977 }
1978 } # INITIAL
1979 } # _tree_construction_initial
1980
1981 sub _tree_construction_root_element ($) {
1982 my $self = shift;
1983
1984 B: {
1985 if ($token->{type} eq 'DOCTYPE') {
1986 !!!parse-error (type => 'in html:#DOCTYPE');
1987 ## Ignore the token
1988 ## Stay in the phase
1989 !!!next-token;
1990 redo B;
1991 } elsif ($token->{type} eq 'comment') {
1992 my $comment = $self->{document}->create_comment ($token->{data});
1993 $self->{document}->append_child ($comment);
1994 ## Stay in the phase
1995 !!!next-token;
1996 redo B;
1997 } elsif ($token->{type} eq 'character') {
1998 if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) { # \x0D
1999 ## Ignore the token.
2000
2001 unless (length $token->{data}) {
2002 ## Stay in the phase
2003 !!!next-token;
2004 redo B;
2005 }
2006 }
2007 #
2008 } elsif ({
2009 'start tag' => 1,
2010 'end tag' => 1,
2011 'end-of-file' => 1,
2012 }->{$token->{type}}) {
2013 ## ISSUE: There is an issue in the spec
2014 #
2015 } else {
2016 die "$0: $token->{type}: Unknown token";
2017 }
2018 my $root_element; !!!create-element ($root_element, 'html');
2019 $self->{document}->append_child ($root_element);
2020 push @{$self->{open_elements}}, [$root_element, 'html'];
2021 #$phase = 'main';
2022 ## reprocess
2023 #redo B;
2024 return;
2025 } # B
2026 } # _tree_construction_root_element
2027
2028 sub _reset_insertion_mode ($) {
2029 my $self = shift;
2030
2031 ## Step 1
2032 my $last;
2033
2034 ## Step 2
2035 my $i = -1;
2036 my $node = $self->{open_elements}->[$i];
2037
2038 ## Step 3
2039 S3: {
2040 ## ISSUE: Oops! "If node is the first node in the stack of open
2041 ## elements, then set last to true. If the context element of the
2042 ## HTML fragment parsing algorithm is neither a td element nor a
2043 ## th element, then set node to the context element. (fragment case)":
2044 ## The second "if" is in the scope of the first "if"!?
2045 if ($self->{open_elements}->[0]->[0] eq $node->[0]) {
2046 $last = 1;
2047 if (defined $self->{inner_html_node}) {
2048 if ($self->{inner_html_node}->[1] eq 'td' or
2049 $self->{inner_html_node}->[1] eq 'th') {
2050 #
2051 } else {
2052 $node = $self->{inner_html_node};
2053 }
2054 }
2055 }
2056
2057 ## Step 4..13
2058 my $new_mode = {
2059 select => 'in select',
2060 td => 'in cell',
2061 th => 'in cell',
2062 tr => 'in row',
2063 tbody => 'in table body',
2064 thead => 'in table head',
2065 tfoot => 'in table foot',
2066 caption => 'in caption',
2067 colgroup => 'in column group',
2068 table => 'in table',
2069 head => 'in body', # not in head!
2070 body => 'in body',
2071 frameset => 'in frameset',
2072 }->{$node->[1]};
2073 $self->{insertion_mode} = $new_mode and return if defined $new_mode;
2074
2075 ## Step 14
2076 if ($node->[1] eq 'html') {
2077 unless (defined $self->{head_element}) {
2078 $self->{insertion_mode} = 'before head';
2079 } else {
2080 $self->{insertion_mode} = 'after head';
2081 }
2082 return;
2083 }
2084
2085 ## Step 15
2086 $self->{insertion_mode} = 'in body' and return if $last;
2087
2088 ## Step 16
2089 $i--;
2090 $node = $self->{open_elements}->[$i];
2091
2092 ## Step 17
2093 redo S3;
2094 } # S3
2095 } # _reset_insertion_mode
2096
2097 sub _tree_construction_main ($) {
2098 my $self = shift;
2099
2100 my $phase = 'main';
2101
2102 my $active_formatting_elements = [];
2103
2104 my $reconstruct_active_formatting_elements = sub { # MUST
2105 my $insert = shift;
2106
2107 ## Step 1
2108 return unless @$active_formatting_elements;
2109
2110 ## Step 3
2111 my $i = -1;
2112 my $entry = $active_formatting_elements->[$i];
2113
2114 ## Step 2
2115 return if $entry->[0] eq '#marker';
2116 for (@{$self->{open_elements}}) {
2117 if ($entry->[0] eq $_->[0]) {
2118 return;
2119 }
2120 }
2121
2122 S4: {
2123 ## Step 4
2124 last S4 if $active_formatting_elements->[0]->[0] eq $entry->[0];
2125
2126 ## Step 5
2127 $i--;
2128 $entry = $active_formatting_elements->[$i];
2129
2130 ## Step 6
2131 if ($entry->[0] eq '#marker') {
2132 #
2133 } else {
2134 my $in_open_elements;
2135 OE: for (@{$self->{open_elements}}) {
2136 if ($entry->[0] eq $_->[0]) {
2137 $in_open_elements = 1;
2138 last OE;
2139 }
2140 }
2141 if ($in_open_elements) {
2142 #
2143 } else {
2144 redo S4;
2145 }
2146 }
2147
2148 ## Step 7
2149 $i++;
2150 $entry = $active_formatting_elements->[$i];
2151 } # S4
2152
2153 S7: {
2154 ## Step 8
2155 my $clone = [$entry->[0]->clone_node (0), $entry->[1]];
2156
2157 ## Step 9
2158 $insert->($clone->[0]);
2159 push @{$self->{open_elements}}, $clone;
2160
2161 ## Step 10
2162 $active_formatting_elements->[$i] = $self->{open_elements}->[-1];
2163
2164 ## Step 11
2165 unless ($clone->[0] eq $active_formatting_elements->[-1]->[0]) {
2166 ## Step 7'
2167 $i++;
2168 $entry = $active_formatting_elements->[$i];
2169
2170 redo S7;
2171 }
2172 } # S7
2173 }; # $reconstruct_active_formatting_elements
2174
2175 my $clear_up_to_marker = sub {
2176 for (reverse 0..$#$active_formatting_elements) {
2177 if ($active_formatting_elements->[$_]->[0] eq '#marker') {
2178 splice @$active_formatting_elements, $_;
2179 return;
2180 }
2181 }
2182 }; # $clear_up_to_marker
2183
2184 my $parse_rcdata = sub ($$) {
2185 my ($content_model_flag, $insert) = @_;
2186
2187 ## Step 1
2188 my $start_tag_name = $token->{tag_name};
2189 my $el;
2190 !!!create-element ($el, $start_tag_name, $token->{attributes});
2191
2192 ## Step 2
2193 $insert->($el); # /context node/->append_child ($el)
2194
2195 ## Step 3
2196 $self->{content_model_flag} = $content_model_flag; # CDATA or RCDATA
2197 delete $self->{escape}; # MUST
2198
2199 ## Step 4
2200 my $text = '';
2201 !!!next-token;
2202 while ($token->{type} eq 'character') { # or until stop tokenizing
2203 $text .= $token->{data};
2204 !!!next-token;
2205 }
2206
2207 ## Step 5
2208 if (length $text) {
2209 my $text = $self->{document}->create_text_node ($text);
2210 $el->append_child ($text);
2211 }
2212
2213 ## Step 6
2214 $self->{content_model_flag} = 'PCDATA';
2215
2216 ## Step 7
2217 if ($token->{type} eq 'end tag' and $token->{tag_name} eq $start_tag_name) {
2218 ## Ignore the token
2219 } else {
2220 !!!parse-error (type => 'in '.$content_model_flag.':#'.$token->{type});
2221 }
2222 !!!next-token;
2223 }; # $parse_rcdata
2224
2225 my $script_start_tag = sub ($) {
2226 my $insert = $_[0];
2227 my $script_el;
2228 !!!create-element ($script_el, 'script', $token->{attributes});
2229 ## TODO: mark as "parser-inserted"
2230
2231 $self->{content_model_flag} = 'CDATA';
2232 delete $self->{escape}; # MUST
2233
2234 my $text = '';
2235 !!!next-token;
2236 while ($token->{type} eq 'character') {
2237 $text .= $token->{data};
2238 !!!next-token;
2239 } # stop if non-character token or tokenizer stops tokenising
2240 if (length $text) {
2241 $script_el->manakai_append_text ($text);
2242 }
2243
2244 $self->{content_model_flag} = 'PCDATA';
2245
2246 if ($token->{type} eq 'end tag' and
2247 $token->{tag_name} eq 'script') {
2248 ## Ignore the token
2249 } else {
2250 !!!parse-error (type => 'in CDATA:#'.$token->{type});
2251 ## ISSUE: And ignore?
2252 ## TODO: mark as "already executed"
2253 }
2254
2255 if (defined $self->{inner_html_node}) {
2256 ## TODO: mark as "already executed"
2257 } else {
2258 ## TODO: $old_insertion_point = current insertion point
2259 ## TODO: insertion point = just before the next input character
2260
2261 $insert->($script_el);
2262
2263 ## TODO: insertion point = $old_insertion_point (might be "undefined")
2264
2265 ## TODO: if there is a script that will execute as soon as the parser resume, then...
2266 }
2267
2268 !!!next-token;
2269 }; # $script_start_tag
2270
2271 my $formatting_end_tag = sub {
2272 my $tag_name = shift;
2273
2274 FET: {
2275 ## Step 1
2276 my $formatting_element;
2277 my $formatting_element_i_in_active;
2278 AFE: for (reverse 0..$#$active_formatting_elements) {
2279 if ($active_formatting_elements->[$_]->[1] eq $tag_name) {
2280 $formatting_element = $active_formatting_elements->[$_];
2281 $formatting_element_i_in_active = $_;
2282 last AFE;
2283 } elsif ($active_formatting_elements->[$_]->[0] eq '#marker') {
2284 last AFE;
2285 }
2286 } # AFE
2287 unless (defined $formatting_element) {
2288 !!!parse-error (type => 'unmatched end tag:'.$tag_name);
2289 ## Ignore the token
2290 !!!next-token;
2291 return;
2292 }
2293 ## has an element in scope
2294 my $in_scope = 1;
2295 my $formatting_element_i_in_open;
2296 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
2297 my $node = $self->{open_elements}->[$_];
2298 if ($node->[0] eq $formatting_element->[0]) {
2299 if ($in_scope) {
2300 $formatting_element_i_in_open = $_;
2301 last INSCOPE;
2302 } else { # in open elements but not in scope
2303 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
2304 ## Ignore the token
2305 !!!next-token;
2306 return;
2307 }
2308 } elsif ({
2309 table => 1, caption => 1, td => 1, th => 1,
2310 button => 1, marquee => 1, object => 1, html => 1,
2311 }->{$node->[1]}) {
2312 $in_scope = 0;
2313 }
2314 } # INSCOPE
2315 unless (defined $formatting_element_i_in_open) {
2316 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
2317 pop @$active_formatting_elements; # $formatting_element
2318 !!!next-token; ## TODO: ok?
2319 return;
2320 }
2321 if (not $self->{open_elements}->[-1]->[0] eq $formatting_element->[0]) {
2322 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
2323 }
2324
2325 ## Step 2
2326 my $furthest_block;
2327 my $furthest_block_i_in_open;
2328 OE: for (reverse 0..$#{$self->{open_elements}}) {
2329 my $node = $self->{open_elements}->[$_];
2330 if (not $formatting_category->{$node->[1]} and
2331 #not $phrasing_category->{$node->[1]} and
2332 ($special_category->{$node->[1]} or
2333 $scoping_category->{$node->[1]})) {
2334 $furthest_block = $node;
2335 $furthest_block_i_in_open = $_;
2336 } elsif ($node->[0] eq $formatting_element->[0]) {
2337 last OE;
2338 }
2339 } # OE
2340
2341 ## Step 3
2342 unless (defined $furthest_block) { # MUST
2343 splice @{$self->{open_elements}}, $formatting_element_i_in_open;
2344 splice @$active_formatting_elements, $formatting_element_i_in_active, 1;
2345 !!!next-token;
2346 return;
2347 }
2348
2349 ## Step 4
2350 my $common_ancestor_node = $self->{open_elements}->[$formatting_element_i_in_open - 1];
2351
2352 ## Step 5
2353 my $furthest_block_parent = $furthest_block->[0]->parent_node;
2354 if (defined $furthest_block_parent) {
2355 $furthest_block_parent->remove_child ($furthest_block->[0]);
2356 }
2357
2358 ## Step 6
2359 my $bookmark_prev_el
2360 = $active_formatting_elements->[$formatting_element_i_in_active - 1]
2361 ->[0];
2362
2363 ## Step 7
2364 my $node = $furthest_block;
2365 my $node_i_in_open = $furthest_block_i_in_open;
2366 my $last_node = $furthest_block;
2367 S7: {
2368 ## Step 1
2369 $node_i_in_open--;
2370 $node = $self->{open_elements}->[$node_i_in_open];
2371
2372 ## Step 2
2373 my $node_i_in_active;
2374 S7S2: {
2375 for (reverse 0..$#$active_formatting_elements) {
2376 if ($active_formatting_elements->[$_]->[0] eq $node->[0]) {
2377 $node_i_in_active = $_;
2378 last S7S2;
2379 }
2380 }
2381 splice @{$self->{open_elements}}, $node_i_in_open, 1;
2382 redo S7;
2383 } # S7S2
2384
2385 ## Step 3
2386 last S7 if $node->[0] eq $formatting_element->[0];
2387
2388 ## Step 4
2389 if ($last_node->[0] eq $furthest_block->[0]) {
2390 $bookmark_prev_el = $node->[0];
2391 }
2392
2393 ## Step 5
2394 if ($node->[0]->has_child_nodes ()) {
2395 my $clone = [$node->[0]->clone_node (0), $node->[1]];
2396 $active_formatting_elements->[$node_i_in_active] = $clone;
2397 $self->{open_elements}->[$node_i_in_open] = $clone;
2398 $node = $clone;
2399 }
2400
2401 ## Step 6
2402 $node->[0]->append_child ($last_node->[0]);
2403
2404 ## Step 7
2405 $last_node = $node;
2406
2407 ## Step 8
2408 redo S7;
2409 } # S7
2410
2411 ## Step 8
2412 $common_ancestor_node->[0]->append_child ($last_node->[0]);
2413
2414 ## Step 9
2415 my $clone = [$formatting_element->[0]->clone_node (0),
2416 $formatting_element->[1]];
2417
2418 ## Step 10
2419 my @cn = @{$furthest_block->[0]->child_nodes};
2420 $clone->[0]->append_child ($_) for @cn;
2421
2422 ## Step 11
2423 $furthest_block->[0]->append_child ($clone->[0]);
2424
2425 ## Step 12
2426 my $i;
2427 AFE: for (reverse 0..$#$active_formatting_elements) {
2428 if ($active_formatting_elements->[$_]->[0] eq $formatting_element->[0]) {
2429 splice @$active_formatting_elements, $_, 1;
2430 $i-- and last AFE if defined $i;
2431 } elsif ($active_formatting_elements->[$_]->[0] eq $bookmark_prev_el) {
2432 $i = $_;
2433 }
2434 } # AFE
2435 splice @$active_formatting_elements, $i + 1, 0, $clone;
2436
2437 ## Step 13
2438 undef $i;
2439 OE: for (reverse 0..$#{$self->{open_elements}}) {
2440 if ($self->{open_elements}->[$_]->[0] eq $formatting_element->[0]) {
2441 splice @{$self->{open_elements}}, $_, 1;
2442 $i-- and last OE if defined $i;
2443 } elsif ($self->{open_elements}->[$_]->[0] eq $furthest_block->[0]) {
2444 $i = $_;
2445 }
2446 } # OE
2447 splice @{$self->{open_elements}}, $i + 1, 1, $clone;
2448
2449 ## Step 14
2450 redo FET;
2451 } # FET
2452 }; # $formatting_end_tag
2453
2454 my $insert_to_current = sub {
2455 $self->{open_elements}->[-1]->[0]->append_child ($_[0]);
2456 }; # $insert_to_current
2457
2458 my $insert_to_foster = sub {
2459 my $child = shift;
2460 if ({
2461 table => 1, tbody => 1, tfoot => 1,
2462 thead => 1, tr => 1,
2463 }->{$self->{open_elements}->[-1]->[1]}) {
2464 # MUST
2465 my $foster_parent_element;
2466 my $next_sibling;
2467 OE: for (reverse 0..$#{$self->{open_elements}}) {
2468 if ($self->{open_elements}->[$_]->[1] eq 'table') {
2469 my $parent = $self->{open_elements}->[$_]->[0]->parent_node;
2470 if (defined $parent and $parent->node_type == 1) {
2471 $foster_parent_element = $parent;
2472 $next_sibling = $self->{open_elements}->[$_]->[0];
2473 } else {
2474 $foster_parent_element
2475 = $self->{open_elements}->[$_ - 1]->[0];
2476 }
2477 last OE;
2478 }
2479 } # OE
2480 $foster_parent_element = $self->{open_elements}->[0]->[0]
2481 unless defined $foster_parent_element;
2482 $foster_parent_element->insert_before
2483 ($child, $next_sibling);
2484 } else {
2485 $self->{open_elements}->[-1]->[0]->append_child ($child);
2486 }
2487 }; # $insert_to_foster
2488
2489 my $in_body = sub {
2490 my $insert = shift;
2491 if ($token->{type} eq 'start tag') {
2492 if ($token->{tag_name} eq 'script') {
2493 ## NOTE: This is an "as if in head" code clone
2494 $script_start_tag->($insert);
2495 return;
2496 } elsif ($token->{tag_name} eq 'style') {
2497 ## NOTE: This is an "as if in head" code clone
2498 $parse_rcdata->('CDATA', $insert);
2499 return;
2500 } elsif ({
2501 base => 1, link => 1, meta => 1,
2502 }->{$token->{tag_name}}) {
2503 ## NOTE: This is an "as if in head" code clone, only "-t" differs
2504 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2505 pop @{$self->{open_elements}}; ## ISSUE: This step is missing in the spec.
2506 !!!next-token;
2507 ## TODO: Extracting |charset| from |meta|.
2508 return;
2509 } elsif ($token->{tag_name} eq 'title') {
2510 !!!parse-error (type => 'in body:title');
2511 ## NOTE: This is an "as if in head" code clone
2512 $parse_rcdata->('RCDATA', sub {
2513 if (defined $self->{head_element}) {
2514 $self->{head_element}->append_child ($_[0]);
2515 } else {
2516 $insert->($_[0]);
2517 }
2518 });
2519 return;
2520 } elsif ($token->{tag_name} eq 'body') {
2521 !!!parse-error (type => 'in body:body');
2522
2523 if (@{$self->{open_elements}} == 1 or
2524 $self->{open_elements}->[1]->[1] ne 'body') {
2525 ## Ignore the token
2526 } else {
2527 my $body_el = $self->{open_elements}->[1]->[0];
2528 for my $attr_name (keys %{$token->{attributes}}) {
2529 unless ($body_el->has_attribute_ns (undef, $attr_name)) {
2530 $body_el->set_attribute_ns
2531 (undef, [undef, $attr_name],
2532 $token->{attributes}->{$attr_name}->{value});
2533 }
2534 }
2535 }
2536 !!!next-token;
2537 return;
2538 } elsif ({
2539 address => 1, blockquote => 1, center => 1, dir => 1,
2540 div => 1, dl => 1, fieldset => 1, listing => 1,
2541 menu => 1, ol => 1, p => 1, ul => 1,
2542 pre => 1,
2543 }->{$token->{tag_name}}) {
2544 ## has a p element in scope
2545 INSCOPE: for (reverse @{$self->{open_elements}}) {
2546 if ($_->[1] eq 'p') {
2547 !!!back-token;
2548 $token = {type => 'end tag', tag_name => 'p'};
2549 return;
2550 } elsif ({
2551 table => 1, caption => 1, td => 1, th => 1,
2552 button => 1, marquee => 1, object => 1, html => 1,
2553 }->{$_->[1]}) {
2554 last INSCOPE;
2555 }
2556 } # INSCOPE
2557
2558 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2559 if ($token->{tag_name} eq 'pre') {
2560 !!!next-token;
2561 if ($token->{type} eq 'character') {
2562 $token->{data} =~ s/^\x0A//;
2563 unless (length $token->{data}) {
2564 !!!next-token;
2565 }
2566 }
2567 } else {
2568 !!!next-token;
2569 }
2570 return;
2571 } elsif ($token->{tag_name} eq 'form') {
2572 if (defined $self->{form_element}) {
2573 !!!parse-error (type => 'in form:form');
2574 ## Ignore the token
2575 !!!next-token;
2576 return;
2577 } else {
2578 ## has a p element in scope
2579 INSCOPE: for (reverse @{$self->{open_elements}}) {
2580 if ($_->[1] eq 'p') {
2581 !!!back-token;
2582 $token = {type => 'end tag', tag_name => 'p'};
2583 return;
2584 } elsif ({
2585 table => 1, caption => 1, td => 1, th => 1,
2586 button => 1, marquee => 1, object => 1, html => 1,
2587 }->{$_->[1]}) {
2588 last INSCOPE;
2589 }
2590 } # INSCOPE
2591
2592 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2593 $self->{form_element} = $self->{open_elements}->[-1]->[0];
2594 !!!next-token;
2595 return;
2596 }
2597 } elsif ($token->{tag_name} eq 'li') {
2598 ## has a p element in scope
2599 INSCOPE: for (reverse @{$self->{open_elements}}) {
2600 if ($_->[1] eq 'p') {
2601 !!!back-token;
2602 $token = {type => 'end tag', tag_name => 'p'};
2603 return;
2604 } elsif ({
2605 table => 1, caption => 1, td => 1, th => 1,
2606 button => 1, marquee => 1, object => 1, html => 1,
2607 }->{$_->[1]}) {
2608 last INSCOPE;
2609 }
2610 } # INSCOPE
2611
2612 ## Step 1
2613 my $i = -1;
2614 my $node = $self->{open_elements}->[$i];
2615 LI: {
2616 ## Step 2
2617 if ($node->[1] eq 'li') {
2618 if ($i != -1) {
2619 !!!parse-error (type => 'end tag missing:'.
2620 $self->{open_elements}->[-1]->[1]);
2621 }
2622 splice @{$self->{open_elements}}, $i;
2623 last LI;
2624 }
2625
2626 ## Step 3
2627 if (not $formatting_category->{$node->[1]} and
2628 #not $phrasing_category->{$node->[1]} and
2629 ($special_category->{$node->[1]} or
2630 $scoping_category->{$node->[1]}) and
2631 $node->[1] ne 'address' and $node->[1] ne 'div') {
2632 last LI;
2633 }
2634
2635 ## Step 4
2636 $i--;
2637 $node = $self->{open_elements}->[$i];
2638 redo LI;
2639 } # LI
2640
2641 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2642 !!!next-token;
2643 return;
2644 } elsif ($token->{tag_name} eq 'dd' or $token->{tag_name} eq 'dt') {
2645 ## has a p element in scope
2646 INSCOPE: for (reverse @{$self->{open_elements}}) {
2647 if ($_->[1] eq 'p') {
2648 !!!back-token;
2649 $token = {type => 'end tag', tag_name => 'p'};
2650 return;
2651 } elsif ({
2652 table => 1, caption => 1, td => 1, th => 1,
2653 button => 1, marquee => 1, object => 1, html => 1,
2654 }->{$_->[1]}) {
2655 last INSCOPE;
2656 }
2657 } # INSCOPE
2658
2659 ## Step 1
2660 my $i = -1;
2661 my $node = $self->{open_elements}->[$i];
2662 LI: {
2663 ## Step 2
2664 if ($node->[1] eq 'dt' or $node->[1] eq 'dd') {
2665 if ($i != -1) {
2666 !!!parse-error (type => 'end tag missing:'.
2667 $self->{open_elements}->[-1]->[1]);
2668 }
2669 splice @{$self->{open_elements}}, $i;
2670 last LI;
2671 }
2672
2673 ## Step 3
2674 if (not $formatting_category->{$node->[1]} and
2675 #not $phrasing_category->{$node->[1]} and
2676 ($special_category->{$node->[1]} or
2677 $scoping_category->{$node->[1]}) and
2678 $node->[1] ne 'address' and $node->[1] ne 'div') {
2679 last LI;
2680 }
2681
2682 ## Step 4
2683 $i--;
2684 $node = $self->{open_elements}->[$i];
2685 redo LI;
2686 } # LI
2687
2688 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2689 !!!next-token;
2690 return;
2691 } elsif ($token->{tag_name} eq 'plaintext') {
2692 ## has a p element in scope
2693 INSCOPE: for (reverse @{$self->{open_elements}}) {
2694 if ($_->[1] eq 'p') {
2695 !!!back-token;
2696 $token = {type => 'end tag', tag_name => 'p'};
2697 return;
2698 } elsif ({
2699 table => 1, caption => 1, td => 1, th => 1,
2700 button => 1, marquee => 1, object => 1, html => 1,
2701 }->{$_->[1]}) {
2702 last INSCOPE;
2703 }
2704 } # INSCOPE
2705
2706 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2707
2708 $self->{content_model_flag} = 'PLAINTEXT';
2709
2710 !!!next-token;
2711 return;
2712 } elsif ({
2713 h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,
2714 }->{$token->{tag_name}}) {
2715 ## has a p element in scope
2716 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
2717 my $node = $self->{open_elements}->[$_];
2718 if ($node->[1] eq 'p') {
2719 !!!back-token;
2720 $token = {type => 'end tag', tag_name => 'p'};
2721 return;
2722 } elsif ({
2723 table => 1, caption => 1, td => 1, th => 1,
2724 button => 1, marquee => 1, object => 1, html => 1,
2725 }->{$node->[1]}) {
2726 last INSCOPE;
2727 }
2728 } # INSCOPE
2729
2730 ## NOTE: See <http://html5.org/tools/web-apps-tracker?from=925&to=926>
2731 ## has an element in scope
2732 #my $i;
2733 #INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
2734 # my $node = $self->{open_elements}->[$_];
2735 # if ({
2736 # h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,
2737 # }->{$node->[1]}) {
2738 # $i = $_;
2739 # last INSCOPE;
2740 # } elsif ({
2741 # table => 1, caption => 1, td => 1, th => 1,
2742 # button => 1, marquee => 1, object => 1, html => 1,
2743 # }->{$node->[1]}) {
2744 # last INSCOPE;
2745 # }
2746 #} # INSCOPE
2747 #
2748 #if (defined $i) {
2749 # !!! parse-error (type => 'in hn:hn');
2750 # splice @{$self->{open_elements}}, $i;
2751 #}
2752
2753 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2754
2755 !!!next-token;
2756 return;
2757 } elsif ($token->{tag_name} eq 'a') {
2758 AFE: for my $i (reverse 0..$#$active_formatting_elements) {
2759 my $node = $active_formatting_elements->[$i];
2760 if ($node->[1] eq 'a') {
2761 !!!parse-error (type => 'in a:a');
2762
2763 !!!back-token;
2764 $token = {type => 'end tag', tag_name => 'a'};
2765 $formatting_end_tag->($token->{tag_name});
2766
2767 AFE2: for (reverse 0..$#$active_formatting_elements) {
2768 if ($active_formatting_elements->[$_]->[0] eq $node->[0]) {
2769 splice @$active_formatting_elements, $_, 1;
2770 last AFE2;
2771 }
2772 } # AFE2
2773 OE: for (reverse 0..$#{$self->{open_elements}}) {
2774 if ($self->{open_elements}->[$_]->[0] eq $node->[0]) {
2775 splice @{$self->{open_elements}}, $_, 1;
2776 last OE;
2777 }
2778 } # OE
2779 last AFE;
2780 } elsif ($node->[0] eq '#marker') {
2781 last AFE;
2782 }
2783 } # AFE
2784
2785 $reconstruct_active_formatting_elements->($insert_to_current);
2786
2787 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2788 push @$active_formatting_elements, $self->{open_elements}->[-1];
2789
2790 !!!next-token;
2791 return;
2792 } elsif ({
2793 b => 1, big => 1, em => 1, font => 1, i => 1,
2794 s => 1, small => 1, strile => 1,
2795 strong => 1, tt => 1, u => 1,
2796 }->{$token->{tag_name}}) {
2797 $reconstruct_active_formatting_elements->($insert_to_current);
2798
2799 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2800 push @$active_formatting_elements, $self->{open_elements}->[-1];
2801
2802 !!!next-token;
2803 return;
2804 } elsif ($token->{tag_name} eq 'nobr') {
2805 $reconstruct_active_formatting_elements->($insert_to_current);
2806
2807 ## has a |nobr| element in scope
2808 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
2809 my $node = $self->{open_elements}->[$_];
2810 if ($node->[1] eq 'nobr') {
2811 !!!parse-error (type => 'not closed:nobr');
2812 !!!back-token;
2813 $token = {type => 'end tag', tag_name => 'nobr'};
2814 return;
2815 } elsif ({
2816 table => 1, caption => 1, td => 1, th => 1,
2817 button => 1, marquee => 1, object => 1, html => 1,
2818 }->{$node->[1]}) {
2819 last INSCOPE;
2820 }
2821 } # INSCOPE
2822
2823 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2824 push @$active_formatting_elements, $self->{open_elements}->[-1];
2825
2826 !!!next-token;
2827 return;
2828 } elsif ($token->{tag_name} eq 'button') {
2829 ## has a button element in scope
2830 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
2831 my $node = $self->{open_elements}->[$_];
2832 if ($node->[1] eq 'button') {
2833 !!!parse-error (type => 'in button:button');
2834 !!!back-token;
2835 $token = {type => 'end tag', tag_name => 'button'};
2836 return;
2837 } elsif ({
2838 table => 1, caption => 1, td => 1, th => 1,
2839 button => 1, marquee => 1, object => 1, html => 1,
2840 }->{$node->[1]}) {
2841 last INSCOPE;
2842 }
2843 } # INSCOPE
2844
2845 $reconstruct_active_formatting_elements->($insert_to_current);
2846
2847 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2848 push @$active_formatting_elements, ['#marker', ''];
2849
2850 !!!next-token;
2851 return;
2852 } elsif ($token->{tag_name} eq 'marquee' or
2853 $token->{tag_name} eq 'object') {
2854 $reconstruct_active_formatting_elements->($insert_to_current);
2855
2856 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2857 push @$active_formatting_elements, ['#marker', ''];
2858
2859 !!!next-token;
2860 return;
2861 } elsif ($token->{tag_name} eq 'xmp') {
2862 $reconstruct_active_formatting_elements->($insert_to_current);
2863 $parse_rcdata->('CDATA', $insert);
2864 return;
2865 } elsif ($token->{tag_name} eq 'table') {
2866 ## has a p element in scope
2867 INSCOPE: for (reverse @{$self->{open_elements}}) {
2868 if ($_->[1] eq 'p') {
2869 !!!back-token;
2870 $token = {type => 'end tag', tag_name => 'p'};
2871 return;
2872 } elsif ({
2873 table => 1, caption => 1, td => 1, th => 1,
2874 button => 1, marquee => 1, object => 1, html => 1,
2875 }->{$_->[1]}) {
2876 last INSCOPE;
2877 }
2878 } # INSCOPE
2879
2880 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2881
2882 $self->{insertion_mode} = 'in table';
2883
2884 !!!next-token;
2885 return;
2886 } elsif ({
2887 area => 1, basefont => 1, bgsound => 1, br => 1,
2888 embed => 1, img => 1, param => 1, spacer => 1, wbr => 1,
2889 image => 1,
2890 }->{$token->{tag_name}}) {
2891 if ($token->{tag_name} eq 'image') {
2892 !!!parse-error (type => 'image');
2893 $token->{tag_name} = 'img';
2894 }
2895
2896 ## NOTE: There is an "as if <br>" code clone.
2897 $reconstruct_active_formatting_elements->($insert_to_current);
2898
2899 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2900 pop @{$self->{open_elements}};
2901
2902 !!!next-token;
2903 return;
2904 } elsif ($token->{tag_name} eq 'hr') {
2905 ## has a p element in scope
2906 INSCOPE: for (reverse @{$self->{open_elements}}) {
2907 if ($_->[1] eq 'p') {
2908 !!!back-token;
2909 $token = {type => 'end tag', tag_name => 'p'};
2910 return;
2911 } elsif ({
2912 table => 1, caption => 1, td => 1, th => 1,
2913 button => 1, marquee => 1, object => 1, html => 1,
2914 }->{$_->[1]}) {
2915 last INSCOPE;
2916 }
2917 } # INSCOPE
2918
2919 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2920 pop @{$self->{open_elements}};
2921
2922 !!!next-token;
2923 return;
2924 } elsif ($token->{tag_name} eq 'input') {
2925 $reconstruct_active_formatting_elements->($insert_to_current);
2926
2927 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2928 ## TODO: associate with $self->{form_element} if defined
2929 pop @{$self->{open_elements}};
2930
2931 !!!next-token;
2932 return;
2933 } elsif ($token->{tag_name} eq 'isindex') {
2934 !!!parse-error (type => 'isindex');
2935
2936 if (defined $self->{form_element}) {
2937 ## Ignore the token
2938 !!!next-token;
2939 return;
2940 } else {
2941 my $at = $token->{attributes};
2942 my $form_attrs;
2943 $form_attrs->{action} = $at->{action} if $at->{action};
2944 my $prompt_attr = $at->{prompt};
2945 $at->{name} = {name => 'name', value => 'isindex'};
2946 delete $at->{action};
2947 delete $at->{prompt};
2948 my @tokens = (
2949 {type => 'start tag', tag_name => 'form',
2950 attributes => $form_attrs},
2951 {type => 'start tag', tag_name => 'hr'},
2952 {type => 'start tag', tag_name => 'p'},
2953 {type => 'start tag', tag_name => 'label'},
2954 );
2955 if ($prompt_attr) {
2956 push @tokens, {type => 'character', data => $prompt_attr->{value}};
2957 } else {
2958 push @tokens, {type => 'character',
2959 data => 'This is a searchable index. Insert your search keywords here: '}; # SHOULD
2960 ## TODO: make this configurable
2961 }
2962 push @tokens,
2963 {type => 'start tag', tag_name => 'input', attributes => $at},
2964 #{type => 'character', data => ''}, # SHOULD
2965 {type => 'end tag', tag_name => 'label'},
2966 {type => 'end tag', tag_name => 'p'},
2967 {type => 'start tag', tag_name => 'hr'},
2968 {type => 'end tag', tag_name => 'form'};
2969 $token = shift @tokens;
2970 !!!back-token (@tokens);
2971 return;
2972 }
2973 } elsif ($token->{tag_name} eq 'textarea') {
2974 my $tag_name = $token->{tag_name};
2975 my $el;
2976 !!!create-element ($el, $token->{tag_name}, $token->{attributes});
2977
2978 ## TODO: $self->{form_element} if defined
2979 $self->{content_model_flag} = 'RCDATA';
2980 delete $self->{escape}; # MUST
2981
2982 $insert->($el);
2983
2984 my $text = '';
2985 !!!next-token;
2986 if ($token->{type} eq 'character') {
2987 $token->{data} =~ s/^\x0A//;
2988 unless (length $token->{data}) {
2989 !!!next-token;
2990 }
2991 }
2992 while ($token->{type} eq 'character') {
2993 $text .= $token->{data};
2994 !!!next-token;
2995 }
2996 if (length $text) {
2997 $el->manakai_append_text ($text);
2998 }
2999
3000 $self->{content_model_flag} = 'PCDATA';
3001
3002 if ($token->{type} eq 'end tag' and
3003 $token->{tag_name} eq $tag_name) {
3004 ## Ignore the token
3005 } else {
3006 !!!parse-error (type => 'in RCDATA:#'.$token->{type});
3007 }
3008 !!!next-token;
3009 return;
3010 } elsif ({
3011 iframe => 1,
3012 noembed => 1,
3013 noframes => 1,
3014 noscript => 0, ## TODO: 1 if scripting is enabled
3015 }->{$token->{tag_name}}) {
3016 $parse_rcdata->('CDATA', $insert);
3017 return;
3018 } elsif ($token->{tag_name} eq 'select') {
3019 $reconstruct_active_formatting_elements->($insert_to_current);
3020
3021 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
3022
3023 $self->{insertion_mode} = 'in select';
3024 !!!next-token;
3025 return;
3026 } elsif ({
3027 caption => 1, col => 1, colgroup => 1, frame => 1,
3028 frameset => 1, head => 1, option => 1, optgroup => 1,
3029 tbody => 1, td => 1, tfoot => 1, th => 1,
3030 thead => 1, tr => 1,
3031 }->{$token->{tag_name}}) {
3032 !!!parse-error (type => 'in body:'.$token->{tag_name});
3033 ## Ignore the token
3034 !!!next-token;
3035 return;
3036
3037 ## ISSUE: An issue on HTML5 new elements in the spec.
3038 } else {
3039 $reconstruct_active_formatting_elements->($insert_to_current);
3040
3041 !!!insert-element-t ($token->{tag_name}, $token->{attributes});
3042
3043 !!!next-token;
3044 return;
3045 }
3046 } elsif ($token->{type} eq 'end tag') {
3047 if ($token->{tag_name} eq 'body') {
3048 if (@{$self->{open_elements}} > 1 and
3049 $self->{open_elements}->[1]->[1] eq 'body') {
3050 for (@{$self->{open_elements}}) {
3051 unless ({
3052 dd => 1, dt => 1, li => 1, p => 1, td => 1,
3053 th => 1, tr => 1, body => 1, html => 1,
3054 tbody => 1, tfoot => 1, thead => 1,
3055 }->{$_->[1]}) {
3056 !!!parse-error (type => 'not closed:'.$_->[1]);
3057 }
3058 }
3059
3060 $self->{insertion_mode} = 'after body';
3061 !!!next-token;
3062 return;
3063 } else {
3064 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3065 ## Ignore the token
3066 !!!next-token;
3067 return;
3068 }
3069 } elsif ($token->{tag_name} eq 'html') {
3070 if (@{$self->{open_elements}} > 1 and $self->{open_elements}->[1]->[1] eq 'body') {
3071 ## ISSUE: There is an issue in the spec.
3072 if ($self->{open_elements}->[-1]->[1] ne 'body') {
3073 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[1]->[1]);
3074 }
3075 $self->{insertion_mode} = 'after body';
3076 ## reprocess
3077 return;
3078 } else {
3079 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3080 ## Ignore the token
3081 !!!next-token;
3082 return;
3083 }
3084 } elsif ({
3085 address => 1, blockquote => 1, center => 1, dir => 1,
3086 div => 1, dl => 1, fieldset => 1, listing => 1,
3087 menu => 1, ol => 1, pre => 1, ul => 1,
3088 p => 1,
3089 dd => 1, dt => 1, li => 1,
3090 button => 1, marquee => 1, object => 1,
3091 }->{$token->{tag_name}}) {
3092 ## has an element in scope
3093 my $i;
3094 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3095 my $node = $self->{open_elements}->[$_];
3096 if ($node->[1] eq $token->{tag_name}) {
3097 ## generate implied end tags
3098 if ({
3099 dd => ($token->{tag_name} ne 'dd'),
3100 dt => ($token->{tag_name} ne 'dt'),
3101 li => ($token->{tag_name} ne 'li'),
3102 p => ($token->{tag_name} ne 'p'),
3103 td => 1, th => 1, tr => 1,
3104 tbody => 1, tfoot=> 1, thead => 1,
3105 }->{$self->{open_elements}->[-1]->[1]}) {
3106 !!!back-token;
3107 $token = {type => 'end tag',
3108 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3109 return;
3110 }
3111 $i = $_;
3112 last INSCOPE unless $token->{tag_name} eq 'p';
3113 } elsif ({
3114 table => 1, caption => 1, td => 1, th => 1,
3115 button => 1, marquee => 1, object => 1, html => 1,
3116 }->{$node->[1]}) {
3117 last INSCOPE;
3118 }
3119 } # INSCOPE
3120
3121 if ($self->{open_elements}->[-1]->[1] ne $token->{tag_name}) {
3122 if (defined $i) {
3123 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3124 } else {
3125 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3126 }
3127 }
3128
3129 if (defined $i) {
3130 splice @{$self->{open_elements}}, $i;
3131 } elsif ($token->{tag_name} eq 'p') {
3132 ## As if <p>, then reprocess the current token
3133 my $el;
3134 !!!create-element ($el, 'p');
3135 $insert->($el);
3136 }
3137 $clear_up_to_marker->()
3138 if {
3139 button => 1, marquee => 1, object => 1,
3140 }->{$token->{tag_name}};
3141 !!!next-token;
3142 return;
3143 } elsif ($token->{tag_name} eq 'form') {
3144 ## has an element in scope
3145 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3146 my $node = $self->{open_elements}->[$_];
3147 if ($node->[1] eq $token->{tag_name}) {
3148 ## generate implied end tags
3149 if ({
3150 dd => 1, dt => 1, li => 1, p => 1,
3151 td => 1, th => 1, tr => 1,
3152 tbody => 1, tfoot=> 1, thead => 1,
3153 }->{$self->{open_elements}->[-1]->[1]}) {
3154 !!!back-token;
3155 $token = {type => 'end tag',
3156 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3157 return;
3158 }
3159 last INSCOPE;
3160 } elsif ({
3161 table => 1, caption => 1, td => 1, th => 1,
3162 button => 1, marquee => 1, object => 1, html => 1,
3163 }->{$node->[1]}) {
3164 last INSCOPE;
3165 }
3166 } # INSCOPE
3167
3168 if ($self->{open_elements}->[-1]->[1] eq $token->{tag_name}) {
3169 pop @{$self->{open_elements}};
3170 } else {
3171 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3172 }
3173
3174 undef $self->{form_element};
3175 !!!next-token;
3176 return;
3177 } elsif ({
3178 h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,
3179 }->{$token->{tag_name}}) {
3180 ## has an element in scope
3181 my $i;
3182 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3183 my $node = $self->{open_elements}->[$_];
3184 if ({
3185 h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,
3186 }->{$node->[1]}) {
3187 ## generate implied end tags
3188 if ({
3189 dd => 1, dt => 1, li => 1, p => 1,
3190 td => 1, th => 1, tr => 1,
3191 tbody => 1, tfoot=> 1, thead => 1,
3192 }->{$self->{open_elements}->[-1]->[1]}) {
3193 !!!back-token;
3194 $token = {type => 'end tag',
3195 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3196 return;
3197 }
3198 $i = $_;
3199 last INSCOPE;
3200 } elsif ({
3201 table => 1, caption => 1, td => 1, th => 1,
3202 button => 1, marquee => 1, object => 1, html => 1,
3203 }->{$node->[1]}) {
3204 last INSCOPE;
3205 }
3206 } # INSCOPE
3207
3208 if ($self->{open_elements}->[-1]->[1] ne $token->{tag_name}) {
3209 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3210 }
3211
3212 splice @{$self->{open_elements}}, $i if defined $i;
3213 !!!next-token;
3214 return;
3215 } elsif ({
3216 a => 1,
3217 b => 1, big => 1, em => 1, font => 1, i => 1,
3218 nobr => 1, s => 1, small => 1, strile => 1,
3219 strong => 1, tt => 1, u => 1,
3220 }->{$token->{tag_name}}) {
3221 $formatting_end_tag->($token->{tag_name});
3222 return;
3223 } elsif ($token->{tag_name} eq 'br') {
3224 !!!parse-error (type => 'unmatched end tag:br');
3225
3226 ## As if <br>
3227 $reconstruct_active_formatting_elements->($insert_to_current);
3228
3229 my $el;
3230 !!!create-element ($el, 'br');
3231 $insert->($el);
3232
3233 ## Ignore the token.
3234 !!!next-token;
3235 return;
3236 } elsif ({
3237 caption => 1, col => 1, colgroup => 1, frame => 1,
3238 frameset => 1, head => 1, option => 1, optgroup => 1,
3239 tbody => 1, td => 1, tfoot => 1, th => 1,
3240 thead => 1, tr => 1,
3241 area => 1, basefont => 1, bgsound => 1,
3242 embed => 1, hr => 1, iframe => 1, image => 1,
3243 img => 1, input => 1, isindex => 1, noembed => 1,
3244 noframes => 1, param => 1, select => 1, spacer => 1,
3245 table => 1, textarea => 1, wbr => 1,
3246 noscript => 0, ## TODO: if scripting is enabled
3247 }->{$token->{tag_name}}) {
3248 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3249 ## Ignore the token
3250 !!!next-token;
3251 return;
3252
3253 ## ISSUE: Issue on HTML5 new elements in spec
3254
3255 } else {
3256 ## Step 1
3257 my $node_i = -1;
3258 my $node = $self->{open_elements}->[$node_i];
3259
3260 ## Step 2
3261 S2: {
3262 if ($node->[1] eq $token->{tag_name}) {
3263 ## Step 1
3264 ## generate implied end tags
3265 if ({
3266 dd => 1, dt => 1, li => 1, p => 1,
3267 td => 1, th => 1, tr => 1,
3268 tbody => 1, tfoot=> 1, thead => 1,
3269 }->{$self->{open_elements}->[-1]->[1]}) {
3270 !!!back-token;
3271 $token = {type => 'end tag',
3272 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3273 return;
3274 }
3275
3276 ## Step 2
3277 if ($token->{tag_name} ne $self->{open_elements}->[-1]->[1]) {
3278 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3279 }
3280
3281 ## Step 3
3282 splice @{$self->{open_elements}}, $node_i;
3283
3284 !!!next-token;
3285 last S2;
3286 } else {
3287 ## Step 3
3288 if (not $formatting_category->{$node->[1]} and
3289 #not $phrasing_category->{$node->[1]} and
3290 ($special_category->{$node->[1]} or
3291 $scoping_category->{$node->[1]})) {
3292 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3293 ## Ignore the token
3294 !!!next-token;
3295 last S2;
3296 }
3297 }
3298
3299 ## Step 4
3300 $node_i--;
3301 $node = $self->{open_elements}->[$node_i];
3302
3303 ## Step 5;
3304 redo S2;
3305 } # S2
3306 return;
3307 }
3308 }
3309 }; # $in_body
3310
3311 B: {
3312 if ($phase eq 'main') {
3313 if ($token->{type} eq 'DOCTYPE') {
3314 !!!parse-error (type => 'in html:#DOCTYPE');
3315 ## Ignore the token
3316 ## Stay in the phase
3317 !!!next-token;
3318 redo B;
3319 } elsif ($token->{type} eq 'start tag' and
3320 $token->{tag_name} eq 'html') {
3321 ## ISSUE: "aa<html>" is not a parse error.
3322 ## ISSUE: "<html>" in fragment is not a parse error.
3323 unless ($token->{first_start_tag}) {
3324 !!!parse-error (type => 'not first start tag');
3325 }
3326 my $top_el = $self->{open_elements}->[0]->[0];
3327 for my $attr_name (keys %{$token->{attributes}}) {
3328 unless ($top_el->has_attribute_ns (undef, $attr_name)) {
3329 $top_el->set_attribute_ns
3330 (undef, [undef, $attr_name],
3331 $token->{attributes}->{$attr_name}->{value});
3332 }
3333 }
3334 !!!next-token;
3335 redo B;
3336 } elsif ($token->{type} eq 'end-of-file') {
3337 ## Generate implied end tags
3338 if ({
3339 dd => 1, dt => 1, li => 1, p => 1, td => 1, th => 1, tr => 1,
3340 tbody => 1, tfoot=> 1, thead => 1,
3341 }->{$self->{open_elements}->[-1]->[1]}) {
3342 !!!back-token;
3343 $token = {type => 'end tag', tag_name => $self->{open_elements}->[-1]->[1]};
3344 redo B;
3345 }
3346
3347 if (@{$self->{open_elements}} > 2 or
3348 (@{$self->{open_elements}} == 2 and $self->{open_elements}->[1]->[1] ne 'body')) {
3349 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3350 } elsif (defined $self->{inner_html_node} and
3351 @{$self->{open_elements}} > 1 and
3352 $self->{open_elements}->[1]->[1] ne 'body') {
3353 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3354 }
3355
3356 ## Stop parsing
3357 last B;
3358
3359 ## ISSUE: There is an issue in the spec.
3360 } else {
3361 if ($self->{insertion_mode} eq 'before head') {
3362 if ($token->{type} eq 'character') {
3363 if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
3364 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
3365 unless (length $token->{data}) {
3366 !!!next-token;
3367 redo B;
3368 }
3369 }
3370 ## As if <head>
3371 !!!create-element ($self->{head_element}, 'head');
3372 $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
3373 push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3374 $self->{insertion_mode} = 'in head';
3375 ## reprocess
3376 redo B;
3377 } elsif ($token->{type} eq 'comment') {
3378 my $comment = $self->{document}->create_comment ($token->{data});
3379 $self->{open_elements}->[-1]->[0]->append_child ($comment);
3380 !!!next-token;
3381 redo B;
3382 } elsif ($token->{type} eq 'start tag') {
3383 my $attr = $token->{tag_name} eq 'head' ? $token->{attributes} : {};
3384 !!!create-element ($self->{head_element}, 'head', $attr);
3385 $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
3386 push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3387 $self->{insertion_mode} = 'in head';
3388 if ($token->{tag_name} eq 'head') {
3389 !!!next-token;
3390 #} elsif ({
3391 # base => 1, link => 1, meta => 1,
3392 # script => 1, style => 1, title => 1,
3393 # }->{$token->{tag_name}}) {
3394 # ## reprocess
3395 } else {
3396 ## reprocess
3397 }
3398 redo B;
3399 } elsif ($token->{type} eq 'end tag') {
3400 if ({
3401 head => 1, body => 1, html => 1,
3402 p => 1, br => 1,
3403 }->{$token->{tag_name}}) {
3404 ## As if <head>
3405 !!!create-element ($self->{head_element}, 'head');
3406 $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
3407 push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3408 $self->{insertion_mode} = 'in head';
3409 ## reprocess
3410 redo B;
3411 } else {
3412 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3413 ## Ignore the token ## ISSUE: An issue in the spec.
3414 !!!next-token;
3415 redo B;
3416 }
3417 } else {
3418 die "$0: $token->{type}: Unknown type";
3419 }
3420 } elsif ($self->{insertion_mode} eq 'in head' or
3421 $self->{insertion_mode} eq 'in head noscript' or
3422 $self->{insertion_mode} eq 'after head') {
3423 if ($token->{type} eq 'character') {
3424 if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
3425 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
3426 unless (length $token->{data}) {
3427 !!!next-token;
3428 redo B;
3429 }
3430 }
3431
3432 #
3433 } elsif ($token->{type} eq 'comment') {
3434 my $comment = $self->{document}->create_comment ($token->{data});
3435 $self->{open_elements}->[-1]->[0]->append_child ($comment);
3436 !!!next-token;
3437 redo B;
3438 } elsif ($token->{type} eq 'start tag') {
3439 if ({base => ($self->{insertion_mode} eq 'in head' or
3440 $self->{insertion_mode} eq 'after head'),
3441 link => 1, meta => 1}->{$token->{tag_name}}) {
3442 ## NOTE: There is a "as if in head" code clone.
3443 if ($self->{insertion_mode} eq 'after head') {
3444 !!!parse-error (type => 'after head:'.$token->{tag_name});
3445 push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3446 }
3447 !!!insert-element ($token->{tag_name}, $token->{attributes});
3448 pop @{$self->{open_elements}}; ## ISSUE: This step is missing in the spec.
3449 ## TODO: Extracting |charset| from |meta|.
3450 pop @{$self->{open_elements}}
3451 if $self->{insertion_mode} eq 'after head';
3452 !!!next-token;
3453 redo B;
3454 } elsif ($token->{tag_name} eq 'title' and
3455 $self->{insertion_mode} eq 'in head') {
3456 ## NOTE: There is a "as if in head" code clone.
3457 if ($self->{insertion_mode} eq 'after head') {
3458 !!!parse-error (type => 'after head:'.$token->{tag_name});
3459 push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3460 }
3461 my $parent = defined $self->{head_element} ? $self->{head_element}
3462 : $self->{open_elements}->[-1]->[0];
3463 $parse_rcdata->('RCDATA', sub { $parent->append_child ($_[0]) });
3464 pop @{$self->{open_elements}}
3465 if $self->{insertion_mode} eq 'after head';
3466 redo B;
3467 } elsif ($token->{tag_name} eq 'style') {
3468 ## NOTE: Or (scripting is enabled and tag_name eq 'noscript' and
3469 ## insertion mode 'in head')
3470 ## NOTE: There is a "as if in head" code clone.
3471 if ($self->{insertion_mode} eq 'after head') {
3472 !!!parse-error (type => 'after head:'.$token->{tag_name});
3473 push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3474 }
3475 $parse_rcdata->('CDATA', $insert_to_current);
3476 pop @{$self->{open_elements}}
3477 if $self->{insertion_mode} eq 'after head';
3478 redo B;
3479 } elsif ($token->{tag_name} eq 'noscript') {
3480 if ($self->{insertion_mode} eq 'in head') {
3481 ## NOTE: and scripting is disalbed
3482 !!!insert-element ($token->{tag_name}, $token->{attributes});
3483 $self->{insertion_mode} = 'in head noscript';
3484 !!!next-token;
3485 redo B;
3486 } elsif ($self->{insertion_mode} eq 'in head noscript') {
3487 !!!parse-error (type => 'in noscript:noscript');
3488 ## Ignore the token
3489 redo B;
3490 } else {
3491 #
3492 }
3493 } elsif ($token->{tag_name} eq 'head' and
3494 $self->{insertion_mode} ne 'after head') {
3495 !!!parse-error (type => 'in head:head'); # or in head noscript
3496 ## Ignore the token
3497 !!!next-token;
3498 redo B;
3499 } elsif ($self->{insertion_mode} ne 'in head noscript' and
3500 $token->{tag_name} eq 'script') {
3501 if ($self->{insertion_mode} eq 'after head') {
3502 !!!parse-error (type => 'after head:'.$token->{tag_name});
3503 push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3504 }
3505 ## NOTE: There is a "as if in head" code clone.
3506 $script_start_tag->($insert_to_current);
3507 pop @{$self->{open_elements}}
3508 if $self->{insertion_mode} eq 'after head';
3509 redo B;
3510 } elsif ($self->{insertion_mode} eq 'after head' and
3511 $token->{tag_name} eq 'body') {
3512 !!!insert-element ('body', $token->{attributes});
3513 $self->{insertion_mode} = 'in body';
3514 !!!next-token;
3515 redo B;
3516 } elsif ($self->{insertion_mode} eq 'after head' and
3517 $token->{tag_name} eq 'frameset') {
3518 !!!insert-element ('frameset', $token->{attributes});
3519 $self->{insertion_mode} = 'in frameset';
3520 !!!next-token;
3521 redo B;
3522 } else {
3523 #
3524 }
3525 } elsif ($token->{type} eq 'end tag') {
3526 if ($self->{insertion_mode} eq 'in head' and
3527 $token->{tag_name} eq 'head') {
3528 pop @{$self->{open_elements}};
3529 $self->{insertion_mode} = 'after head';
3530 !!!next-token;
3531 redo B;
3532 } elsif ($self->{insertion_mode} eq 'in head noscript' and
3533 $token->{tag_name} eq 'noscript') {
3534 pop @{$self->{open_elements}};
3535 $self->{insertion_mode} = 'in head';
3536 !!!next-token;
3537 redo B;
3538 } elsif ($self->{insertion_mode} eq 'in head' and
3539 {
3540 body => 1, html => 1,
3541 p => 1, br => 1,
3542 }->{$token->{tag_name}}) {
3543 #
3544 } elsif ($self->{insertion_mode} eq 'in head noscript' and
3545 {
3546 p => 1, br => 1,
3547 }->{$token->{tag_name}}) {
3548 #
3549 } elsif ($self->{insertion_mode} ne 'after head') {
3550 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3551 ## Ignore the token
3552 !!!next-token;
3553 redo B;
3554 } else {
3555 #
3556 }
3557 } else {
3558 #
3559 }
3560
3561 ## As if </head> or </noscript> or <body>
3562 if ($self->{insertion_mode} eq 'in head') {
3563 pop @{$self->{open_elements}};
3564 $self->{insertion_mode} = 'after head';
3565 } elsif ($self->{insertion_mode} eq 'in head noscript') {
3566 pop @{$self->{open_elements}};
3567 !!!parse-error (type => 'in noscript:'.(defined $token->{tag_name} ? ($token->{type} eq 'end tag' ? '/' : '') . $token->{tag_name} : '#' . $token->{type}));
3568 $self->{insertion_mode} = 'in head';
3569 } else { # 'after head'
3570 !!!insert-element ('body');
3571 $self->{insertion_mode} = 'in body';
3572 }
3573 ## reprocess
3574 redo B;
3575
3576 ## ISSUE: An issue in the spec.
3577 } elsif ($self->{insertion_mode} eq 'in body') {
3578 if ($token->{type} eq 'character') {
3579 ## NOTE: There is a code clone of "character in body".
3580 $reconstruct_active_formatting_elements->($insert_to_current);
3581
3582 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
3583
3584 !!!next-token;
3585 redo B;
3586 } elsif ($token->{type} eq 'comment') {
3587 ## NOTE: There is a code clone of "comment in body".
3588 my $comment = $self->{document}->create_comment ($token->{data});
3589 $self->{open_elements}->[-1]->[0]->append_child ($comment);
3590 !!!next-token;
3591 redo B;
3592 } else {
3593 $in_body->($insert_to_current);
3594 redo B;
3595 }
3596 } elsif ($self->{insertion_mode} eq 'in table') {
3597 if ($token->{type} eq 'character') {
3598 ## NOTE: There are "character in table" code clones.
3599 if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
3600 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
3601
3602 unless (length $token->{data}) {
3603 !!!next-token;
3604 redo B;
3605 }
3606 }
3607
3608 !!!parse-error (type => 'in table:#character');
3609
3610 ## As if in body, but insert into foster parent element
3611 ## ISSUE: Spec says that "whenever a node would be inserted
3612 ## into the current node" while characters might not be
3613 ## result in a new Text node.
3614 $reconstruct_active_formatting_elements->($insert_to_foster);
3615
3616 if ({
3617 table => 1, tbody => 1, tfoot => 1,
3618 thead => 1, tr => 1,
3619 }->{$self->{open_elements}->[-1]->[1]}) {
3620 # MUST
3621 my $foster_parent_element;
3622 my $next_sibling;
3623 my $prev_sibling;
3624 OE: for (reverse 0..$#{$self->{open_elements}}) {
3625 if ($self->{open_elements}->[$_]->[1] eq 'table') {
3626 my $parent = $self->{open_elements}->[$_]->[0]->parent_node;
3627 if (defined $parent and $parent->node_type == 1) {
3628 $foster_parent_element = $parent;
3629 $next_sibling = $self->{open_elements}->[$_]->[0];
3630 $prev_sibling = $next_sibling->previous_sibling;
3631 } else {
3632 $foster_parent_element = $self->{open_elements}->[$_ - 1]->[0];
3633 $prev_sibling = $foster_parent_element->last_child;
3634 }
3635 last OE;
3636 }
3637 } # OE
3638 $foster_parent_element = $self->{open_elements}->[0]->[0] and
3639 $prev_sibling = $foster_parent_element->last_child
3640 unless defined $foster_parent_element;
3641 if (defined $prev_sibling and
3642 $prev_sibling->node_type == 3) {
3643 $prev_sibling->manakai_append_text ($token->{data});
3644 } else {
3645 $foster_parent_element->insert_before
3646 ($self->{document}->create_text_node ($token->{data}),
3647 $next_sibling);
3648 }
3649 } else {
3650 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
3651 }
3652
3653 !!!next-token;
3654 redo B;
3655 } elsif ($token->{type} eq 'comment') {
3656 my $comment = $self->{document}->create_comment ($token->{data});
3657 $self->{open_elements}->[-1]->[0]->append_child ($comment);
3658 !!!next-token;
3659 redo B;
3660 } elsif ($token->{type} eq 'start tag') {
3661 if ({
3662 caption => 1,
3663 colgroup => 1,
3664 tbody => 1, tfoot => 1, thead => 1,
3665 }->{$token->{tag_name}}) {
3666 ## Clear back to table context
3667 while ($self->{open_elements}->[-1]->[1] ne 'table' and
3668 $self->{open_elements}->[-1]->[1] ne 'html') {
3669 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3670 pop @{$self->{open_elements}};
3671 }
3672
3673 push @$active_formatting_elements, ['#marker', '']
3674 if $token->{tag_name} eq 'caption';
3675
3676 !!!insert-element ($token->{tag_name}, $token->{attributes});
3677 $self->{insertion_mode} = {
3678 caption => 'in caption',
3679 colgroup => 'in column group',
3680 tbody => 'in table body',
3681 tfoot => 'in table body',
3682 thead => 'in table body',
3683 }->{$token->{tag_name}};
3684 !!!next-token;
3685 redo B;
3686 } elsif ({
3687 col => 1,
3688 td => 1, th => 1, tr => 1,
3689 }->{$token->{tag_name}}) {
3690 ## Clear back to table context
3691 while ($self->{open_elements}->[-1]->[1] ne 'table' and
3692 $self->{open_elements}->[-1]->[1] ne 'html') {
3693 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3694 pop @{$self->{open_elements}};
3695 }
3696
3697 !!!insert-element ($token->{tag_name} eq 'col' ? 'colgroup' : 'tbody');
3698 $self->{insertion_mode} = $token->{tag_name} eq 'col'
3699 ? 'in column group' : 'in table body';
3700 ## reprocess
3701 redo B;
3702 } elsif ($token->{tag_name} eq 'table') {
3703 ## NOTE: There are code clones for this "table in table"
3704 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3705
3706 ## As if </table>
3707 ## have a table element in table scope
3708 my $i;
3709 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3710 my $node = $self->{open_elements}->[$_];
3711 if ($node->[1] eq 'table') {
3712 $i = $_;
3713 last INSCOPE;
3714 } elsif ({
3715 table => 1, html => 1,
3716 }->{$node->[1]}) {
3717 last INSCOPE;
3718 }
3719 } # INSCOPE
3720 unless (defined $i) {
3721 !!!parse-error (type => 'unmatched end tag:table');
3722 ## Ignore tokens </table><table>
3723 !!!next-token;
3724 redo B;
3725 }
3726
3727 ## generate implied end tags
3728 if ({
3729 dd => 1, dt => 1, li => 1, p => 1,
3730 td => 1, th => 1, tr => 1,
3731 tbody => 1, tfoot=> 1, thead => 1,
3732 }->{$self->{open_elements}->[-1]->[1]}) {
3733 !!!back-token; # <table>
3734 $token = {type => 'end tag', tag_name => 'table'};
3735 !!!back-token;
3736 $token = {type => 'end tag',
3737 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3738 redo B;
3739 }
3740
3741 if ($self->{open_elements}->[-1]->[1] ne 'table') {
3742 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3743 }
3744
3745 splice @{$self->{open_elements}}, $i;
3746
3747 $self->_reset_insertion_mode;
3748
3749 ## reprocess
3750 redo B;
3751 } else {
3752 #
3753 }
3754 } elsif ($token->{type} eq 'end tag') {
3755 if ($token->{tag_name} eq 'table') {
3756 ## have a table element in table scope
3757 my $i;
3758 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3759 my $node = $self->{open_elements}->[$_];
3760 if ($node->[1] eq $token->{tag_name}) {
3761 $i = $_;
3762 last INSCOPE;
3763 } elsif ({
3764 table => 1, html => 1,
3765 }->{$node->[1]}) {
3766 last INSCOPE;
3767 }
3768 } # INSCOPE
3769 unless (defined $i) {
3770 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3771 ## Ignore the token
3772 !!!next-token;
3773 redo B;
3774 }
3775
3776 ## generate implied end tags
3777 if ({
3778 dd => 1, dt => 1, li => 1, p => 1,
3779 td => 1, th => 1, tr => 1,
3780 tbody => 1, tfoot=> 1, thead => 1,
3781 }->{$self->{open_elements}->[-1]->[1]}) {
3782 !!!back-token;
3783 $token = {type => 'end tag',
3784 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3785 redo B;
3786 }
3787
3788 if ($self->{open_elements}->[-1]->[1] ne 'table') {
3789 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3790 }
3791
3792 splice @{$self->{open_elements}}, $i;
3793
3794 $self->_reset_insertion_mode;
3795
3796 !!!next-token;
3797 redo B;
3798 } elsif ({
3799 body => 1, caption => 1, col => 1, colgroup => 1,
3800 html => 1, tbody => 1, td => 1, tfoot => 1, th => 1,
3801 thead => 1, tr => 1,
3802 }->{$token->{tag_name}}) {
3803 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3804 ## Ignore the token
3805 !!!next-token;
3806 redo B;
3807 } else {
3808 #
3809 }
3810 } else {
3811 #
3812 }
3813
3814 !!!parse-error (type => 'in table:'.$token->{tag_name});
3815 $in_body->($insert_to_foster);
3816 redo B;
3817 } elsif ($self->{insertion_mode} eq 'in caption') {
3818 if ($token->{type} eq 'character') {
3819 ## NOTE: This is a code clone of "character in body".
3820 $reconstruct_active_formatting_elements->($insert_to_current);
3821
3822 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
3823
3824 !!!next-token;
3825 redo B;
3826 } elsif ($token->{type} eq 'comment') {
3827 ## NOTE: This is a code clone of "comment in body".
3828 my $comment = $self->{document}->create_comment ($token->{data});
3829 $self->{open_elements}->[-1]->[0]->append_child ($comment);
3830 !!!next-token;
3831 redo B;
3832 } elsif ($token->{type} eq 'start tag') {
3833 if ({
3834 caption => 1, col => 1, colgroup => 1, tbody => 1,
3835 td => 1, tfoot => 1, th => 1, thead => 1, tr => 1,
3836 }->{$token->{tag_name}}) {
3837 !!!parse-error (type => 'not closed:caption');
3838
3839 ## As if </caption>
3840 ## have a table element in table scope
3841 my $i;
3842 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3843 my $node = $self->{open_elements}->[$_];
3844 if ($node->[1] eq 'caption') {
3845 $i = $_;
3846 last INSCOPE;
3847 } elsif ({
3848 table => 1, html => 1,
3849 }->{$node->[1]}) {
3850 last INSCOPE;
3851 }
3852 } # INSCOPE
3853 unless (defined $i) {
3854 !!!parse-error (type => 'unmatched end tag:caption');
3855 ## Ignore the token
3856 !!!next-token;
3857 redo B;
3858 }
3859
3860 ## generate implied end tags
3861 if ({
3862 dd => 1, dt => 1, li => 1, p => 1,
3863 td => 1, th => 1, tr => 1,
3864 tbody => 1, tfoot=> 1, thead => 1,
3865 }->{$self->{open_elements}->[-1]->[1]}) {
3866 !!!back-token; # <?>
3867 $token = {type => 'end tag', tag_name => 'caption'};
3868 !!!back-token;
3869 $token = {type => 'end tag',
3870 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3871 redo B;
3872 }
3873
3874 if ($self->{open_elements}->[-1]->[1] ne 'caption') {
3875 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3876 }
3877
3878 splice @{$self->{open_elements}}, $i;
3879
3880 $clear_up_to_marker->();
3881
3882 $self->{insertion_mode} = 'in table';
3883
3884 ## reprocess
3885 redo B;
3886 } else {
3887 #
3888 }
3889 } elsif ($token->{type} eq 'end tag') {
3890 if ($token->{tag_name} eq 'caption') {
3891 ## have a table element in table scope
3892 my $i;
3893 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3894 my $node = $self->{open_elements}->[$_];
3895 if ($node->[1] eq $token->{tag_name}) {
3896 $i = $_;
3897 last INSCOPE;
3898 } elsif ({
3899 table => 1, html => 1,
3900 }->{$node->[1]}) {
3901 last INSCOPE;
3902 }
3903 } # INSCOPE
3904 unless (defined $i) {
3905 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3906 ## Ignore the token
3907 !!!next-token;
3908 redo B;
3909 }
3910
3911 ## generate implied end tags
3912 if ({
3913 dd => 1, dt => 1, li => 1, p => 1,
3914 td => 1, th => 1, tr => 1,
3915 tbody => 1, tfoot=> 1, thead => 1,
3916 }->{$self->{open_elements}->[-1]->[1]}) {
3917 !!!back-token;
3918 $token = {type => 'end tag',
3919 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3920 redo B;
3921 }
3922
3923 if ($self->{open_elements}->[-1]->[1] ne 'caption') {
3924 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3925 }
3926
3927 splice @{$self->{open_elements}}, $i;
3928
3929 $clear_up_to_marker->();
3930
3931 $self->{insertion_mode} = 'in table';
3932
3933 !!!next-token;
3934 redo B;
3935 } elsif ($token->{tag_name} eq 'table') {
3936 !!!parse-error (type => 'not closed:caption');
3937
3938 ## As if </caption>
3939 ## have a table element in table scope
3940 my $i;
3941 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3942 my $node = $self->{open_elements}->[$_];
3943 if ($node->[1] eq 'caption') {
3944 $i = $_;
3945 last INSCOPE;
3946 } elsif ({
3947 table => 1, html => 1,
3948 }->{$node->[1]}) {
3949 last INSCOPE;
3950 }
3951 } # INSCOPE
3952 unless (defined $i) {
3953 !!!parse-error (type => 'unmatched end tag:caption');
3954 ## Ignore the token
3955 !!!next-token;
3956 redo B;
3957 }
3958
3959 ## generate implied end tags
3960 if ({
3961 dd => 1, dt => 1, li => 1, p => 1,
3962 td => 1, th => 1, tr => 1,
3963 tbody => 1, tfoot=> 1, thead => 1,
3964 }->{$self->{open_elements}->[-1]->[1]}) {
3965 !!!back-token; # </table>
3966 $token = {type => 'end tag', tag_name => 'caption'};
3967 !!!back-token;
3968 $token = {type => 'end tag',
3969 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3970 redo B;
3971 }
3972
3973 if ($self->{open_elements}->[-1]->[1] ne 'caption') {
3974 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3975 }
3976
3977 splice @{$self->{open_elements}}, $i;
3978
3979 $clear_up_to_marker->();
3980
3981 $self->{insertion_mode} = 'in table';
3982
3983 ## reprocess
3984 redo B;
3985 } elsif ({
3986 body => 1, col => 1, colgroup => 1,
3987 html => 1, tbody => 1, td => 1, tfoot => 1,
3988 th => 1, thead => 1, tr => 1,
3989 }->{$token->{tag_name}}) {
3990 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3991 ## Ignore the token
3992 redo B;
3993 } else {
3994 #
3995 }
3996 } else {
3997 #
3998 }
3999
4000 $in_body->($insert_to_current);
4001 redo B;
4002 } elsif ($self->{insertion_mode} eq 'in column group') {
4003 if ($token->{type} eq 'character') {
4004 if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
4005 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
4006 unless (length $token->{data}) {
4007 !!!next-token;
4008 redo B;
4009 }
4010 }
4011
4012 #
4013 } elsif ($token->{type} eq 'comment') {
4014 my $comment = $self->{document}->create_comment ($token->{data});
4015 $self->{open_elements}->[-1]->[0]->append_child ($comment);
4016 !!!next-token;
4017 redo B;
4018 } elsif ($token->{type} eq 'start tag') {
4019 if ($token->{tag_name} eq 'col') {
4020 !!!insert-element ($token->{tag_name}, $token->{attributes});
4021 pop @{$self->{open_elements}};
4022 !!!next-token;
4023 redo B;
4024 } else {
4025 #
4026 }
4027 } elsif ($token->{type} eq 'end tag') {
4028 if ($token->{tag_name} eq 'colgroup') {
4029 if ($self->{open_elements}->[-1]->[1] eq 'html') {
4030 !!!parse-error (type => 'unmatched end tag:colgroup');
4031 ## Ignore the token
4032 !!!next-token;
4033 redo B;
4034 } else {
4035 pop @{$self->{open_elements}}; # colgroup
4036 $self->{insertion_mode} = 'in table';
4037 !!!next-token;
4038 redo B;
4039 }
4040 } elsif ($token->{tag_name} eq 'col') {
4041 !!!parse-error (type => 'unmatched end tag:col');
4042 ## Ignore the token
4043 !!!next-token;
4044 redo B;
4045 } else {
4046 #
4047 }
4048 } else {
4049 #
4050 }
4051
4052 ## As if </colgroup>
4053 if ($self->{open_elements}->[-1]->[1] eq 'html') {
4054 !!!parse-error (type => 'unmatched end tag:colgroup');
4055 ## Ignore the token
4056 !!!next-token;
4057 redo B;
4058 } else {
4059 pop @{$self->{open_elements}}; # colgroup
4060 $self->{insertion_mode} = 'in table';
4061 ## reprocess
4062 redo B;
4063 }
4064 } elsif ($self->{insertion_mode} eq 'in table body') {
4065 if ($token->{type} eq 'character') {
4066 ## NOTE: This is a "character in table" code clone.
4067 if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
4068 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
4069
4070 unless (length $token->{data}) {
4071 !!!next-token;
4072 redo B;
4073 }
4074 }
4075
4076 !!!parse-error (type => 'in table:#character');
4077
4078 ## As if in body, but insert into foster parent element
4079 ## ISSUE: Spec says that "whenever a node would be inserted
4080 ## into the current node" while characters might not be
4081 ## result in a new Text node.
4082 $reconstruct_active_formatting_elements->($insert_to_foster);
4083
4084 if ({
4085 table => 1, tbody => 1, tfoot => 1,
4086 thead => 1, tr => 1,
4087 }->{$self->{open_elements}->[-1]->[1]}) {
4088 # MUST
4089 my $foster_parent_element;
4090 my $next_sibling;
4091 my $prev_sibling;
4092 OE: for (reverse 0..$#{$self->{open_elements}}) {
4093 if ($self->{open_elements}->[$_]->[1] eq 'table') {
4094 my $parent = $self->{open_elements}->[$_]->[0]->parent_node;
4095 if (defined $parent and $parent->node_type == 1) {
4096 $foster_parent_element = $parent;
4097 $next_sibling = $self->{open_elements}->[$_]->[0];
4098 $prev_sibling = $next_sibling->previous_sibling;
4099 } else {
4100 $foster_parent_element = $self->{open_elements}->[$_ - 1]->[0];
4101 $prev_sibling = $foster_parent_element->last_child;
4102 }
4103 last OE;
4104 }
4105 } # OE
4106 $foster_parent_element = $self->{open_elements}->[0]->[0] and
4107 $prev_sibling = $foster_parent_element->last_child
4108 unless defined $foster_parent_element;
4109 if (defined $prev_sibling and
4110 $prev_sibling->node_type == 3) {
4111 $prev_sibling->manakai_append_text ($token->{data});
4112 } else {
4113 $foster_parent_element->insert_before
4114 ($self->{document}->create_text_node ($token->{data}),
4115 $next_sibling);
4116 }
4117 } else {
4118 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
4119 }
4120
4121 !!!next-token;
4122 redo B;
4123 } elsif ($token->{type} eq 'comment') {
4124 ## Copied from 'in table'
4125 my $comment = $self->{document}->create_comment ($token->{data});
4126 $self->{open_elements}->[-1]->[0]->append_child ($comment);
4127 !!!next-token;
4128 redo B;
4129 } elsif ($token->{type} eq 'start tag') {
4130 if ({
4131 tr => 1,
4132 th => 1, td => 1,
4133 }->{$token->{tag_name}}) {
4134 unless ($token->{tag_name} eq 'tr') {
4135 !!!parse-error (type => 'missing start tag:tr');
4136 }
4137
4138 ## Clear back to table body context
4139 while (not {
4140 tbody => 1, tfoot => 1, thead => 1, html => 1,
4141 }->{$self->{open_elements}->[-1]->[1]}) {
4142 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4143 pop @{$self->{open_elements}};
4144 }
4145
4146 $self->{insertion_mode} = 'in row';
4147 if ($token->{tag_name} eq 'tr') {
4148 !!!insert-element ($token->{tag_name}, $token->{attributes});
4149 !!!next-token;
4150 } else {
4151 !!!insert-element ('tr');
4152 ## reprocess
4153 }
4154 redo B;
4155 } elsif ({
4156 caption => 1, col => 1, colgroup => 1,
4157 tbody => 1, tfoot => 1, thead => 1,
4158 }->{$token->{tag_name}}) {
4159 ## have an element in table scope
4160 my $i;
4161 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4162 my $node = $self->{open_elements}->[$_];
4163 if ({
4164 tbody => 1, thead => 1, tfoot => 1,
4165 }->{$node->[1]}) {
4166 $i = $_;
4167 last INSCOPE;
4168 } elsif ({
4169 table => 1, html => 1,
4170 }->{$node->[1]}) {
4171 last INSCOPE;
4172 }
4173 } # INSCOPE
4174 unless (defined $i) {
4175 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4176 ## Ignore the token
4177 !!!next-token;
4178 redo B;
4179 }
4180
4181 ## Clear back to table body context
4182 while (not {
4183 tbody => 1, tfoot => 1, thead => 1, html => 1,
4184 }->{$self->{open_elements}->[-1]->[1]}) {
4185 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4186 pop @{$self->{open_elements}};
4187 }
4188
4189 ## As if <{current node}>
4190 ## have an element in table scope
4191 ## true by definition
4192
4193 ## Clear back to table body context
4194 ## nop by definition
4195
4196 pop @{$self->{open_elements}};
4197 $self->{insertion_mode} = 'in table';
4198 ## reprocess
4199 redo B;
4200 } elsif ($token->{tag_name} eq 'table') {
4201 ## NOTE: This is a code clone of "table in table"
4202 !!!parse-error (type => 'not closed:table');
4203
4204 ## As if </table>
4205 ## have a table element in table scope
4206 my $i;
4207 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4208 my $node = $self->{open_elements}->[$_];
4209 if ($node->[1] eq 'table') {
4210 $i = $_;
4211 last INSCOPE;
4212 } elsif ({
4213 table => 1, html => 1,
4214 }->{$node->[1]}) {
4215 last INSCOPE;
4216 }
4217 } # INSCOPE
4218 unless (defined $i) {
4219 !!!parse-error (type => 'unmatched end tag:table');
4220 ## Ignore tokens </table><table>
4221 !!!next-token;
4222 redo B;
4223 }
4224
4225 ## generate implied end tags
4226 if ({
4227 dd => 1, dt => 1, li => 1, p => 1,
4228 td => 1, th => 1, tr => 1,
4229 tbody => 1, tfoot=> 1, thead => 1,
4230 }->{$self->{open_elements}->[-1]->[1]}) {
4231 !!!back-token; # <table>
4232 $token = {type => 'end tag', tag_name => 'table'};
4233 !!!back-token;
4234 $token = {type => 'end tag',
4235 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
4236 redo B;
4237 }
4238
4239 if ($self->{open_elements}->[-1]->[1] ne 'table') {
4240 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4241 }
4242
4243 splice @{$self->{open_elements}}, $i;
4244
4245 $self->_reset_insertion_mode;
4246
4247 ## reprocess
4248 redo B;
4249 } else {
4250 #
4251 }
4252 } elsif ($token->{type} eq 'end tag') {
4253 if ({
4254 tbody => 1, tfoot => 1, thead => 1,
4255 }->{$token->{tag_name}}) {
4256 ## have an element in table scope
4257 my $i;
4258 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4259 my $node = $self->{open_elements}->[$_];
4260 if ($node->[1] eq $token->{tag_name}) {
4261 $i = $_;
4262 last INSCOPE;
4263 } elsif ({
4264 table => 1, html => 1,
4265 }->{$node->[1]}) {
4266 last INSCOPE;
4267 }
4268 } # INSCOPE
4269 unless (defined $i) {
4270 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4271 ## Ignore the token
4272 !!!next-token;
4273 redo B;
4274 }
4275
4276 ## Clear back to table body context
4277 while (not {
4278 tbody => 1, tfoot => 1, thead => 1, html => 1,
4279 }->{$self->{open_elements}->[-1]->[1]}) {
4280 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4281 pop @{$self->{open_elements}};
4282 }
4283
4284 pop @{$self->{open_elements}};
4285 $self->{insertion_mode} = 'in table';
4286 !!!next-token;
4287 redo B;
4288 } elsif ($token->{tag_name} eq 'table') {
4289 ## have an element in table scope
4290 my $i;
4291 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4292 my $node = $self->{open_elements}->[$_];
4293 if ({
4294 tbody => 1, thead => 1, tfoot => 1,
4295 }->{$node->[1]}) {
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 ## As if <{current node}>
4320 ## have an element in table scope
4321 ## true by definition
4322
4323 ## Clear back to table body context
4324 ## nop by definition
4325
4326 pop @{$self->{open_elements}};
4327 $self->{insertion_mode} = 'in table';
4328 ## reprocess
4329 redo B;
4330 } elsif ({
4331 body => 1, caption => 1, col => 1, colgroup => 1,
4332 html => 1, td => 1, th => 1, tr => 1,
4333 }->{$token->{tag_name}}) {
4334 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4335 ## Ignore the token
4336 !!!next-token;
4337 redo B;
4338 } else {
4339 #
4340 }
4341 } else {
4342 #
4343 }
4344
4345 ## As if in table
4346 !!!parse-error (type => 'in table:'.$token->{tag_name});
4347 $in_body->($insert_to_foster);
4348 redo B;
4349 } elsif ($self->{insertion_mode} eq 'in row') {
4350 if ($token->{type} eq 'character') {
4351 ## NOTE: This is a "character in table" code clone.
4352 if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
4353 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
4354
4355 unless (length $token->{data}) {
4356 !!!next-token;
4357 redo B;
4358 }
4359 }
4360
4361 !!!parse-error (type => 'in table:#character');
4362
4363 ## As if in body, but insert into foster parent element
4364 ## ISSUE: Spec says that "whenever a node would be inserted
4365 ## into the current node" while characters might not be
4366 ## result in a new Text node.
4367 $reconstruct_active_formatting_elements->($insert_to_foster);
4368
4369 if ({
4370 table => 1, tbody => 1, tfoot => 1,
4371 thead => 1, tr => 1,
4372 }->{$self->{open_elements}->[-1]->[1]}) {
4373 # MUST
4374 my $foster_parent_element;
4375 my $next_sibling;
4376 my $prev_sibling;
4377 OE: for (reverse 0..$#{$self->{open_elements}}) {
4378 if ($self->{open_elements}->[$_]->[1] eq 'table') {
4379 my $parent = $self->{open_elements}->[$_]->[0]->parent_node;
4380 if (defined $parent and $parent->node_type == 1) {
4381 $foster_parent_element = $parent;
4382 $next_sibling = $self->{open_elements}->[$_]->[0];
4383 $prev_sibling = $next_sibling->previous_sibling;
4384 } else {
4385 $foster_parent_element = $self->{open_elements}->[$_ - 1]->[0];
4386 $prev_sibling = $foster_parent_element->last_child;
4387 }
4388 last OE;
4389 }
4390 } # OE
4391 $foster_parent_element = $self->{open_elements}->[0]->[0] and
4392 $prev_sibling = $foster_parent_element->last_child
4393 unless defined $foster_parent_element;
4394 if (defined $prev_sibling and
4395 $prev_sibling->node_type == 3) {
4396 $prev_sibling->manakai_append_text ($token->{data});
4397 } else {
4398 $foster_parent_element->insert_before
4399 ($self->{document}->create_text_node ($token->{data}),
4400 $next_sibling);
4401 }
4402 } else {
4403 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
4404 }
4405
4406 !!!next-token;
4407 redo B;
4408 } elsif ($token->{type} eq 'comment') {
4409 ## Copied from 'in table'
4410 my $comment = $self->{document}->create_comment ($token->{data});
4411 $self->{open_elements}->[-1]->[0]->append_child ($comment);
4412 !!!next-token;
4413 redo B;
4414 } elsif ($token->{type} eq 'start tag') {
4415 if ($token->{tag_name} eq 'th' or
4416 $token->{tag_name} eq 'td') {
4417 ## Clear back to table row context
4418 while (not {
4419 tr => 1, html => 1,
4420 }->{$self->{open_elements}->[-1]->[1]}) {
4421 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4422 pop @{$self->{open_elements}};
4423 }
4424
4425 !!!insert-element ($token->{tag_name}, $token->{attributes});
4426 $self->{insertion_mode} = 'in cell';
4427
4428 push @$active_formatting_elements, ['#marker', ''];
4429
4430 !!!next-token;
4431 redo B;
4432 } elsif ({
4433 caption => 1, col => 1, colgroup => 1,
4434 tbody => 1, tfoot => 1, thead => 1, tr => 1,
4435 }->{$token->{tag_name}}) {
4436 ## As if </tr>
4437 ## have an element in table scope
4438 my $i;
4439 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4440 my $node = $self->{open_elements}->[$_];
4441 if ($node->[1] eq 'tr') {
4442 $i = $_;
4443 last INSCOPE;
4444 } elsif ({
4445 table => 1, html => 1,
4446 }->{$node->[1]}) {
4447 last INSCOPE;
4448 }
4449 } # INSCOPE
4450 unless (defined $i) {
4451 !!!parse-error (type => 'unmacthed end tag:'.$token->{tag_name});
4452 ## Ignore the token
4453 !!!next-token;
4454 redo B;
4455 }
4456
4457 ## Clear back to table row context
4458 while (not {
4459 tr => 1, html => 1,
4460 }->{$self->{open_elements}->[-1]->[1]}) {
4461 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4462 pop @{$self->{open_elements}};
4463 }
4464
4465 pop @{$self->{open_elements}}; # tr
4466 $self->{insertion_mode} = 'in table body';
4467 ## reprocess
4468 redo B;
4469 } elsif ($token->{tag_name} eq 'table') {
4470 ## NOTE: This is a code clone of "table in table"
4471 !!!parse-error (type => 'not closed:table');
4472
4473 ## As if </table>
4474 ## have a table element in table scope
4475 my $i;
4476 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4477 my $node = $self->{open_elements}->[$_];
4478 if ($node->[1] eq 'table') {
4479 $i = $_;
4480 last INSCOPE;
4481 } elsif ({
4482 table => 1, html => 1,
4483 }->{$node->[1]}) {
4484 last INSCOPE;
4485 }
4486 } # INSCOPE
4487 unless (defined $i) {
4488 !!!parse-error (type => 'unmatched end tag:table');
4489 ## Ignore tokens </table><table>
4490 !!!next-token;
4491 redo B;
4492 }
4493
4494 ## generate implied end tags
4495 if ({
4496 dd => 1, dt => 1, li => 1, p => 1,
4497 td => 1, th => 1, tr => 1,
4498 tbody => 1, tfoot=> 1, thead => 1,
4499 }->{$self->{open_elements}->[-1]->[1]}) {
4500 !!!back-token; # <table>
4501 $token = {type => 'end tag', tag_name => 'table'};
4502 !!!back-token;
4503 $token = {type => 'end tag',
4504 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
4505 redo B;
4506 }
4507
4508 if ($self->{open_elements}->[-1]->[1] ne 'table') {
4509 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4510 }
4511
4512 splice @{$self->{open_elements}}, $i;
4513
4514 $self->_reset_insertion_mode;
4515
4516 ## reprocess
4517 redo B;
4518 } else {
4519 #
4520 }
4521 } elsif ($token->{type} eq 'end tag') {
4522 if ($token->{tag_name} eq 'tr') {
4523 ## have an element in table scope
4524 my $i;
4525 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4526 my $node = $self->{open_elements}->[$_];
4527 if ($node->[1] eq $token->{tag_name}) {
4528 $i = $_;
4529 last INSCOPE;
4530 } elsif ({
4531 table => 1, html => 1,
4532 }->{$node->[1]}) {
4533 last INSCOPE;
4534 }
4535 } # INSCOPE
4536 unless (defined $i) {
4537 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4538 ## Ignore the token
4539 !!!next-token;
4540 redo B;
4541 }
4542
4543 ## Clear back to table row context
4544 while (not {
4545 tr => 1, html => 1,
4546 }->{$self->{open_elements}->[-1]->[1]}) {
4547 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4548 pop @{$self->{open_elements}};
4549 }
4550
4551 pop @{$self->{open_elements}}; # tr
4552 $self->{insertion_mode} = 'in table body';
4553 !!!next-token;
4554 redo B;
4555 } elsif ($token->{tag_name} eq 'table') {
4556 ## As if </tr>
4557 ## have an element in table scope
4558 my $i;
4559 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4560 my $node = $self->{open_elements}->[$_];
4561 if ($node->[1] eq 'tr') {
4562 $i = $_;
4563 last INSCOPE;
4564 } elsif ({
4565 table => 1, html => 1,
4566 }->{$node->[1]}) {
4567 last INSCOPE;
4568 }
4569 } # INSCOPE
4570 unless (defined $i) {
4571 !!!parse-error (type => 'unmatched end tag:'.$token->{type});
4572 ## Ignore the token
4573 !!!next-token;
4574 redo B;
4575 }
4576
4577 ## Clear back to table row context
4578 while (not {
4579 tr => 1, html => 1,
4580 }->{$self->{open_elements}->[-1]->[1]}) {
4581 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4582 pop @{$self->{open_elements}};
4583 }
4584
4585 pop @{$self->{open_elements}}; # tr
4586 $self->{insertion_mode} = 'in table body';
4587 ## reprocess
4588 redo B;
4589 } elsif ({
4590 tbody => 1, tfoot => 1, thead => 1,
4591 }->{$token->{tag_name}}) {
4592 ## have an element in table scope
4593 my $i;
4594 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4595 my $node = $self->{open_elements}->[$_];
4596 if ($node->[1] eq $token->{tag_name}) {
4597 $i = $_;
4598 last INSCOPE;
4599 } elsif ({
4600 table => 1, html => 1,
4601 }->{$node->[1]}) {
4602 last INSCOPE;
4603 }
4604 } # INSCOPE
4605 unless (defined $i) {
4606 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4607 ## Ignore the token
4608 !!!next-token;
4609 redo B;
4610 }
4611
4612 ## As if </tr>
4613 ## have an element in table scope
4614 my $i;
4615 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4616 my $node = $self->{open_elements}->[$_];
4617 if ($node->[1] eq 'tr') {
4618 $i = $_;
4619 last INSCOPE;
4620 } elsif ({
4621 table => 1, html => 1,
4622 }->{$node->[1]}) {
4623 last INSCOPE;
4624 }
4625 } # INSCOPE
4626 unless (defined $i) {
4627 !!!parse-error (type => 'unmatched end tag:tr');
4628 ## Ignore the token
4629 !!!next-token;
4630 redo B;
4631 }
4632
4633 ## Clear back to table row context
4634 while (not {
4635 tr => 1, html => 1,
4636 }->{$self->{open_elements}->[-1]->[1]}) {
4637 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4638 pop @{$self->{open_elements}};
4639 }
4640
4641 pop @{$self->{open_elements}}; # tr
4642 $self->{insertion_mode} = 'in table body';
4643 ## reprocess
4644 redo B;
4645 } elsif ({
4646 body => 1, caption => 1, col => 1,
4647 colgroup => 1, html => 1, td => 1, th => 1,
4648 }->{$token->{tag_name}}) {
4649 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4650 ## Ignore the token
4651 !!!next-token;
4652 redo B;
4653 } else {
4654 #
4655 }
4656 } else {
4657 #
4658 }
4659
4660 ## As if in table
4661 !!!parse-error (type => 'in table:'.$token->{tag_name});
4662 $in_body->($insert_to_foster);
4663 redo B;
4664 } elsif ($self->{insertion_mode} eq 'in cell') {
4665 if ($token->{type} eq 'character') {
4666 ## NOTE: This is a code clone of "character in body".
4667 $reconstruct_active_formatting_elements->($insert_to_current);
4668
4669 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
4670
4671 !!!next-token;
4672 redo B;
4673 } elsif ($token->{type} eq 'comment') {
4674 ## NOTE: This is a code clone of "comment in body".
4675 my $comment = $self->{document}->create_comment ($token->{data});
4676 $self->{open_elements}->[-1]->[0]->append_child ($comment);
4677 !!!next-token;
4678 redo B;
4679 } elsif ($token->{type} eq 'start tag') {
4680 if ({
4681 caption => 1, col => 1, colgroup => 1,
4682 tbody => 1, td => 1, tfoot => 1, th => 1,
4683 thead => 1, tr => 1,
4684 }->{$token->{tag_name}}) {
4685 ## have an element in table scope
4686 my $tn;
4687 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4688 my $node = $self->{open_elements}->[$_];
4689 if ($node->[1] eq 'td' or $node->[1] eq 'th') {
4690 $tn = $node->[1];
4691 last INSCOPE;
4692 } elsif ({
4693 table => 1, html => 1,
4694 }->{$node->[1]}) {
4695 last INSCOPE;
4696 }
4697 } # INSCOPE
4698 unless (defined $tn) {
4699 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4700 ## Ignore the token
4701 !!!next-token;
4702 redo B;
4703 }
4704
4705 ## Close the cell
4706 !!!back-token; # <?>
4707 $token = {type => 'end tag', tag_name => $tn};
4708 redo B;
4709 } else {
4710 #
4711 }
4712 } elsif ($token->{type} eq 'end tag') {
4713 if ($token->{tag_name} eq 'td' or $token->{tag_name} eq 'th') {
4714 ## have an element in table scope
4715 my $i;
4716 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4717 my $node = $self->{open_elements}->[$_];
4718 if ($node->[1] eq $token->{tag_name}) {
4719 $i = $_;
4720 last INSCOPE;
4721 } elsif ({
4722 table => 1, html => 1,
4723 }->{$node->[1]}) {
4724 last INSCOPE;
4725 }
4726 } # INSCOPE
4727 unless (defined $i) {
4728 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4729 ## Ignore the token
4730 !!!next-token;
4731 redo B;
4732 }
4733
4734 ## generate implied end tags
4735 if ({
4736 dd => 1, dt => 1, li => 1, p => 1,
4737 td => ($token->{tag_name} eq 'th'),
4738 th => ($token->{tag_name} eq 'td'),
4739 tr => 1,
4740 tbody => 1, tfoot=> 1, thead => 1,
4741 }->{$self->{open_elements}->[-1]->[1]}) {
4742 !!!back-token;
4743 $token = {type => 'end tag',
4744 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
4745 redo B;
4746 }
4747
4748 if ($self->{open_elements}->[-1]->[1] ne $token->{tag_name}) {
4749 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4750 }
4751
4752 splice @{$self->{open_elements}}, $i;
4753
4754 $clear_up_to_marker->();
4755
4756 $self->{insertion_mode} = 'in row';
4757
4758 !!!next-token;
4759 redo B;
4760 } elsif ({
4761 body => 1, caption => 1, col => 1,
4762 colgroup => 1, html => 1,
4763 }->{$token->{tag_name}}) {
4764 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4765 ## Ignore the token
4766 !!!next-token;
4767 redo B;
4768 } elsif ({
4769 table => 1, tbody => 1, tfoot => 1,
4770 thead => 1, tr => 1,
4771 }->{$token->{tag_name}}) {
4772 ## have an element in table scope
4773 my $i;
4774 my $tn;
4775 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4776 my $node = $self->{open_elements}->[$_];
4777 if ($node->[1] eq $token->{tag_name}) {
4778 $i = $_;
4779 last INSCOPE;
4780 } elsif ($node->[1] eq 'td' or $node->[1] eq 'th') {
4781 $tn = $node->[1];
4782 ## NOTE: There is exactly one |td| or |th| element
4783 ## in scope in the stack of open elements by definition.
4784 } elsif ({
4785 table => 1, html => 1,
4786 }->{$node->[1]}) {
4787 last INSCOPE;
4788 }
4789 } # INSCOPE
4790 unless (defined $i) {
4791 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4792 ## Ignore the token
4793 !!!next-token;
4794 redo B;
4795 }
4796
4797 ## Close the cell
4798 !!!back-token; # </?>
4799 $token = {type => 'end tag', tag_name => $tn};
4800 redo B;
4801 } else {
4802 #
4803 }
4804 } else {
4805 #
4806 }
4807
4808 $in_body->($insert_to_current);
4809 redo B;
4810 } elsif ($self->{insertion_mode} eq 'in select') {
4811 if ($token->{type} eq 'character') {
4812 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
4813 !!!next-token;
4814 redo B;
4815 } elsif ($token->{type} eq 'comment') {
4816 my $comment = $self->{document}->create_comment ($token->{data});
4817 $self->{open_elements}->[-1]->[0]->append_child ($comment);
4818 !!!next-token;
4819 redo B;
4820 } elsif ($token->{type} eq 'start tag') {
4821 if ($token->{tag_name} eq 'option') {
4822 if ($self->{open_elements}->[-1]->[1] eq 'option') {
4823 ## As if </option>
4824 pop @{$self->{open_elements}};
4825 }
4826
4827 !!!insert-element ($token->{tag_name}, $token->{attributes});
4828 !!!next-token;
4829 redo B;
4830 } elsif ($token->{tag_name} eq 'optgroup') {
4831 if ($self->{open_elements}->[-1]->[1] eq 'option') {
4832 ## As if </option>
4833 pop @{$self->{open_elements}};
4834 }
4835
4836 if ($self->{open_elements}->[-1]->[1] eq 'optgroup') {
4837 ## As if </optgroup>
4838 pop @{$self->{open_elements}};
4839 }
4840
4841 !!!insert-element ($token->{tag_name}, $token->{attributes});
4842 !!!next-token;
4843 redo B;
4844 } elsif ($token->{tag_name} eq 'select') {
4845 !!!parse-error (type => 'not closed:select');
4846 ## As if </select> instead
4847 ## have an element in table scope
4848 my $i;
4849 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4850 my $node = $self->{open_elements}->[$_];
4851 if ($node->[1] eq $token->{tag_name}) {
4852 $i = $_;
4853 last INSCOPE;
4854 } elsif ({
4855 table => 1, html => 1,
4856 }->{$node->[1]}) {
4857 last INSCOPE;
4858 }
4859 } # INSCOPE
4860 unless (defined $i) {
4861 !!!parse-error (type => 'unmatched end tag:select');
4862 ## Ignore the token
4863 !!!next-token;
4864 redo B;
4865 }
4866
4867 splice @{$self->{open_elements}}, $i;
4868
4869 $self->_reset_insertion_mode;
4870
4871 !!!next-token;
4872 redo B;
4873 } else {
4874 #
4875 }
4876 } elsif ($token->{type} eq 'end tag') {
4877 if ($token->{tag_name} eq 'optgroup') {
4878 if ($self->{open_elements}->[-1]->[1] eq 'option' and
4879 $self->{open_elements}->[-2]->[1] eq 'optgroup') {
4880 ## As if </option>
4881 splice @{$self->{open_elements}}, -2;
4882 } elsif ($self->{open_elements}->[-1]->[1] eq 'optgroup') {
4883 pop @{$self->{open_elements}};
4884 } else {
4885 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4886 ## Ignore the token
4887 }
4888 !!!next-token;
4889 redo B;
4890 } elsif ($token->{tag_name} eq 'option') {
4891 if ($self->{open_elements}->[-1]->[1] eq 'option') {
4892 pop @{$self->{open_elements}};
4893 } else {
4894 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4895 ## Ignore the token
4896 }
4897 !!!next-token;
4898 redo B;
4899 } elsif ($token->{tag_name} eq 'select') {
4900 ## have an element in table scope
4901 my $i;
4902 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4903 my $node = $self->{open_elements}->[$_];
4904 if ($node->[1] eq $token->{tag_name}) {
4905 $i = $_;
4906 last INSCOPE;
4907 } elsif ({
4908 table => 1, html => 1,
4909 }->{$node->[1]}) {
4910 last INSCOPE;
4911 }
4912 } # INSCOPE
4913 unless (defined $i) {
4914 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4915 ## Ignore the token
4916 !!!next-token;
4917 redo B;
4918 }
4919
4920 splice @{$self->{open_elements}}, $i;
4921
4922 $self->_reset_insertion_mode;
4923
4924 !!!next-token;
4925 redo B;
4926 } elsif ({
4927 caption => 1, table => 1, tbody => 1,
4928 tfoot => 1, thead => 1, tr => 1, td => 1, th => 1,
4929 }->{$token->{tag_name}}) {
4930 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4931
4932 ## have an element in table scope
4933 my $i;
4934 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4935 my $node = $self->{open_elements}->[$_];
4936 if ($node->[1] eq $token->{tag_name}) {
4937 $i = $_;
4938 last INSCOPE;
4939 } elsif ({
4940 table => 1, html => 1,
4941 }->{$node->[1]}) {
4942 last INSCOPE;
4943 }
4944 } # INSCOPE
4945 unless (defined $i) {
4946 ## Ignore the token
4947 !!!next-token;
4948 redo B;
4949 }
4950
4951 ## As if </select>
4952 ## have an element in table scope
4953 undef $i;
4954 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4955 my $node = $self->{open_elements}->[$_];
4956 if ($node->[1] eq 'select') {
4957 $i = $_;
4958 last INSCOPE;
4959 } elsif ({
4960 table => 1, html => 1,
4961 }->{$node->[1]}) {
4962 last INSCOPE;
4963 }
4964 } # INSCOPE
4965 unless (defined $i) {
4966 !!!parse-error (type => 'unmatched end tag:select');
4967 ## Ignore the </select> token
4968 !!!next-token; ## TODO: ok?
4969 redo B;
4970 }
4971
4972 splice @{$self->{open_elements}}, $i;
4973
4974 $self->_reset_insertion_mode;
4975
4976 ## reprocess
4977 redo B;
4978 } else {
4979 #
4980 }
4981 } else {
4982 #
4983 }
4984
4985 !!!parse-error (type => 'in select:'.$token->{tag_name});
4986 ## Ignore the token
4987 !!!next-token;
4988 redo B;
4989 } elsif ($self->{insertion_mode} eq 'after body') {
4990 if ($token->{type} eq 'character') {
4991 if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
4992 ## As if in body
4993 $reconstruct_active_formatting_elements->($insert_to_current);
4994
4995 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
4996
4997 unless (length $token->{data}) {
4998 !!!next-token;
4999 redo B;
5000 }
5001 }
5002
5003 #
5004 !!!parse-error (type => 'after body:#'.$token->{type});
5005 } elsif ($token->{type} eq 'comment') {
5006 my $comment = $self->{document}->create_comment ($token->{data});
5007 $self->{open_elements}->[0]->[0]->append_child ($comment);
5008 !!!next-token;
5009 redo B;
5010 } elsif ($token->{type} eq 'start tag') {
5011 !!!parse-error (type => 'after body:'.$token->{tag_name});
5012 #
5013 } elsif ($token->{type} eq 'end tag') {
5014 if ($token->{tag_name} eq 'html') {
5015 if (defined $self->{inner_html_node}) {
5016 !!!parse-error (type => 'unmatched end tag:html');
5017 ## Ignore the token
5018 !!!next-token;
5019 redo B;
5020 } else {
5021 $phase = 'trailing end';
5022 !!!next-token;
5023 redo B;
5024 }
5025 } else {
5026 !!!parse-error (type => 'after body:/'.$token->{tag_name});
5027 }
5028 } else {
5029 !!!parse-error (type => 'after body:#'.$token->{type});
5030 }
5031
5032 $self->{insertion_mode} = 'in body';
5033 ## reprocess
5034 redo B;
5035 } elsif ($self->{insertion_mode} eq 'in frameset') {
5036 if ($token->{type} eq 'character') {
5037 if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
5038 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
5039
5040 unless (length $token->{data}) {
5041 !!!next-token;
5042 redo B;
5043 }
5044 }
5045
5046 #
5047 } elsif ($token->{type} eq 'comment') {
5048 my $comment = $self->{document}->create_comment ($token->{data});
5049 $self->{open_elements}->[-1]->[0]->append_child ($comment);
5050 !!!next-token;
5051 redo B;
5052 } elsif ($token->{type} eq 'start tag') {
5053 if ($token->{tag_name} eq 'frameset') {
5054 !!!insert-element ($token->{tag_name}, $token->{attributes});
5055 !!!next-token;
5056 redo B;
5057 } elsif ($token->{tag_name} eq 'frame') {
5058 !!!insert-element ($token->{tag_name}, $token->{attributes});
5059 pop @{$self->{open_elements}};
5060 !!!next-token;
5061 redo B;
5062 } elsif ($token->{tag_name} eq 'noframes') {
5063 $in_body->($insert_to_current);
5064 redo B;
5065 } else {
5066 #
5067 }
5068 } elsif ($token->{type} eq 'end tag') {
5069 if ($token->{tag_name} eq 'frameset') {
5070 if ($self->{open_elements}->[-1]->[1] eq 'html' and
5071 @{$self->{open_elements}} == 1) {
5072 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
5073 ## Ignore the token
5074 !!!next-token;
5075 } else {
5076 pop @{$self->{open_elements}};
5077 !!!next-token;
5078 }
5079
5080 ## if not inner_html and
5081 if ($self->{open_elements}->[-1]->[1] ne 'frameset') {
5082 $self->{insertion_mode} = 'after frameset';
5083 }
5084 redo B;
5085 } else {
5086 #
5087 }
5088 } else {
5089 #
5090 }
5091
5092 if (defined $token->{tag_name}) {
5093 !!!parse-error (type => 'in frameset:'.($token->{type} eq 'end tag' ? '/' : '').$token->{tag_name});
5094 } else {
5095 !!!parse-error (type => 'in frameset:#'.$token->{type});
5096 }
5097 ## Ignore the token
5098 !!!next-token;
5099 redo B;
5100 } elsif ($self->{insertion_mode} eq 'after frameset') {
5101 if ($token->{type} eq 'character') {
5102 if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
5103 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
5104
5105 unless (length $token->{data}) {
5106 !!!next-token;
5107 redo B;
5108 }
5109 }
5110
5111 #
5112 } elsif ($token->{type} eq 'comment') {
5113 my $comment = $self->{document}->create_comment ($token->{data});
5114 $self->{open_elements}->[-1]->[0]->append_child ($comment);
5115 !!!next-token;
5116 redo B;
5117 } elsif ($token->{type} eq 'start tag') {
5118 if ($token->{tag_name} eq 'noframes') {
5119 $in_body->($insert_to_current);
5120 redo B;
5121 } else {
5122 #
5123 }
5124 } elsif ($token->{type} eq 'end tag') {
5125 if ($token->{tag_name} eq 'html') {
5126 $phase = 'trailing end';
5127 !!!next-token;
5128 redo B;
5129 } else {
5130 #
5131 }
5132 } else {
5133 #
5134 }
5135
5136 if (defined $token->{tag_name}) {
5137 !!!parse-error (type => 'after frameset:'.($token->{tag_name} eq 'end tag' ? '/' : '').$token->{tag_name});
5138 } else {
5139 !!!parse-error (type => 'after frameset:#'.$token->{type});
5140 }
5141 ## Ignore the token
5142 !!!next-token;
5143 redo B;
5144
5145 ## ISSUE: An issue in spec there
5146 } else {
5147 die "$0: $self->{insertion_mode}: Unknown insertion mode";
5148 }
5149 }
5150 } elsif ($phase eq 'trailing end') {
5151 ## states in the main stage is preserved yet # MUST
5152
5153 if ($token->{type} eq 'DOCTYPE') {
5154 !!!parse-error (type => 'after html:#DOCTYPE');
5155 ## Ignore the token
5156 !!!next-token;
5157 redo B;
5158 } elsif ($token->{type} eq 'comment') {
5159 my $comment = $self->{document}->create_comment ($token->{data});
5160 $self->{document}->append_child ($comment);
5161 !!!next-token;
5162 redo B;
5163 } elsif ($token->{type} eq 'character') {
5164 if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
5165 my $data = $1;
5166 ## As if in the main phase.
5167 ## NOTE: The insertion mode in the main phase
5168 ## just before the phase has been changed to the trailing
5169 ## end phase is either "after body" or "after frameset".
5170 $reconstruct_active_formatting_elements->($insert_to_current)
5171 if $phase eq 'main';
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 $phase = 'main';
5183 ## reprocess
5184 redo B;
5185 } elsif ($token->{type} eq 'start tag' or
5186 $token->{type} eq 'end tag') {
5187 !!!parse-error (type => 'after html:'.($token->{type} eq 'end tag' ? '/' : '').$token->{tag_name});
5188 $phase = 'main';
5189 ## reprocess
5190 redo B;
5191 } elsif ($token->{type} eq 'end-of-file') {
5192 ## Stop parsing
5193 last B;
5194 } else {
5195 die "$0: $token->{type}: Unknown token";
5196 }
5197 }
5198 } # B
5199
5200 ## Stop parsing # MUST
5201
5202 ## TODO: script stuffs
5203 } # _tree_construct_main
5204
5205 sub set_inner_html ($$$) {
5206 my $class = shift;
5207 my $node = shift;
5208 my $s = \$_[0];
5209 my $onerror = $_[1];
5210
5211 my $nt = $node->node_type;
5212 if ($nt == 9) {
5213 # MUST
5214
5215 ## Step 1 # MUST
5216 ## TODO: If the document has an active parser, ...
5217 ## ISSUE: There is an issue in the spec.
5218
5219 ## Step 2 # MUST
5220 my @cn = @{$node->child_nodes};
5221 for (@cn) {
5222 $node->remove_child ($_);
5223 }
5224
5225 ## Step 3, 4, 5 # MUST
5226 $class->parse_string ($$s => $node, $onerror);
5227 } elsif ($nt == 1) {
5228 ## TODO: If non-html element
5229
5230 ## NOTE: Most of this code is copied from |parse_string|
5231
5232 ## Step 1 # MUST
5233 my $this_doc = $node->owner_document;
5234 my $doc = $this_doc->implementation->create_document;
5235 $doc->manakai_is_html (1);
5236 my $p = $class->new;
5237 $p->{document} = $doc;
5238
5239 ## Step 9 # MUST
5240 my $i = 0;
5241 my $line = 1;
5242 my $column = 0;
5243 $p->{set_next_input_character} = sub {
5244 my $self = shift;
5245
5246 pop @{$self->{prev_input_character}};
5247 unshift @{$self->{prev_input_character}}, $self->{next_input_character};
5248
5249 $self->{next_input_character} = -1 and return if $i >= length $$s;
5250 $self->{next_input_character} = ord substr $$s, $i++, 1;
5251 $column++;
5252
5253 if ($self->{next_input_character} == 0x000A) { # LF
5254 $line++;
5255 $column = 0;
5256 } elsif ($self->{next_input_character} == 0x000D) { # CR
5257 $i++ if substr ($$s, $i, 1) eq "\x0A";
5258 $self->{next_input_character} = 0x000A; # LF # MUST
5259 $line++;
5260 $column = 0;
5261 } elsif ($self->{next_input_character} > 0x10FFFF) {
5262 $self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
5263 } elsif ($self->{next_input_character} == 0x0000) { # NULL
5264 !!!parse-error (type => 'NULL');
5265 $self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
5266 }
5267 };
5268 $p->{prev_input_character} = [-1, -1, -1];
5269 $p->{next_input_character} = -1;
5270
5271 my $ponerror = $onerror || sub {
5272 my (%opt) = @_;
5273 warn "Parse error ($opt{type}) at line $opt{line} column $opt{column}\n";
5274 };
5275 $p->{parse_error} = sub {
5276 $ponerror->(@_, line => $line, column => $column);
5277 };
5278
5279 $p->_initialize_tokenizer;
5280 $p->_initialize_tree_constructor;
5281
5282 ## Step 2
5283 my $node_ln = $node->local_name;
5284 $p->{content_model_flag} = {
5285 title => 'RCDATA',
5286 textarea => 'RCDATA',
5287 style => 'CDATA',
5288 script => 'CDATA',
5289 xmp => 'CDATA',
5290 iframe => 'CDATA',
5291 noembed => 'CDATA',
5292 noframes => 'CDATA',
5293 noscript => 'CDATA',
5294 plaintext => 'PLAINTEXT',
5295 }->{$node_ln} || 'PCDATA';
5296 ## ISSUE: What is "the name of the element"? local name?
5297
5298 $p->{inner_html_node} = [$node, $node_ln];
5299
5300 ## Step 4
5301 my $root = $doc->create_element_ns
5302 ('http://www.w3.org/1999/xhtml', [undef, 'html']);
5303
5304 ## Step 5 # MUST
5305 $doc->append_child ($root);
5306
5307 ## Step 6 # MUST
5308 push @{$p->{open_elements}}, [$root, 'html'];
5309
5310 undef $p->{head_element};
5311
5312 ## Step 7 # MUST
5313 $p->_reset_insertion_mode;
5314
5315 ## Step 8 # MUST
5316 my $anode = $node;
5317 AN: while (defined $anode) {
5318 if ($anode->node_type == 1) {
5319 my $nsuri = $anode->namespace_uri;
5320 if (defined $nsuri and $nsuri eq 'http://www.w3.org/1999/xhtml') {
5321 if ($anode->local_name eq 'form') { ## TODO: case?
5322 $p->{form_element} = $anode;
5323 last AN;
5324 }
5325 }
5326 }
5327 $anode = $anode->parent_node;
5328 } # AN
5329
5330 ## Step 3 # MUST
5331 ## Step 10 # MUST
5332 {
5333 my $self = $p;
5334 !!!next-token;
5335 }
5336 $p->_tree_construction_main;
5337
5338 ## Step 11 # MUST
5339 my @cn = @{$node->child_nodes};
5340 for (@cn) {
5341 $node->remove_child ($_);
5342 }
5343 ## ISSUE: mutation events? read-only?
5344
5345 ## Step 12 # MUST
5346 @cn = @{$root->child_nodes};
5347 for (@cn) {
5348 $this_doc->adopt_node ($_);
5349 $node->append_child ($_);
5350 }
5351 ## ISSUE: mutation events?
5352
5353 $p->_terminate_tree_constructor;
5354 } else {
5355 die "$0: |set_inner_html| is not defined for node of type $nt";
5356 }
5357 } # set_inner_html
5358
5359 } # tree construction stage
5360
5361 sub get_inner_html ($$$) {
5362 my (undef, $node, $on_error) = @_;
5363
5364 ## Step 1
5365 my $s = '';
5366
5367 my $in_cdata;
5368 my $parent = $node;
5369 while (defined $parent) {
5370 if ($parent->node_type == 1 and
5371 $parent->namespace_uri eq 'http://www.w3.org/1999/xhtml' and
5372 {
5373 style => 1, script => 1, xmp => 1, iframe => 1,
5374 noembed => 1, noframes => 1, noscript => 1,
5375 }->{$parent->local_name}) { ## TODO: case thingy
5376 $in_cdata = 1;
5377 }
5378 $parent = $parent->parent_node;
5379 }
5380
5381 ## Step 2
5382 my @node = @{$node->child_nodes};
5383 C: while (@node) {
5384 my $child = shift @node;
5385 unless (ref $child) {
5386 if ($child eq 'cdata-out') {
5387 $in_cdata = 0;
5388 } else {
5389 $s .= $child; # end tag
5390 }
5391 next C;
5392 }
5393
5394 my $nt = $child->node_type;
5395 if ($nt == 1) { # Element
5396 my $tag_name = $child->tag_name; ## TODO: manakai_tag_name
5397 $s .= '<' . $tag_name;
5398 ## NOTE: Non-HTML case:
5399 ## <http://permalink.gmane.org/gmane.org.w3c.whatwg.discuss/11191>
5400
5401 my @attrs = @{$child->attributes}; # sort order MUST be stable
5402 for my $attr (@attrs) { # order is implementation dependent
5403 my $attr_name = $attr->name; ## TODO: manakai_name
5404 $s .= ' ' . $attr_name . '="';
5405 my $attr_value = $attr->value;
5406 ## escape
5407 $attr_value =~ s/&/&amp;/g;
5408 $attr_value =~ s/</&lt;/g;
5409 $attr_value =~ s/>/&gt;/g;
5410 $attr_value =~ s/"/&quot;/g;
5411 $s .= $attr_value . '"';
5412 }
5413 $s .= '>';
5414
5415 next C if {
5416 area => 1, base => 1, basefont => 1, bgsound => 1,
5417 br => 1, col => 1, embed => 1, frame => 1, hr => 1,
5418 img => 1, input => 1, link => 1, meta => 1, param => 1,
5419 spacer => 1, wbr => 1,
5420 }->{$tag_name};
5421
5422 $s .= "\x0A" if $tag_name eq 'pre' or $tag_name eq 'textarea';
5423
5424 if (not $in_cdata and {
5425 style => 1, script => 1, xmp => 1, iframe => 1,
5426 noembed => 1, noframes => 1, noscript => 1,
5427 plaintext => 1,
5428 }->{$tag_name}) {
5429 unshift @node, 'cdata-out';
5430 $in_cdata = 1;
5431 }
5432
5433 unshift @node, @{$child->child_nodes}, '</' . $tag_name . '>';
5434 } elsif ($nt == 3 or $nt == 4) {
5435 if ($in_cdata) {
5436 $s .= $child->data;
5437 } else {
5438 my $value = $child->data;
5439 $value =~ s/&/&amp;/g;
5440 $value =~ s/</&lt;/g;
5441 $value =~ s/>/&gt;/g;
5442 $value =~ s/"/&quot;/g;
5443 $s .= $value;
5444 }
5445 } elsif ($nt == 8) {
5446 $s .= '<!--' . $child->data . '-->';
5447 } elsif ($nt == 10) {
5448 $s .= '<!DOCTYPE ' . $child->name . '>';
5449 } elsif ($nt == 5) { # entrefs
5450 push @node, @{$child->child_nodes};
5451 } else {
5452 $on_error->($child) if defined $on_error;
5453 }
5454 ## ISSUE: This code does not support PIs.
5455 } # C
5456
5457 ## Step 3
5458 return \$s;
5459 } # get_inner_html
5460
5461 1;
5462 # $Date: 2007/07/01 06:18:57 $

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24