/[suikacvs]/markup/html/whatpm/Whatpm/HTML.pm.src
Suika

Contents of /markup/html/whatpm/Whatpm/HTML.pm.src

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.39 - (hide annotations) (download) (as text)
Sat Jul 21 04:55:20 2007 UTC (17 years, 3 months ago) by wakaba
Branch: MAIN
Changes since 1.38: +3 -3 lines
File MIME type: application/x-wais-source
++ whatpm/Whatpm/ChangeLog	21 Jul 2007 04:51:33 -0000
2007-07-21  Wakaba  <wakaba@suika.fam.cx>

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

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24