/[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.34 - (hide annotations) (download) (as text)
Mon Jul 16 01:52:27 2007 UTC (19 years, 2 months ago) by wakaba
Branch: MAIN
Changes since 1.33: +57 -3 lines
File MIME type: application/x-wais-source
++ whatpm/Whatpm/ChangeLog	16 Jul 2007 01:52:23 -0000
2007-07-16  Wakaba  <wakaba@suika.fam.cx>

	* HTML.pm.src: |meta| charset declaration extraction
	implemented (but changing the encoding is not yet:-).

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24