/[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.22 - (show annotations) (download) (as text)
Sat Jun 23 14:55:45 2007 UTC (19 years, 3 months ago) by wakaba
Branch: MAIN
Changes since 1.21: +19 -8 lines
File MIME type: application/x-wais-source
++ whatpm/t/ChangeLog	23 Jun 2007 14:55:20 -0000
	* tree-test-1.dat: Tests for |<isindex>| are added.

2007-06-23  Wakaba  <wakaba@suika.fam.cx>

++ whatpm/Whatpm/ChangeLog	23 Jun 2007 14:55:42 -0000
	* HTML.pm.src: HTML5 revision 920 (<isindex>).

2007-06-23  Wakaba  <wakaba@suika.fam.cx>

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24