/[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.64 - (hide annotations) (download) (as text)
Sun Nov 11 08:39:42 2007 UTC (18 years, 10 months ago) by wakaba
Branch: MAIN
Changes since 1.63: +12 -7 lines
File MIME type: application/x-wais-source
++ whatpm/Whatpm/ChangeLog	11 Nov 2007 08:39:39 -0000
	* HTML.pm.src (parse_char_string): Set |inner_encoding|
	attribute if possible.

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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24