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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.12 - (hide annotations) (download) (as text)
Sat Jun 23 04:38:50 2007 UTC (19 years, 3 months ago) by wakaba
Branch: MAIN
Changes since 1.11: +35 -4 lines
File MIME type: application/x-wais-source
++ whatpm/t/ChangeLog	23 Jun 2007 04:36:28 -0000
	* tree-test-1.dat: New test for HTML5 revision 884 (</form>
	try to close its children).

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

++ whatpm/Whatpm/ChangeLog	23 Jun 2007 04:38:44 -0000
	* HTML.pm.src: HTML5 revision 884 (</form> don't close
	the form element if a descendant element without implied end tag has
	still been open).

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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24