/[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.29 - (hide annotations) (download) (as text)
Mon Jun 25 11:05:57 2007 UTC (17 years, 4 months ago) by wakaba
Branch: MAIN
Changes since 1.28: +16 -9 lines
File MIME type: application/x-wais-source
++ whatpm/t/ChangeLog	25 Jun 2007 11:03:58 -0000
	* tree-test-1.dat: The tests using |#document-fragment|
	are move to |tree-test-2.dat|.

	* tree-test-2.dat: New test data.  In addition to
	tests from |tree-test-1.dat|, two tests
	for |</table>| are added.

	* HTML-tree.t: |tree-test-2.dat| is added.

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

++ whatpm/Whatpm/ChangeLog	25 Jun 2007 11:05:50 -0000
	* HTML.pm.src (_reset_insertion_mode): Interpretation
	of Step 3 has been changed.

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

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24