/[suikacvs]/markup/html/whatpm/Whatpm/HTML.pm
Suika

Contents of /markup/html/whatpm/Whatpm/HTML.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.40 - (hide annotations) (download)
Sat Jul 21 04:55:20 2007 UTC (19 years ago) by wakaba
Branch: MAIN
Changes since 1.39: +3 -3 lines
++ whatpm/Whatpm/ChangeLog	21 Jul 2007 04:51:33 -0000
2007-07-21  Wakaba  <wakaba@suika.fam.cx>

	* HTML.pm.src: Add the name of the attribute
	to the "duplicate attribute" error.

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24