/[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.54 - (hide annotations) (download) (as text)
Sat Aug 11 06:37:12 2007 UTC (17 years, 2 months ago) by wakaba
Branch: MAIN
Changes since 1.53: +212 -179 lines
File MIME type: application/x-wais-source
++ whatpm/Whatpm/ChangeLog	11 Aug 2007 06:36:59 -0000
	* ContentType.pm (SEE ALSO): Updated.

	* HTML.pm.src: Insertion modes are now represented in number.

2007-08-11  Wakaba  <wakaba@suika.fam.cx>

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24