/[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.32 - (hide annotations) (download) (as text)
Sun Jul 1 06:18:57 2007 UTC (17 years, 4 months ago) by wakaba
Branch: MAIN
Changes since 1.31: +7 -3 lines
File MIME type: application/x-wais-source
++ whatpm/t/ChangeLog	1 Jul 2007 06:18:53 -0000
	* tree-test-1.dat: New tests for unmatched end tags.

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

++ whatpm/Whatpm/ChangeLog	1 Jul 2007 06:18:08 -0000
	* HTML.pm.src: Report correct error message
	for |<body></div></body>|.

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

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24