/[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.41 - (hide annotations) (download) (as text)
Sat Jul 21 06:04:07 2007 UTC (17 years, 3 months ago) by wakaba
Branch: MAIN
Changes since 1.40: +4 -2 lines
File MIME type: application/x-wais-source
++ whatpm/t/ChangeLog	21 Jul 2007 06:04:01 -0000
	* tree-test-1.dat: More tests for ignoring some tokens
	are added.

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

++ whatpm/Whatpm/ChangeLog	21 Jul 2007 06:03:37 -0000
	* HTML.pm.src: Two |!!!next-token|s were missing.

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

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24