/[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.23 - (hide annotations) (download) (as text)
Sat Jun 23 16:01:36 2007 UTC (19 years, 3 months ago) by wakaba
Branch: MAIN
Changes since 1.22: +119 -90 lines
File MIME type: application/x-wais-source
++ whatpm/t/ChangeLog	23 Jun 2007 15:56:20 -0000
2007-06-24  Wakaba  <wakaba@suika.fam.cx>

	* tree-test-1.dat: Tests for CDATA/RCDATA innerHTML
	with matching end tag are added (HTML5 revision 923).
	A test for h2 in h1 is added (HTML5 revision 926).

	* tree-test-1.dat: Tests for |<isindex>| are added (HTML5
	revision 920).
++ whatpm/Whatpm/ChangeLog	23 Jun 2007 15:54:57 -0000
2007-06-24  Wakaba  <wakaba@suika.fam.cx>

	* HTML.pm.src: HTML5 revision 923 (matching end tag in CDATA
	or RCDATA in fragment parsing mode).  HTML5 revision
	924 (<!--> and <!--->).  HTML5 revision 926 (hn in hn).

2007-06-23  Wakaba  <wakaba@suika.fam.cx>

	* HTML.pm.src (get_inner_html): HTML5 revision 922 (inner_html
	for <pre> and <textarea>).

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24