/[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.57 - (hide annotations) (download)
Sat Aug 11 06:53:38 2007 UTC (18 years, 11 months ago) by wakaba
Branch: MAIN
Changes since 1.56: +164 -156 lines
++ whatpm/Whatpm/ChangeLog	11 Aug 2007 06:53:35 -0000
	* HTML.pm.src: Token types are now represented in number.

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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24