/[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.4 - (hide annotations) (download) (as text)
Fri May 4 09:16:04 2007 UTC (19 years, 5 months ago) by wakaba
Branch: MAIN
Changes since 1.3: +65 -12 lines
File MIME type: application/x-wais-source
++ whatpm/t/ChangeLog	4 May 2007 09:15:43 -0000
2007-05-03  Wakaba  <wakaba@suika.fam.cx>

	* tokenizer-test-1.test: Incorrect DOCTYPE testa
	are added.

	* tree-test-1.dat: |innerHTML| tests are added.

2007-05-03  Wakaba  <wakaba@suika.fam.cx>

	* LICENSE: New document.

++ whatpm/Whatpm/ChangeLog	4 May 2007 09:13:06 -0000
2007-05-04  Wakaba  <wakaba@suika.fam.cx>

	* NanoDOM.pm (manakai_parent_element,
	document_element, manakai_local_name,
	manakai_element_type_match): New method.

2007-05-03  Wakaba  <wakaba@suika.fam.cx>

	* HTML.pm.src: Replace decimal and hexadecimal numeric
	entities in C1 range using Windows-1252 mapping.  Bare LF
	did not count as new line for error reporting.

1 wakaba 1.2 package Whatpm::HTML;
2 wakaba 1.1 use strict;
3 wakaba 1.4 our $VERSION=do{my @r=(q$Revision: 1.3 $=~/\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     ## <http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2006-December/thread.html#8562>
284     my $c1_entity_char = {
285     128, 8364,
286     129, 65533,
287     130, 8218,
288     131, 402,
289     132, 8222,
290     133, 8230,
291     134, 8224,
292     135, 8225,
293     136, 710,
294     137, 8240,
295     138, 352,
296     139, 8249,
297     140, 338,
298     141, 65533,
299     142, 381,
300     143, 65533,
301     144, 65533,
302     145, 8216,
303     146, 8217,
304     147, 8220,
305     148, 8221,
306     149, 8226,
307     150, 8211,
308     151, 8212,
309     152, 732,
310     153, 8482,
311     154, 353,
312     155, 8250,
313     156, 339,
314     157, 65533,
315     158, 382,
316     159, 376,
317     }; # $c1_entity_char
318 wakaba 1.1
319     my $special_category = {
320     address => 1, area => 1, base => 1, basefont => 1, bgsound => 1,
321     blockquote => 1, body => 1, br => 1, center => 1, col => 1, colgroup => 1,
322     dd => 1, dir => 1, div => 1, dl => 1, dt => 1, embed => 1, fieldset => 1,
323     form => 1, frame => 1, frameset => 1, h1 => 1, h2 => 1, h3 => 1,
324     h4 => 1, h5 => 1, h6 => 1, head => 1, hr => 1, iframe => 1, image => 1,
325     img => 1, input => 1, isindex => 1, li => 1, link => 1, listing => 1,
326     menu => 1, meta => 1, noembed => 1, noframes => 1, noscript => 1,
327     ol => 1, optgroup => 1, option => 1, p => 1, param => 1, plaintext => 1,
328     pre => 1, script => 1, select => 1, spacer => 1, style => 1, tbody => 1,
329     textarea => 1, tfoot => 1, thead => 1, title => 1, tr => 1, ul => 1, wbr => 1,
330     };
331     my $scoping_category = {
332     button => 1, caption => 1, html => 1, marquee => 1, object => 1,
333     table => 1, td => 1, th => 1,
334     };
335     my $formatting_category = {
336     a => 1, b => 1, big => 1, em => 1, font => 1, i => 1, nobr => 1,
337     s => 1, small => 1, strile => 1, strong => 1, tt => 1, u => 1,
338     };
339     # $phrasing_category: all other elements
340    
341     sub parse_string ($$$;$) {
342     my $self = shift->new;
343     my $s = \$_[0];
344     $self->{document} = $_[1];
345    
346 wakaba 1.3 ## NOTE: |set_inner_html| copies most of this method's code
347    
348 wakaba 1.1 my $i = 0;
349 wakaba 1.3 my $line = 1;
350     my $column = 0;
351 wakaba 1.1 $self->{set_next_input_character} = sub {
352     my $self = shift;
353     $self->{next_input_character} = -1 and return if $i >= length $$s;
354     $self->{next_input_character} = ord substr $$s, $i++, 1;
355 wakaba 1.3 $column++;
356 wakaba 1.1
357 wakaba 1.4 if ($self->{next_input_character} == 0x000A) { # LF
358     $line++;
359     $column = 0;
360     } elsif ($self->{next_input_character} == 0x000D) { # CR
361 wakaba 1.1 if ($i >= length $$s) {
362     #
363     } else {
364     my $next_char = ord substr $$s, $i++, 1;
365     if ($next_char == 0x000A) { # LF
366     #
367     } else {
368     push @{$self->{char}}, $next_char;
369     }
370     }
371     $self->{next_input_character} = 0x000A; # LF # MUST
372 wakaba 1.3 $line++;
373 wakaba 1.4 $column = 0;
374 wakaba 1.1 } elsif ($self->{next_input_character} > 0x10FFFF) {
375     $self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
376     } elsif ($self->{next_input_character} == 0x0000) { # NULL
377     $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     ## NOTE: <http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2006-December/thread.html#8562>
1542     ## ISSUE: Not in the spec yet; parse error?
1543     $num = $c1_entity_char->{$num};
1544 wakaba 1.1 }
1545    
1546     return {type => 'character', data => chr $num};
1547     } # X
1548     } elsif (0x0030 <= $self->{next_input_character} and
1549     $self->{next_input_character} <= 0x0039) { # 0..9
1550     my $code = $self->{next_input_character} - 0x0030;
1551     !!!next-input-character;
1552    
1553     while (0x0030 <= $self->{next_input_character} and
1554     $self->{next_input_character} <= 0x0039) { # 0..9
1555     $code *= 10;
1556     $code += $self->{next_input_character} - 0x0030;
1557    
1558     !!!next-input-character;
1559     }
1560    
1561     if ($self->{next_input_character} == 0x003B) { # ;
1562     !!!next-input-character;
1563     } else {
1564 wakaba 1.3 !!!parse-error (type => 'no refc');
1565 wakaba 1.1 }
1566    
1567     ## TODO: check the definition for |a valid Unicode character|.
1568     if ($code > 1114111 or $code == 0) {
1569     $code = 0xFFFD; # REPLACEMENT CHARACTER
1570     ## ISSUE: Why this is not an error?
1571 wakaba 1.4 } elsif (0x80 <= $code and $code <= 0x9F) {
1572     ## NOTE: <http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2006-December/thread.html#8562>
1573     ## ISSUE: Not in the spec yet; parse error?
1574     $code = $c1_entity_char->{$code};
1575 wakaba 1.1 }
1576    
1577     return {type => 'character', data => chr $code};
1578     } else {
1579 wakaba 1.3 !!!parse-error (type => 'bare nero');
1580 wakaba 1.1 !!!back-next-input-character ($self->{next_input_character});
1581     $self->{next_input_character} = 0x0023; # #
1582     return undef;
1583     }
1584     } elsif ((0x0041 <= $self->{next_input_character} and
1585     $self->{next_input_character} <= 0x005A) or
1586     (0x0061 <= $self->{next_input_character} and
1587     $self->{next_input_character} <= 0x007A)) {
1588     my $entity_name = chr $self->{next_input_character};
1589     !!!next-input-character;
1590    
1591     my $value = $entity_name;
1592     my $match;
1593    
1594     while (length $entity_name < 10 and
1595     ## NOTE: Some number greater than the maximum length of entity name
1596     ((0x0041 <= $self->{next_input_character} and
1597     $self->{next_input_character} <= 0x005A) or
1598     (0x0061 <= $self->{next_input_character} and
1599     $self->{next_input_character} <= 0x007A) or
1600     (0x0030 <= $self->{next_input_character} and
1601     $self->{next_input_character} <= 0x0039))) {
1602     $entity_name .= chr $self->{next_input_character};
1603     if (defined $entity_char->{$entity_name}) {
1604     $value = $entity_char->{$entity_name};
1605     $match = 1;
1606     } else {
1607     $value .= chr $self->{next_input_character};
1608     }
1609     !!!next-input-character;
1610     }
1611    
1612     if ($match) {
1613     if ($self->{next_input_character} == 0x003B) { # ;
1614     !!!next-input-character;
1615     } else {
1616 wakaba 1.3 !!!parse-error (type => 'refc');
1617 wakaba 1.1 }
1618    
1619     return {type => 'character', data => $value};
1620     } else {
1621 wakaba 1.3 !!!parse-error (type => 'bare ero');
1622 wakaba 1.1 ## NOTE: No characters are consumed in the spec.
1623     !!!back-token ({type => 'character', data => $value});
1624     return undef;
1625     }
1626     } else {
1627     ## no characters are consumed
1628 wakaba 1.3 !!!parse-error (type => 'bare ero');
1629 wakaba 1.1 return undef;
1630     }
1631     } # _tokenize_attempt_to_consume_an_entity
1632    
1633     sub _initialize_tree_constructor ($) {
1634     my $self = shift;
1635     ## NOTE: $self->{document} MUST be specified before this method is called
1636     $self->{document}->strict_error_checking (0);
1637     ## TODO: Turn mutation events off # MUST
1638     ## TODO: Turn loose Document option (manakai extension) on
1639     ## TODO: Mark the Document as an HTML document # MUST
1640     } # _initialize_tree_constructor
1641    
1642     sub _terminate_tree_constructor ($) {
1643     my $self = shift;
1644     $self->{document}->strict_error_checking (1);
1645     ## TODO: Turn mutation events on
1646     } # _terminate_tree_constructor
1647    
1648     ## ISSUE: Should append_child (for example) in script executed in tree construction stage fire mutation events?
1649    
1650 wakaba 1.3 { # tree construction stage
1651     my $token;
1652    
1653 wakaba 1.1 sub _construct_tree ($) {
1654     my ($self) = @_;
1655    
1656     ## When an interactive UA render the $self->{document} available
1657     ## to the user, or when it begin accepting user input, are
1658     ## not defined.
1659    
1660     ## Append a character: collect it and all subsequent consecutive
1661     ## characters and insert one Text node whose data is concatenation
1662     ## of all those characters. # MUST
1663    
1664     !!!next-token;
1665    
1666 wakaba 1.3 $self->{insertion_mode} = 'before head';
1667     undef $self->{form_element};
1668     undef $self->{head_element};
1669     $self->{open_elements} = [];
1670     undef $self->{inner_html_node};
1671    
1672     $self->_tree_construction_initial; # MUST
1673     $self->_tree_construction_root_element;
1674     $self->_tree_construction_main;
1675     } # _construct_tree
1676    
1677     sub _tree_construction_initial ($) {
1678     my $self = shift;
1679     B: {
1680     if ($token->{type} eq 'DOCTYPE') {
1681     if ($token->{error}) {
1682     ## ISSUE: Spec currently left this case undefined.
1683     !!!parse-error (type => 'bogus DOCTYPE');
1684     }
1685     my $doctype = $self->{document}->create_document_type_definition
1686     ($token->{name});
1687     $self->{document}->append_child ($doctype);
1688     #$phase = 'root element';
1689     !!!next-token;
1690     #redo B;
1691     return;
1692     } elsif ({
1693     comment => 1,
1694     'start tag' => 1,
1695     'end tag' => 1,
1696     'end-of-file' => 1,
1697     }->{$token->{type}}) {
1698     ## ISSUE: Spec currently left this case undefined.
1699     !!!parse-error (type => 'missing DOCTYPE');
1700     #$phase = 'root element';
1701     ## reprocess
1702     #redo B;
1703     return;
1704     } elsif ($token->{type} eq 'character') {
1705     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
1706     $self->{document}->manakai_append_text ($1);
1707     ## ISSUE: DOM3 Core does not allow Document > Text
1708     unless (length $token->{data}) {
1709     ## Stay in the phase
1710     !!!next-token;
1711     redo B;
1712     }
1713     }
1714     ## ISSUE: Spec currently left this case undefined.
1715     !!!parse-error (type => 'missing DOCTYPE');
1716     #$phase = 'root element';
1717     ## reprocess
1718     #redo B;
1719     return;
1720     } else {
1721     die "$0: $token->{type}: Unknown token";
1722     }
1723     } # B
1724     } # _tree_construction_initial
1725    
1726     sub _tree_construction_root_element ($) {
1727     my $self = shift;
1728    
1729     B: {
1730     if ($token->{type} eq 'DOCTYPE') {
1731     !!!parse-error (type => 'in html:#DOCTYPE');
1732     ## Ignore the token
1733     ## Stay in the phase
1734     !!!next-token;
1735     redo B;
1736     } elsif ($token->{type} eq 'comment') {
1737     my $comment = $self->{document}->create_comment ($token->{data});
1738     $self->{document}->append_child ($comment);
1739     ## Stay in the phase
1740     !!!next-token;
1741     redo B;
1742     } elsif ($token->{type} eq 'character') {
1743     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
1744     $self->{document}->manakai_append_text ($1);
1745     ## ISSUE: DOM3 Core does not allow Document > Text
1746     unless (length $token->{data}) {
1747     ## Stay in the phase
1748     !!!next-token;
1749     redo B;
1750     }
1751     }
1752     #
1753     } elsif ({
1754     'start tag' => 1,
1755     'end tag' => 1,
1756     'end-of-file' => 1,
1757     }->{$token->{type}}) {
1758     ## ISSUE: There is an issue in the spec
1759     #
1760     } else {
1761     die "$0: $token->{type}: Unknown token";
1762     }
1763     my $root_element; !!!create-element ($root_element, 'html');
1764     $self->{document}->append_child ($root_element);
1765     push @{$self->{open_elements}}, [$root_element, 'html'];
1766     #$phase = 'main';
1767     ## reprocess
1768     #redo B;
1769     return;
1770     } # B
1771     } # _tree_construction_root_element
1772    
1773     sub _reset_insertion_mode ($) {
1774     my $self = shift;
1775    
1776     ## Step 1
1777     my $last;
1778    
1779     ## Step 2
1780     my $i = -1;
1781     my $node = $self->{open_elements}->[$i];
1782    
1783     ## Step 3
1784     S3: {
1785     $last = 1 if $self->{open_elements}->[0]->[0] eq $node->[0];
1786     if (defined $self->{inner_html_node}) {
1787     if ($self->{inner_html_node}->[1] eq 'td' or
1788     $self->{inner_html_node}->[1] eq 'th') {
1789     #
1790     } else {
1791     $node = $self->{inner_html_node};
1792     }
1793     }
1794    
1795     ## Step 4..13
1796     my $new_mode = {
1797     select => 'in select',
1798     td => 'in cell',
1799     th => 'in cell',
1800     tr => 'in row',
1801     tbody => 'in table body',
1802     thead => 'in table head',
1803     tfoot => 'in table foot',
1804     caption => 'in caption',
1805     colgroup => 'in column group',
1806     table => 'in table',
1807     head => 'in body', # not in head!
1808     body => 'in body',
1809     frameset => 'in frameset',
1810     }->{$node->[1]};
1811     $self->{insertion_mode} = $new_mode and return if defined $new_mode;
1812    
1813     ## Step 14
1814     if ($node->[1] eq 'html') {
1815     unless (defined $self->{head_element}) {
1816     $self->{insertion_mode} = 'before head';
1817     } else {
1818     $self->{insertion_mode} = 'after head';
1819     }
1820     return;
1821     }
1822    
1823     ## Step 15
1824     $self->{insertion_mode} = 'in body' and return if $last;
1825    
1826     ## Step 16
1827     $i--;
1828     $node = $self->{open_elements}->[$i];
1829    
1830     ## Step 17
1831     redo S3;
1832     } # S3
1833     } # _reset_insertion_mode
1834    
1835     sub _tree_construction_main ($) {
1836     my $self = shift;
1837    
1838     my $phase = 'main';
1839 wakaba 1.1
1840     my $active_formatting_elements = [];
1841    
1842     my $reconstruct_active_formatting_elements = sub { # MUST
1843     my $insert = shift;
1844    
1845     ## Step 1
1846     return unless @$active_formatting_elements;
1847    
1848     ## Step 3
1849     my $i = -1;
1850     my $entry = $active_formatting_elements->[$i];
1851    
1852     ## Step 2
1853     return if $entry->[0] eq '#marker';
1854 wakaba 1.3 for (@{$self->{open_elements}}) {
1855 wakaba 1.1 if ($entry->[0] eq $_->[0]) {
1856     return;
1857     }
1858     }
1859    
1860     S4: {
1861     ## Step 4
1862     last S4 if $active_formatting_elements->[0]->[0] eq $entry->[0];
1863    
1864     ## Step 5
1865     $i--;
1866     $entry = $active_formatting_elements->[$i];
1867    
1868     ## Step 6
1869     if ($entry->[0] eq '#marker') {
1870     #
1871     } else {
1872     my $in_open_elements;
1873 wakaba 1.3 OE: for (@{$self->{open_elements}}) {
1874 wakaba 1.1 if ($entry->[0] eq $_->[0]) {
1875     $in_open_elements = 1;
1876     last OE;
1877     }
1878     }
1879     if ($in_open_elements) {
1880     #
1881     } else {
1882     redo S4;
1883     }
1884     }
1885    
1886     ## Step 7
1887     $i++;
1888     $entry = $active_formatting_elements->[$i];
1889     } # S4
1890    
1891     S7: {
1892     ## Step 8
1893     my $clone = [$entry->[0]->clone_node (0), $entry->[1]];
1894    
1895     ## Step 9
1896     $insert->($clone->[0]);
1897 wakaba 1.3 push @{$self->{open_elements}}, $clone;
1898 wakaba 1.1
1899     ## Step 10
1900 wakaba 1.3 $active_formatting_elements->[$i] = $self->{open_elements}->[-1];
1901 wakaba 1.1
1902     ## Step 11
1903     unless ($clone->[0] eq $active_formatting_elements->[-1]->[0]) {
1904     ## Step 7'
1905     $i++;
1906     $entry = $active_formatting_elements->[$i];
1907    
1908     redo S7;
1909     }
1910     } # S7
1911     }; # $reconstruct_active_formatting_elements
1912    
1913     my $clear_up_to_marker = sub {
1914     for (reverse 0..$#$active_formatting_elements) {
1915     if ($active_formatting_elements->[$_]->[0] eq '#marker') {
1916     splice @$active_formatting_elements, $_;
1917     return;
1918     }
1919     }
1920     }; # $clear_up_to_marker
1921    
1922     my $style_start_tag = sub {
1923     my $style_el; !!!create-element ($style_el, 'style');
1924 wakaba 1.3 ## $self->{insertion_mode} eq 'in head' and ... (always true)
1925     (($self->{insertion_mode} eq 'in head' and defined $self->{head_element})
1926     ? $self->{head_element} : $self->{open_elements}->[-1]->[0])
1927 wakaba 1.1 ->append_child ($style_el);
1928     $self->{content_model_flag} = 'CDATA';
1929    
1930     my $text = '';
1931     !!!next-token;
1932     while ($token->{type} eq 'character') {
1933     $text .= $token->{data};
1934     !!!next-token;
1935     } # stop if non-character token or tokenizer stops tokenising
1936     if (length $text) {
1937     $style_el->manakai_append_text ($text);
1938     }
1939    
1940     $self->{content_model_flag} = 'PCDATA';
1941    
1942     if ($token->{type} eq 'end tag' and $token->{tag_name} eq 'style') {
1943     ## Ignore the token
1944     } else {
1945 wakaba 1.3 !!!parse-error (type => 'in CDATA:#'.$token->{type});
1946 wakaba 1.1 ## ISSUE: And ignore?
1947     }
1948     !!!next-token;
1949     }; # $style_start_tag
1950    
1951     my $script_start_tag = sub {
1952     my $script_el;
1953     !!!create-element ($script_el, 'script', $token->{attributes});
1954     ## TODO: mark as "parser-inserted"
1955    
1956     $self->{content_model_flag} = 'CDATA';
1957    
1958     my $text = '';
1959     !!!next-token;
1960     while ($token->{type} eq 'character') {
1961     $text .= $token->{data};
1962     !!!next-token;
1963     } # stop if non-character token or tokenizer stops tokenising
1964     if (length $text) {
1965     $script_el->manakai_append_text ($text);
1966     }
1967    
1968     $self->{content_model_flag} = 'PCDATA';
1969    
1970     if ($token->{type} eq 'end tag' and
1971     $token->{tag_name} eq 'script') {
1972     ## Ignore the token
1973     } else {
1974 wakaba 1.3 !!!parse-error (type => 'in CDATA:#'.$token->{type});
1975 wakaba 1.1 ## ISSUE: And ignore?
1976     ## TODO: mark as "already executed"
1977     }
1978    
1979 wakaba 1.3 if (defined $self->{inner_html_node}) {
1980     ## TODO: mark as "already executed"
1981     } else {
1982 wakaba 1.1 ## TODO: $old_insertion_point = current insertion point
1983     ## TODO: insertion point = just before the next input character
1984    
1985 wakaba 1.3 (($self->{insertion_mode} eq 'in head' and defined $self->{head_element})
1986     ? $self->{head_element} : $self->{open_elements}->[-1]->[0])->append_child ($script_el);
1987 wakaba 1.1
1988     ## TODO: insertion point = $old_insertion_point (might be "undefined")
1989    
1990     ## TODO: if there is a script that will execute as soon as the parser resume, then...
1991     }
1992    
1993     !!!next-token;
1994     }; # $script_start_tag
1995    
1996     my $formatting_end_tag = sub {
1997     my $tag_name = shift;
1998    
1999     FET: {
2000     ## Step 1
2001     my $formatting_element;
2002     my $formatting_element_i_in_active;
2003     AFE: for (reverse 0..$#$active_formatting_elements) {
2004     if ($active_formatting_elements->[$_]->[1] eq $tag_name) {
2005     $formatting_element = $active_formatting_elements->[$_];
2006     $formatting_element_i_in_active = $_;
2007     last AFE;
2008     } elsif ($active_formatting_elements->[$_]->[0] eq '#marker') {
2009     last AFE;
2010     }
2011     } # AFE
2012     unless (defined $formatting_element) {
2013 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$tag_name);
2014 wakaba 1.1 ## Ignore the token
2015     !!!next-token;
2016     return;
2017     }
2018     ## has an element in scope
2019     my $in_scope = 1;
2020     my $formatting_element_i_in_open;
2021 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
2022     my $node = $self->{open_elements}->[$_];
2023 wakaba 1.1 if ($node->[0] eq $formatting_element->[0]) {
2024     if ($in_scope) {
2025     $formatting_element_i_in_open = $_;
2026     last INSCOPE;
2027     } else { # in open elements but not in scope
2028 wakaba 1.4 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
2029 wakaba 1.1 ## Ignore the token
2030     !!!next-token;
2031     return;
2032     }
2033     } elsif ({
2034     table => 1, caption => 1, td => 1, th => 1,
2035     button => 1, marquee => 1, object => 1, html => 1,
2036     }->{$node->[1]}) {
2037     $in_scope = 0;
2038     }
2039     } # INSCOPE
2040     unless (defined $formatting_element_i_in_open) {
2041 wakaba 1.4 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
2042 wakaba 1.1 pop @$active_formatting_elements; # $formatting_element
2043     !!!next-token; ## TODO: ok?
2044     return;
2045     }
2046 wakaba 1.3 if (not $self->{open_elements}->[-1]->[0] eq $formatting_element->[0]) {
2047 wakaba 1.4 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
2048 wakaba 1.1 }
2049    
2050     ## Step 2
2051     my $furthest_block;
2052     my $furthest_block_i_in_open;
2053 wakaba 1.3 OE: for (reverse 0..$#{$self->{open_elements}}) {
2054     my $node = $self->{open_elements}->[$_];
2055 wakaba 1.1 if (not $formatting_category->{$node->[1]} and
2056     #not $phrasing_category->{$node->[1]} and
2057     ($special_category->{$node->[1]} or
2058     $scoping_category->{$node->[1]})) {
2059     $furthest_block = $node;
2060     $furthest_block_i_in_open = $_;
2061     } elsif ($node->[0] eq $formatting_element->[0]) {
2062     last OE;
2063     }
2064     } # OE
2065    
2066     ## Step 3
2067     unless (defined $furthest_block) { # MUST
2068 wakaba 1.3 splice @{$self->{open_elements}}, $formatting_element_i_in_open;
2069 wakaba 1.1 splice @$active_formatting_elements, $formatting_element_i_in_active, 1;
2070     !!!next-token;
2071     return;
2072     }
2073    
2074     ## Step 4
2075 wakaba 1.3 my $common_ancestor_node = $self->{open_elements}->[$formatting_element_i_in_open - 1];
2076 wakaba 1.1
2077     ## Step 5
2078     my $furthest_block_parent = $furthest_block->[0]->parent_node;
2079     if (defined $furthest_block_parent) {
2080     $furthest_block_parent->remove_child ($furthest_block->[0]);
2081     }
2082    
2083     ## Step 6
2084     my $bookmark_prev_el
2085     = $active_formatting_elements->[$formatting_element_i_in_active - 1]
2086     ->[0];
2087    
2088     ## Step 7
2089     my $node = $furthest_block;
2090     my $node_i_in_open = $furthest_block_i_in_open;
2091     my $last_node = $furthest_block;
2092     S7: {
2093     ## Step 1
2094     $node_i_in_open--;
2095 wakaba 1.3 $node = $self->{open_elements}->[$node_i_in_open];
2096 wakaba 1.1
2097     ## Step 2
2098     my $node_i_in_active;
2099     S7S2: {
2100     for (reverse 0..$#$active_formatting_elements) {
2101     if ($active_formatting_elements->[$_]->[0] eq $node->[0]) {
2102     $node_i_in_active = $_;
2103     last S7S2;
2104     }
2105     }
2106 wakaba 1.3 splice @{$self->{open_elements}}, $node_i_in_open, 1;
2107 wakaba 1.1 redo S7;
2108     } # S7S2
2109    
2110     ## Step 3
2111     last S7 if $node->[0] eq $formatting_element->[0];
2112    
2113     ## Step 4
2114     if ($last_node->[0] eq $furthest_block->[0]) {
2115     $bookmark_prev_el = $node->[0];
2116     }
2117    
2118     ## Step 5
2119     if ($node->[0]->has_child_nodes ()) {
2120     my $clone = [$node->[0]->clone_node (0), $node->[1]];
2121     $active_formatting_elements->[$node_i_in_active] = $clone;
2122 wakaba 1.3 $self->{open_elements}->[$node_i_in_open] = $clone;
2123 wakaba 1.1 $node = $clone;
2124     }
2125    
2126     ## Step 6
2127     $node->[0]->append_child ($last_node->[0]);
2128    
2129     ## Step 7
2130     $last_node = $node;
2131    
2132     ## Step 8
2133     redo S7;
2134     } # S7
2135    
2136     ## Step 8
2137     $common_ancestor_node->[0]->append_child ($last_node->[0]);
2138    
2139     ## Step 9
2140     my $clone = [$formatting_element->[0]->clone_node (0),
2141     $formatting_element->[1]];
2142    
2143     ## Step 10
2144     my @cn = @{$furthest_block->[0]->child_nodes};
2145     $clone->[0]->append_child ($_) for @cn;
2146    
2147     ## Step 11
2148     $furthest_block->[0]->append_child ($clone->[0]);
2149    
2150     ## Step 12
2151     my $i;
2152     AFE: for (reverse 0..$#$active_formatting_elements) {
2153     if ($active_formatting_elements->[$_]->[0] eq $formatting_element->[0]) {
2154     splice @$active_formatting_elements, $_, 1;
2155     $i-- and last AFE if defined $i;
2156     } elsif ($active_formatting_elements->[$_]->[0] eq $bookmark_prev_el) {
2157     $i = $_;
2158     }
2159     } # AFE
2160     splice @$active_formatting_elements, $i + 1, 0, $clone;
2161    
2162     ## Step 13
2163     undef $i;
2164 wakaba 1.3 OE: for (reverse 0..$#{$self->{open_elements}}) {
2165     if ($self->{open_elements}->[$_]->[0] eq $formatting_element->[0]) {
2166     splice @{$self->{open_elements}}, $_, 1;
2167 wakaba 1.1 $i-- and last OE if defined $i;
2168 wakaba 1.3 } elsif ($self->{open_elements}->[$_]->[0] eq $furthest_block->[0]) {
2169 wakaba 1.1 $i = $_;
2170     }
2171     } # OE
2172 wakaba 1.3 splice @{$self->{open_elements}}, $i + 1, 1, $clone;
2173 wakaba 1.1
2174     ## Step 14
2175     redo FET;
2176     } # FET
2177     }; # $formatting_end_tag
2178    
2179     my $insert_to_current = sub {
2180 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child (shift);
2181 wakaba 1.1 }; # $insert_to_current
2182    
2183     my $insert_to_foster = sub {
2184     my $child = shift;
2185     if ({
2186     table => 1, tbody => 1, tfoot => 1,
2187     thead => 1, tr => 1,
2188 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
2189 wakaba 1.1 # MUST
2190     my $foster_parent_element;
2191     my $next_sibling;
2192 wakaba 1.3 OE: for (reverse 0..$#{$self->{open_elements}}) {
2193     if ($self->{open_elements}->[$_]->[1] eq 'table') {
2194     my $parent = $self->{open_elements}->[$_]->[0]->parent_node;
2195 wakaba 1.1 if (defined $parent and $parent->node_type == 1) {
2196     $foster_parent_element = $parent;
2197 wakaba 1.3 $next_sibling = $self->{open_elements}->[$_]->[0];
2198 wakaba 1.1 } else {
2199     $foster_parent_element
2200 wakaba 1.3 = $self->{open_elements}->[$_ - 1]->[0];
2201 wakaba 1.1 }
2202     last OE;
2203     }
2204     } # OE
2205 wakaba 1.3 $foster_parent_element = $self->{open_elements}->[0]->[0]
2206 wakaba 1.1 unless defined $foster_parent_element;
2207     $foster_parent_element->insert_before
2208     ($child, $next_sibling);
2209     } else {
2210 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($child);
2211 wakaba 1.1 }
2212     }; # $insert_to_foster
2213    
2214     my $in_body = sub {
2215     my $insert = shift;
2216     if ($token->{type} eq 'start tag') {
2217     if ($token->{tag_name} eq 'script') {
2218     $script_start_tag->();
2219     return;
2220     } elsif ($token->{tag_name} eq 'style') {
2221     $style_start_tag->();
2222     return;
2223     } elsif ({
2224     base => 1, link => 1, meta => 1,
2225     }->{$token->{tag_name}}) {
2226 wakaba 1.3 !!!parse-error (type => 'in body:'.$token->{tag_name});
2227 wakaba 1.1 ## NOTE: This is an "as if in head" code clone
2228     my $el;
2229     !!!create-element ($el, $token->{tag_name}, $token->{attributes});
2230 wakaba 1.3 if (defined $self->{head_element}) {
2231     $self->{head_element}->append_child ($el);
2232 wakaba 1.1 } else {
2233     $insert->($el);
2234     }
2235    
2236     !!!next-token;
2237     return;
2238     } elsif ($token->{tag_name} eq 'title') {
2239 wakaba 1.3 !!!parse-error (type => 'in body:title');
2240 wakaba 1.1 ## NOTE: There is an "as if in head" code clone
2241     my $title_el;
2242     !!!create-element ($title_el, 'title', $token->{attributes});
2243 wakaba 1.3 (defined $self->{head_element} ? $self->{head_element} : $self->{open_elements}->[-1]->[0])
2244 wakaba 1.1 ->append_child ($title_el);
2245     $self->{content_model_flag} = 'RCDATA';
2246    
2247     my $text = '';
2248     !!!next-token;
2249     while ($token->{type} eq 'character') {
2250     $text .= $token->{data};
2251     !!!next-token;
2252     }
2253     if (length $text) {
2254     $title_el->manakai_append_text ($text);
2255     }
2256    
2257     $self->{content_model_flag} = 'PCDATA';
2258    
2259     if ($token->{type} eq 'end tag' and
2260     $token->{tag_name} eq 'title') {
2261     ## Ignore the token
2262     } else {
2263 wakaba 1.3 !!!parse-error (type => 'in RCDATA:#'.$token->{type});
2264 wakaba 1.1 ## ISSUE: And ignore?
2265     }
2266     !!!next-token;
2267     return;
2268     } elsif ($token->{tag_name} eq 'body') {
2269 wakaba 1.3 !!!parse-error (type => 'in body:body');
2270 wakaba 1.1
2271 wakaba 1.3 if (@{$self->{open_elements}} == 1 or
2272     $self->{open_elements}->[1]->[1] ne 'body') {
2273 wakaba 1.1 ## Ignore the token
2274     } else {
2275 wakaba 1.3 my $body_el = $self->{open_elements}->[1]->[0];
2276 wakaba 1.1 for my $attr_name (keys %{$token->{attributes}}) {
2277     unless ($body_el->has_attribute_ns (undef, $attr_name)) {
2278     $body_el->set_attribute_ns
2279     (undef, [undef, $attr_name],
2280     $token->{attributes}->{$attr_name}->{value});
2281     }
2282     }
2283     }
2284     !!!next-token;
2285     return;
2286     } elsif ({
2287     address => 1, blockquote => 1, center => 1, dir => 1,
2288     div => 1, dl => 1, fieldset => 1, listing => 1,
2289     menu => 1, ol => 1, p => 1, ul => 1,
2290     pre => 1,
2291     }->{$token->{tag_name}}) {
2292     ## has a p element in scope
2293 wakaba 1.3 INSCOPE: for (reverse @{$self->{open_elements}}) {
2294 wakaba 1.1 if ($_->[1] eq 'p') {
2295     !!!back-token;
2296     $token = {type => 'end tag', tag_name => 'p'};
2297     return;
2298     } elsif ({
2299     table => 1, caption => 1, td => 1, th => 1,
2300     button => 1, marquee => 1, object => 1, html => 1,
2301     }->{$_->[1]}) {
2302     last INSCOPE;
2303     }
2304     } # INSCOPE
2305    
2306     !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2307     if ($token->{tag_name} eq 'pre') {
2308     !!!next-token;
2309     if ($token->{type} eq 'character') {
2310     $token->{data} =~ s/^\x0A//;
2311     unless (length $token->{data}) {
2312     !!!next-token;
2313     }
2314     }
2315     } else {
2316     !!!next-token;
2317     }
2318     return;
2319     } elsif ($token->{tag_name} eq 'form') {
2320 wakaba 1.3 if (defined $self->{form_element}) {
2321     !!!parse-error (type => 'in form:form');
2322 wakaba 1.1 ## Ignore the token
2323     } 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.3 splice @{$self->{open_elements}}, $i;
2365 wakaba 1.1 last LI;
2366     }
2367    
2368     ## Step 3
2369     if (not $formatting_category->{$node->[1]} and
2370     #not $phrasing_category->{$node->[1]} and
2371     ($special_category->{$node->[1]} or
2372     $scoping_category->{$node->[1]}) and
2373     $node->[1] ne 'address' and $node->[1] ne 'div') {
2374     last LI;
2375     }
2376    
2377     ## Step 4
2378     $i--;
2379 wakaba 1.3 $node = $self->{open_elements}->[$i];
2380 wakaba 1.1 redo LI;
2381     } # LI
2382    
2383     !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2384     !!!next-token;
2385     return;
2386     } elsif ($token->{tag_name} eq 'dd' or $token->{tag_name} eq 'dt') {
2387     ## has a p element in scope
2388 wakaba 1.3 INSCOPE: for (reverse @{$self->{open_elements}}) {
2389 wakaba 1.1 if ($_->[1] eq 'p') {
2390     !!!back-token;
2391     $token = {type => 'end tag', tag_name => 'p'};
2392     return;
2393     } elsif ({
2394     table => 1, caption => 1, td => 1, th => 1,
2395     button => 1, marquee => 1, object => 1, html => 1,
2396     }->{$_->[1]}) {
2397     last INSCOPE;
2398     }
2399     } # INSCOPE
2400    
2401     ## Step 1
2402     my $i = -1;
2403 wakaba 1.3 my $node = $self->{open_elements}->[$i];
2404 wakaba 1.1 LI: {
2405     ## Step 2
2406     if ($node->[1] eq 'dt' or $node->[1] eq 'dd') {
2407 wakaba 1.3 splice @{$self->{open_elements}}, $i;
2408 wakaba 1.1 last LI;
2409     }
2410    
2411     ## Step 3
2412     if (not $formatting_category->{$node->[1]} and
2413     #not $phrasing_category->{$node->[1]} and
2414     ($special_category->{$node->[1]} or
2415     $scoping_category->{$node->[1]}) and
2416     $node->[1] ne 'address' and $node->[1] ne 'div') {
2417     last LI;
2418     }
2419    
2420     ## Step 4
2421     $i--;
2422 wakaba 1.3 $node = $self->{open_elements}->[$i];
2423 wakaba 1.1 redo LI;
2424     } # LI
2425    
2426     !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2427     !!!next-token;
2428     return;
2429     } elsif ($token->{tag_name} eq 'plaintext') {
2430     ## has a p element in scope
2431 wakaba 1.3 INSCOPE: for (reverse @{$self->{open_elements}}) {
2432 wakaba 1.1 if ($_->[1] eq 'p') {
2433     !!!back-token;
2434     $token = {type => 'end tag', tag_name => 'p'};
2435     return;
2436     } elsif ({
2437     table => 1, caption => 1, td => 1, th => 1,
2438     button => 1, marquee => 1, object => 1, html => 1,
2439     }->{$_->[1]}) {
2440     last INSCOPE;
2441     }
2442     } # INSCOPE
2443    
2444     !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2445    
2446     $self->{content_model_flag} = 'PLAINTEXT';
2447    
2448     !!!next-token;
2449     return;
2450     } elsif ({
2451     h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,
2452     }->{$token->{tag_name}}) {
2453     ## has a p element in scope
2454 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
2455     my $node = $self->{open_elements}->[$_];
2456 wakaba 1.1 if ($node->[1] eq 'p') {
2457     !!!back-token;
2458     $token = {type => 'end tag', tag_name => 'p'};
2459     return;
2460     } elsif ({
2461     table => 1, caption => 1, td => 1, th => 1,
2462     button => 1, marquee => 1, object => 1, html => 1,
2463     }->{$node->[1]}) {
2464     last INSCOPE;
2465     }
2466     } # INSCOPE
2467    
2468     ## has an element in scope
2469     my $i;
2470 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
2471     my $node = $self->{open_elements}->[$_];
2472 wakaba 1.1 if ({
2473     h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,
2474     }->{$node->[1]}) {
2475     $i = $_;
2476     last INSCOPE;
2477     } elsif ({
2478     table => 1, caption => 1, td => 1, th => 1,
2479     button => 1, marquee => 1, object => 1, html => 1,
2480     }->{$node->[1]}) {
2481     last INSCOPE;
2482     }
2483     } # INSCOPE
2484    
2485     if (defined $i) {
2486 wakaba 1.3 !!!parse-error (type => 'in hn:hn');
2487     splice @{$self->{open_elements}}, $i;
2488 wakaba 1.1 }
2489    
2490     !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2491    
2492     !!!next-token;
2493     return;
2494     } elsif ($token->{tag_name} eq 'a') {
2495     AFE: for my $i (reverse 0..$#$active_formatting_elements) {
2496     my $node = $active_formatting_elements->[$i];
2497     if ($node->[1] eq 'a') {
2498 wakaba 1.3 !!!parse-error (type => 'in a:a');
2499 wakaba 1.1
2500     !!!back-token;
2501     $token = {type => 'end tag', tag_name => 'a'};
2502     $formatting_end_tag->($token->{tag_name});
2503    
2504     AFE2: for (reverse 0..$#$active_formatting_elements) {
2505     if ($active_formatting_elements->[$_]->[0] eq $node->[0]) {
2506     splice @$active_formatting_elements, $_, 1;
2507     last AFE2;
2508     }
2509     } # AFE2
2510 wakaba 1.3 OE: for (reverse 0..$#{$self->{open_elements}}) {
2511     if ($self->{open_elements}->[$_]->[0] eq $node->[0]) {
2512     splice @{$self->{open_elements}}, $_, 1;
2513 wakaba 1.1 last OE;
2514     }
2515     } # OE
2516     last AFE;
2517     } elsif ($node->[0] eq '#marker') {
2518     last AFE;
2519     }
2520     } # AFE
2521    
2522     $reconstruct_active_formatting_elements->($insert_to_current);
2523    
2524     !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2525 wakaba 1.3 push @$active_formatting_elements, $self->{open_elements}->[-1];
2526 wakaba 1.1
2527     !!!next-token;
2528     return;
2529     } elsif ({
2530     b => 1, big => 1, em => 1, font => 1, i => 1,
2531     nobr => 1, s => 1, small => 1, strile => 1,
2532     strong => 1, tt => 1, u => 1,
2533     }->{$token->{tag_name}}) {
2534     $reconstruct_active_formatting_elements->($insert_to_current);
2535    
2536     !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2537 wakaba 1.3 push @$active_formatting_elements, $self->{open_elements}->[-1];
2538 wakaba 1.1
2539     !!!next-token;
2540     return;
2541     } elsif ($token->{tag_name} eq 'button') {
2542     ## has a button element in scope
2543 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
2544     my $node = $self->{open_elements}->[$_];
2545 wakaba 1.1 if ($node->[1] eq 'button') {
2546 wakaba 1.3 !!!parse-error (type => 'in button:button');
2547 wakaba 1.1 !!!back-token;
2548     $token = {type => 'end tag', tag_name => 'button'};
2549     return;
2550     } elsif ({
2551     table => 1, caption => 1, td => 1, th => 1,
2552     button => 1, marquee => 1, object => 1, html => 1,
2553     }->{$node->[1]}) {
2554     last INSCOPE;
2555     }
2556     } # INSCOPE
2557    
2558     $reconstruct_active_formatting_elements->($insert_to_current);
2559    
2560     !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2561     push @$active_formatting_elements, ['#marker', ''];
2562    
2563     !!!next-token;
2564     return;
2565     } elsif ($token->{tag_name} eq 'marquee' or
2566     $token->{tag_name} eq 'object') {
2567     $reconstruct_active_formatting_elements->($insert_to_current);
2568    
2569     !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2570     push @$active_formatting_elements, ['#marker', ''];
2571    
2572     !!!next-token;
2573     return;
2574     } elsif ($token->{tag_name} eq 'xmp') {
2575     $reconstruct_active_formatting_elements->($insert_to_current);
2576    
2577     !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2578    
2579     $self->{content_model_flag} = 'CDATA';
2580    
2581     !!!next-token;
2582     return;
2583     } elsif ($token->{tag_name} eq 'table') {
2584     ## has a p element in scope
2585 wakaba 1.3 INSCOPE: for (reverse @{$self->{open_elements}}) {
2586 wakaba 1.1 if ($_->[1] eq 'p') {
2587     !!!back-token;
2588     $token = {type => 'end tag', tag_name => 'p'};
2589     return;
2590     } elsif ({
2591     table => 1, caption => 1, td => 1, th => 1,
2592     button => 1, marquee => 1, object => 1, html => 1,
2593     }->{$_->[1]}) {
2594     last INSCOPE;
2595     }
2596     } # INSCOPE
2597    
2598     !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2599    
2600 wakaba 1.3 $self->{insertion_mode} = 'in table';
2601 wakaba 1.1
2602     !!!next-token;
2603     return;
2604     } elsif ({
2605     area => 1, basefont => 1, bgsound => 1, br => 1,
2606     embed => 1, img => 1, param => 1, spacer => 1, wbr => 1,
2607     image => 1,
2608     }->{$token->{tag_name}}) {
2609     if ($token->{tag_name} eq 'image') {
2610 wakaba 1.3 !!!parse-error (type => 'image');
2611 wakaba 1.1 $token->{tag_name} = 'img';
2612     }
2613    
2614     $reconstruct_active_formatting_elements->($insert_to_current);
2615    
2616     !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2617 wakaba 1.3 pop @{$self->{open_elements}};
2618 wakaba 1.1
2619     !!!next-token;
2620     return;
2621     } elsif ($token->{tag_name} eq 'hr') {
2622     ## has a p element in scope
2623 wakaba 1.3 INSCOPE: for (reverse @{$self->{open_elements}}) {
2624 wakaba 1.1 if ($_->[1] eq 'p') {
2625     !!!back-token;
2626     $token = {type => 'end tag', tag_name => 'p'};
2627     return;
2628     } elsif ({
2629     table => 1, caption => 1, td => 1, th => 1,
2630     button => 1, marquee => 1, object => 1, html => 1,
2631     }->{$_->[1]}) {
2632     last INSCOPE;
2633     }
2634     } # INSCOPE
2635    
2636     !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2637 wakaba 1.3 pop @{$self->{open_elements}};
2638 wakaba 1.1
2639     !!!next-token;
2640     return;
2641     } elsif ($token->{tag_name} eq 'input') {
2642     $reconstruct_active_formatting_elements->($insert_to_current);
2643    
2644     !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2645 wakaba 1.3 ## TODO: associate with $self->{form_element} if defined
2646     pop @{$self->{open_elements}};
2647 wakaba 1.1
2648     !!!next-token;
2649     return;
2650     } elsif ($token->{tag_name} eq 'isindex') {
2651 wakaba 1.3 !!!parse-error (type => 'isindex');
2652 wakaba 1.1
2653 wakaba 1.3 if (defined $self->{form_element}) {
2654 wakaba 1.1 ## Ignore the token
2655     !!!next-token;
2656     return;
2657     } else {
2658     my $at = $token->{attributes};
2659     $at->{name} = {name => 'name', value => 'isindex'};
2660     my @tokens = (
2661     {type => 'start tag', tag_name => 'form'},
2662     {type => 'start tag', tag_name => 'hr'},
2663     {type => 'start tag', tag_name => 'p'},
2664     {type => 'start tag', tag_name => 'label'},
2665     {type => 'character',
2666     data => 'This is a searchable index. Insert your search keywords here: '}, # SHOULD
2667     ## TODO: make this configurable
2668     {type => 'start tag', tag_name => 'input', attributes => $at},
2669     #{type => 'character', data => ''}, # SHOULD
2670     {type => 'end tag', tag_name => 'label'},
2671     {type => 'end tag', tag_name => 'p'},
2672     {type => 'start tag', tag_name => 'hr'},
2673     {type => 'end tag', tag_name => 'form'},
2674     );
2675     $token = shift @tokens;
2676     !!!back-token (@tokens);
2677     return;
2678     }
2679     } elsif ({
2680     textarea => 1,
2681     noembed => 1,
2682     noframes => 1,
2683     noscript => 0, ## TODO: 1 if scripting is enabled
2684     }->{$token->{tag_name}}) {
2685     my $tag_name = $token->{tag_name};
2686     my $el;
2687     !!!create-element ($el, $token->{tag_name}, $token->{attributes});
2688    
2689     if ($token->{tag_name} eq 'textarea') {
2690 wakaba 1.3 ## TODO: $self->{form_element} if defined
2691 wakaba 1.1 $self->{content_model_flag} = 'RCDATA';
2692     } else {
2693     $self->{content_model_flag} = 'CDATA';
2694     }
2695    
2696     $insert->($el);
2697    
2698     my $text = '';
2699     !!!next-token;
2700     while ($token->{type} eq 'character') {
2701     $text .= $token->{data};
2702     !!!next-token;
2703     }
2704     if (length $text) {
2705     $el->manakai_append_text ($text);
2706     }
2707    
2708     $self->{content_model_flag} = 'PCDATA';
2709    
2710     if ($token->{type} eq 'end tag' and
2711     $token->{tag_name} eq $tag_name) {
2712     ## Ignore the token
2713     } else {
2714 wakaba 1.3 if ($token->{tag_name} eq 'textarea') {
2715     !!!parse-error (type => 'in CDATA:#'.$token->{type});
2716     } else {
2717     !!!parse-error (type => 'in RCDATA:#'.$token->{type});
2718     }
2719 wakaba 1.1 ## ISSUE: And ignore?
2720     }
2721     !!!next-token;
2722     return;
2723     } elsif ($token->{tag_name} eq 'select') {
2724     $reconstruct_active_formatting_elements->($insert_to_current);
2725    
2726     !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2727    
2728 wakaba 1.3 $self->{insertion_mode} = 'in select';
2729 wakaba 1.1 !!!next-token;
2730     return;
2731     } elsif ({
2732     caption => 1, col => 1, colgroup => 1, frame => 1,
2733     frameset => 1, head => 1, option => 1, optgroup => 1,
2734     tbody => 1, td => 1, tfoot => 1, th => 1,
2735     thead => 1, tr => 1,
2736     }->{$token->{tag_name}}) {
2737 wakaba 1.3 !!!parse-error (type => 'in body:'.$token->{tag_name});
2738 wakaba 1.1 ## Ignore the token
2739     !!!next-token;
2740     return;
2741    
2742     ## ISSUE: An issue on HTML5 new elements in the spec.
2743     } else {
2744     $reconstruct_active_formatting_elements->($insert_to_current);
2745    
2746     !!!insert-element-t ($token->{tag_name}, $token->{attributes});
2747    
2748     !!!next-token;
2749     return;
2750     }
2751     } elsif ($token->{type} eq 'end tag') {
2752     if ($token->{tag_name} eq 'body') {
2753 wakaba 1.3 if (@{$self->{open_elements}} > 1 and $self->{open_elements}->[1]->[1] eq 'body') {
2754 wakaba 1.1 ## ISSUE: There is an issue in the spec.
2755 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne 'body') {
2756     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
2757 wakaba 1.1 }
2758 wakaba 1.3 $self->{insertion_mode} = 'after body';
2759 wakaba 1.1 !!!next-token;
2760     return;
2761     } else {
2762 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
2763 wakaba 1.1 ## Ignore the token
2764     !!!next-token;
2765     return;
2766     }
2767     } elsif ($token->{tag_name} eq 'html') {
2768 wakaba 1.3 if (@{$self->{open_elements}} > 1 and $self->{open_elements}->[1]->[1] eq 'body') {
2769 wakaba 1.1 ## ISSUE: There is an issue in the spec.
2770 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne 'body') {
2771     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[1]->[1]);
2772 wakaba 1.1 }
2773 wakaba 1.3 $self->{insertion_mode} = 'after body';
2774 wakaba 1.1 ## reprocess
2775     return;
2776     } else {
2777 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
2778 wakaba 1.1 ## Ignore the token
2779     !!!next-token;
2780     return;
2781     }
2782     } elsif ({
2783     address => 1, blockquote => 1, center => 1, dir => 1,
2784     div => 1, dl => 1, fieldset => 1, listing => 1,
2785     menu => 1, ol => 1, pre => 1, ul => 1,
2786     form => 1,
2787     p => 1,
2788     dd => 1, dt => 1, li => 1,
2789     button => 1, marquee => 1, object => 1,
2790     }->{$token->{tag_name}}) {
2791     ## has an element in scope
2792     my $i;
2793 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
2794     my $node = $self->{open_elements}->[$_];
2795 wakaba 1.1 if ($node->[1] eq $token->{tag_name}) {
2796     ## generate implied end tags
2797     if ({
2798     dd => ($token->{tag_name} ne 'dd'),
2799     dt => ($token->{tag_name} ne 'dt'),
2800     li => ($token->{tag_name} ne 'li'),
2801     p => ($token->{tag_name} ne 'p'),
2802     td => 1, th => 1, tr => 1,
2803 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
2804 wakaba 1.1 !!!back-token;
2805     $token = {type => 'end tag',
2806 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
2807 wakaba 1.1 return;
2808     }
2809     $i = $_;
2810     last INSCOPE unless $token->{tag_name} eq 'p';
2811     } elsif ({
2812     table => 1, caption => 1, td => 1, th => 1,
2813     button => 1, marquee => 1, object => 1, html => 1,
2814     }->{$node->[1]}) {
2815     last INSCOPE;
2816     }
2817     } # INSCOPE
2818    
2819 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne $token->{tag_name}) {
2820     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
2821 wakaba 1.1 }
2822    
2823 wakaba 1.3 splice @{$self->{open_elements}}, $i if defined $i;
2824     undef $self->{form_element} if $token->{tag_name} eq 'form';
2825 wakaba 1.1 $clear_up_to_marker->()
2826     if {
2827     button => 1, marquee => 1, object => 1,
2828     }->{$token->{tag_name}};
2829     !!!next-token;
2830     return;
2831     } elsif ({
2832     h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,
2833     }->{$token->{tag_name}}) {
2834     ## has an element in scope
2835     my $i;
2836 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
2837     my $node = $self->{open_elements}->[$_];
2838 wakaba 1.1 if ({
2839     h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,
2840     }->{$node->[1]}) {
2841     ## generate implied end tags
2842     if ({
2843     dd => 1, dt => 1, li => 1, p => 1,
2844     td => 1, th => 1, tr => 1,
2845 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
2846 wakaba 1.1 !!!back-token;
2847     $token = {type => 'end tag',
2848 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
2849 wakaba 1.1 return;
2850     }
2851     $i = $_;
2852     last INSCOPE;
2853     } elsif ({
2854     table => 1, caption => 1, td => 1, th => 1,
2855     button => 1, marquee => 1, object => 1, html => 1,
2856     }->{$node->[1]}) {
2857     last INSCOPE;
2858     }
2859     } # INSCOPE
2860    
2861 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne $token->{tag_name}) {
2862     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
2863 wakaba 1.1 }
2864    
2865 wakaba 1.3 splice @{$self->{open_elements}}, $i if defined $i;
2866 wakaba 1.1 !!!next-token;
2867     return;
2868     } elsif ({
2869     a => 1,
2870     b => 1, big => 1, em => 1, font => 1, i => 1,
2871     nobr => 1, s => 1, small => 1, strile => 1,
2872     strong => 1, tt => 1, u => 1,
2873     }->{$token->{tag_name}}) {
2874     $formatting_end_tag->($token->{tag_name});
2875     return;
2876     } elsif ({
2877     caption => 1, col => 1, colgroup => 1, frame => 1,
2878     frameset => 1, head => 1, option => 1, optgroup => 1,
2879     tbody => 1, td => 1, tfoot => 1, th => 1,
2880     thead => 1, tr => 1,
2881     area => 1, basefont => 1, bgsound => 1, br => 1,
2882     embed => 1, hr => 1, iframe => 1, image => 1,
2883     img => 1, input => 1, isindex=> 1, noembed => 1,
2884     noframes => 1, param => 1, select => 1, spacer => 1,
2885     table => 1, textarea => 1, wbr => 1,
2886     noscript => 0, ## TODO: if scripting is enabled
2887     }->{$token->{tag_name}}) {
2888 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
2889 wakaba 1.1 ## Ignore the token
2890     !!!next-token;
2891     return;
2892    
2893     ## ISSUE: Issue on HTML5 new elements in spec
2894    
2895     } else {
2896     ## Step 1
2897     my $node_i = -1;
2898 wakaba 1.3 my $node = $self->{open_elements}->[$node_i];
2899 wakaba 1.1
2900     ## Step 2
2901     S2: {
2902     if ($node->[1] eq $token->{tag_name}) {
2903     ## Step 1
2904     ## generate implied end tags
2905     if ({
2906     dd => 1, dt => 1, li => 1, p => 1,
2907     td => 1, th => 1, tr => 1,
2908 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
2909 wakaba 1.1 !!!back-token;
2910     $token = {type => 'end tag',
2911 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
2912 wakaba 1.1 return;
2913     }
2914    
2915     ## Step 2
2916 wakaba 1.3 if ($token->{tag_name} ne $self->{open_elements}->[-1]->[1]) {
2917     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
2918 wakaba 1.1 }
2919    
2920     ## Step 3
2921 wakaba 1.3 splice @{$self->{open_elements}}, $node_i;
2922    
2923     !!!next-token;
2924 wakaba 1.1 last S2;
2925     } else {
2926     ## Step 3
2927     if (not $formatting_category->{$node->[1]} and
2928     #not $phrasing_category->{$node->[1]} and
2929     ($special_category->{$node->[1]} or
2930     $scoping_category->{$node->[1]})) {
2931 wakaba 1.3 !!!parse-error (type => 'not closed:'.$node->[1]);
2932 wakaba 1.1 ## Ignore the token
2933     !!!next-token;
2934     last S2;
2935     }
2936     }
2937    
2938     ## Step 4
2939     $node_i--;
2940 wakaba 1.3 $node = $self->{open_elements}->[$node_i];
2941 wakaba 1.1
2942     ## Step 5;
2943     redo S2;
2944     } # S2
2945 wakaba 1.3 return;
2946 wakaba 1.1 }
2947     }
2948     }; # $in_body
2949    
2950     B: {
2951 wakaba 1.3 if ($phase eq 'main') {
2952 wakaba 1.1 if ($token->{type} eq 'DOCTYPE') {
2953 wakaba 1.3 !!!parse-error (type => 'in html:#DOCTYPE');
2954 wakaba 1.1 ## Ignore the token
2955     ## Stay in the phase
2956     !!!next-token;
2957     redo B;
2958     } elsif ($token->{type} eq 'start tag' and
2959     $token->{tag_name} eq 'html') {
2960     ## TODO: unless it is the first start tag token, parse-error
2961 wakaba 1.3 my $top_el = $self->{open_elements}->[0]->[0];
2962 wakaba 1.1 for my $attr_name (keys %{$token->{attributes}}) {
2963     unless ($top_el->has_attribute_ns (undef, $attr_name)) {
2964     $top_el->set_attribute_ns
2965     (undef, [undef, $attr_name],
2966     $token->{attributes}->{$attr_name}->{value});
2967     }
2968     }
2969     !!!next-token;
2970     redo B;
2971     } elsif ($token->{type} eq 'end-of-file') {
2972     ## Generate implied end tags
2973     if ({
2974     dd => 1, dt => 1, li => 1, p => 1, td => 1, th => 1, tr => 1,
2975 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
2976 wakaba 1.1 !!!back-token;
2977 wakaba 1.3 $token = {type => 'end tag', tag_name => $self->{open_elements}->[-1]->[1]};
2978 wakaba 1.1 redo B;
2979     }
2980    
2981 wakaba 1.3 if (@{$self->{open_elements}} > 2 or
2982     (@{$self->{open_elements}} == 2 and $self->{open_elements}->[1]->[1] ne 'body')) {
2983     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
2984     } elsif (defined $self->{inner_html_node} and
2985     @{$self->{open_elements}} > 1 and
2986     $self->{open_elements}->[1]->[1] ne 'body') {
2987     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
2988 wakaba 1.1 }
2989    
2990     ## Stop parsing
2991     last B;
2992    
2993     ## ISSUE: There is an issue in the spec.
2994     } else {
2995 wakaba 1.3 if ($self->{insertion_mode} eq 'before head') {
2996 wakaba 1.1 if ($token->{type} eq 'character') {
2997     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
2998 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
2999 wakaba 1.1 unless (length $token->{data}) {
3000     !!!next-token;
3001     redo B;
3002     }
3003     }
3004     ## As if <head>
3005 wakaba 1.3 !!!create-element ($self->{head_element}, 'head');
3006     $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
3007     push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3008     $self->{insertion_mode} = 'in head';
3009 wakaba 1.1 ## reprocess
3010     redo B;
3011     } elsif ($token->{type} eq 'comment') {
3012     my $comment = $self->{document}->create_comment ($token->{data});
3013 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
3014 wakaba 1.1 !!!next-token;
3015     redo B;
3016     } elsif ($token->{type} eq 'start tag') {
3017     my $attr = $token->{tag_name} eq 'head' ? $token->{attributes} : {};
3018 wakaba 1.3 !!!create-element ($self->{head_element}, 'head', $attr);
3019     $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
3020     push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3021     $self->{insertion_mode} = 'in head';
3022 wakaba 1.1 if ($token->{tag_name} eq 'head') {
3023     !!!next-token;
3024     #} elsif ({
3025     # base => 1, link => 1, meta => 1,
3026     # script => 1, style => 1, title => 1,
3027     # }->{$token->{tag_name}}) {
3028     # ## reprocess
3029     } else {
3030     ## reprocess
3031     }
3032     redo B;
3033     } elsif ($token->{type} eq 'end tag') {
3034     if ($token->{tag_name} eq 'html') {
3035     ## As if <head>
3036 wakaba 1.3 !!!create-element ($self->{head_element}, 'head');
3037     $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
3038     push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3039     $self->{insertion_mode} = 'in head';
3040 wakaba 1.1 ## reprocess
3041     redo B;
3042     } else {
3043 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3044 wakaba 1.1 ## Ignore the token
3045     !!!next-token;
3046     redo B;
3047     }
3048     } else {
3049     die "$0: $token->{type}: Unknown type";
3050     }
3051 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'in head') {
3052 wakaba 1.1 if ($token->{type} eq 'character') {
3053     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
3054 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
3055 wakaba 1.1 unless (length $token->{data}) {
3056     !!!next-token;
3057     redo B;
3058     }
3059     }
3060    
3061     #
3062     } elsif ($token->{type} eq 'comment') {
3063     my $comment = $self->{document}->create_comment ($token->{data});
3064 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
3065 wakaba 1.1 !!!next-token;
3066     redo B;
3067     } elsif ($token->{type} eq 'start tag') {
3068     if ($token->{tag_name} eq 'title') {
3069     ## NOTE: There is an "as if in head" code clone
3070     my $title_el;
3071     !!!create-element ($title_el, 'title', $token->{attributes});
3072 wakaba 1.3 (defined $self->{head_element} ? $self->{head_element} : $self->{open_elements}->[-1]->[0])
3073 wakaba 1.1 ->append_child ($title_el);
3074     $self->{content_model_flag} = 'RCDATA';
3075    
3076     my $text = '';
3077     !!!next-token;
3078     while ($token->{type} eq 'character') {
3079     $text .= $token->{data};
3080     !!!next-token;
3081     }
3082     if (length $text) {
3083     $title_el->manakai_append_text ($text);
3084     }
3085    
3086     $self->{content_model_flag} = 'PCDATA';
3087    
3088     if ($token->{type} eq 'end tag' and
3089     $token->{tag_name} eq 'title') {
3090     ## Ignore the token
3091     } else {
3092 wakaba 1.3 !!!parse-error (type => 'in RCDATA:#'.$token->{type});
3093 wakaba 1.1 ## ISSUE: And ignore?
3094     }
3095     !!!next-token;
3096     redo B;
3097     } elsif ($token->{tag_name} eq 'style') {
3098     $style_start_tag->();
3099     redo B;
3100     } elsif ($token->{tag_name} eq 'script') {
3101     $script_start_tag->();
3102     redo B;
3103     } elsif ({base => 1, link => 1, meta => 1}->{$token->{tag_name}}) {
3104     ## NOTE: There are "as if in head" code clones
3105     my $el;
3106     !!!create-element ($el, $token->{tag_name}, $token->{attributes});
3107 wakaba 1.3 (defined $self->{head_element} ? $self->{head_element} : $self->{open_elements}->[-1]->[0])
3108 wakaba 1.1 ->append_child ($el);
3109    
3110     !!!next-token;
3111     redo B;
3112     } elsif ($token->{tag_name} eq 'head') {
3113 wakaba 1.3 !!!parse-error (type => 'in head:head');
3114 wakaba 1.1 ## Ignore the token
3115     !!!next-token;
3116     redo B;
3117     } else {
3118     #
3119     }
3120     } elsif ($token->{type} eq 'end tag') {
3121     if ($token->{tag_name} eq 'head') {
3122 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] eq 'head') {
3123     pop @{$self->{open_elements}};
3124 wakaba 1.1 } else {
3125 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:head');
3126 wakaba 1.1 }
3127 wakaba 1.3 $self->{insertion_mode} = 'after head';
3128 wakaba 1.1 !!!next-token;
3129     redo B;
3130     } elsif ($token->{tag_name} eq 'html') {
3131     #
3132     } else {
3133 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3134 wakaba 1.1 ## Ignore the token
3135     !!!next-token;
3136     redo B;
3137     }
3138     } else {
3139     #
3140     }
3141    
3142 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] eq 'head') {
3143 wakaba 1.1 ## As if </head>
3144 wakaba 1.3 pop @{$self->{open_elements}};
3145 wakaba 1.1 }
3146 wakaba 1.3 $self->{insertion_mode} = 'after head';
3147 wakaba 1.1 ## reprocess
3148     redo B;
3149    
3150     ## ISSUE: An issue in the spec.
3151 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'after head') {
3152 wakaba 1.1 if ($token->{type} eq 'character') {
3153     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
3154 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
3155 wakaba 1.1 unless (length $token->{data}) {
3156     !!!next-token;
3157     redo B;
3158     }
3159     }
3160    
3161     #
3162     } elsif ($token->{type} eq 'comment') {
3163     my $comment = $self->{document}->create_comment ($token->{data});
3164 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
3165 wakaba 1.1 !!!next-token;
3166     redo B;
3167     } elsif ($token->{type} eq 'start tag') {
3168     if ($token->{tag_name} eq 'body') {
3169     !!!insert-element ('body', $token->{attributes});
3170 wakaba 1.3 $self->{insertion_mode} = 'in body';
3171 wakaba 1.1 !!!next-token;
3172     redo B;
3173     } elsif ($token->{tag_name} eq 'frameset') {
3174     !!!insert-element ('frameset', $token->{attributes});
3175 wakaba 1.3 $self->{insertion_mode} = 'in frameset';
3176 wakaba 1.1 !!!next-token;
3177     redo B;
3178     } elsif ({
3179     base => 1, link => 1, meta => 1,
3180 wakaba 1.3 script => 1, style => 1, title => 1,
3181 wakaba 1.1 }->{$token->{tag_name}}) {
3182 wakaba 1.3 !!!parse-error (type => 'after head:'.$token->{tag_name});
3183     $self->{insertion_mode} = 'in head';
3184 wakaba 1.1 ## reprocess
3185     redo B;
3186     } else {
3187     #
3188     }
3189     } else {
3190     #
3191     }
3192    
3193     ## As if <body>
3194     !!!insert-element ('body');
3195 wakaba 1.3 $self->{insertion_mode} = 'in body';
3196 wakaba 1.1 ## reprocess
3197     redo B;
3198 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'in body') {
3199 wakaba 1.1 if ($token->{type} eq 'character') {
3200     ## NOTE: There is a code clone of "character in body".
3201     $reconstruct_active_formatting_elements->($insert_to_current);
3202    
3203 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
3204 wakaba 1.1
3205     !!!next-token;
3206     redo B;
3207     } elsif ($token->{type} eq 'comment') {
3208     ## NOTE: There is a code clone of "comment in body".
3209     my $comment = $self->{document}->create_comment ($token->{data});
3210 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
3211 wakaba 1.1 !!!next-token;
3212     redo B;
3213     } else {
3214     $in_body->($insert_to_current);
3215     redo B;
3216     }
3217 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'in table') {
3218 wakaba 1.1 if ($token->{type} eq 'character') {
3219     ## NOTE: There are "character in table" code clones.
3220     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
3221 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
3222 wakaba 1.1
3223     unless (length $token->{data}) {
3224     !!!next-token;
3225     redo B;
3226     }
3227     }
3228    
3229 wakaba 1.3 !!!parse-error (type => 'in table:#character');
3230    
3231 wakaba 1.1 ## As if in body, but insert into foster parent element
3232     ## ISSUE: Spec says that "whenever a node would be inserted
3233     ## into the current node" while characters might not be
3234     ## result in a new Text node.
3235     $reconstruct_active_formatting_elements->($insert_to_foster);
3236    
3237     if ({
3238     table => 1, tbody => 1, tfoot => 1,
3239     thead => 1, tr => 1,
3240 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
3241 wakaba 1.1 # MUST
3242     my $foster_parent_element;
3243     my $next_sibling;
3244     my $prev_sibling;
3245 wakaba 1.3 OE: for (reverse 0..$#{$self->{open_elements}}) {
3246     if ($self->{open_elements}->[$_]->[1] eq 'table') {
3247     my $parent = $self->{open_elements}->[$_]->[0]->parent_node;
3248 wakaba 1.1 if (defined $parent and $parent->node_type == 1) {
3249     $foster_parent_element = $parent;
3250 wakaba 1.3 $next_sibling = $self->{open_elements}->[$_]->[0];
3251 wakaba 1.1 $prev_sibling = $next_sibling->previous_sibling;
3252     } else {
3253 wakaba 1.3 $foster_parent_element = $self->{open_elements}->[$_ - 1]->[0];
3254 wakaba 1.1 $prev_sibling = $foster_parent_element->last_child;
3255     }
3256     last OE;
3257     }
3258     } # OE
3259 wakaba 1.3 $foster_parent_element = $self->{open_elements}->[0]->[0] and
3260 wakaba 1.1 $prev_sibling = $foster_parent_element->last_child
3261     unless defined $foster_parent_element;
3262     if (defined $prev_sibling and
3263     $prev_sibling->node_type == 3) {
3264     $prev_sibling->manakai_append_text ($token->{data});
3265     } else {
3266     $foster_parent_element->insert_before
3267     ($self->{document}->create_text_node ($token->{data}),
3268     $next_sibling);
3269     }
3270     } else {
3271 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
3272 wakaba 1.1 }
3273    
3274     !!!next-token;
3275     redo B;
3276     } elsif ($token->{type} eq 'comment') {
3277     my $comment = $self->{document}->create_comment ($token->{data});
3278 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
3279 wakaba 1.1 !!!next-token;
3280     redo B;
3281     } elsif ($token->{type} eq 'start tag') {
3282     if ({
3283     caption => 1,
3284     colgroup => 1,
3285     tbody => 1, tfoot => 1, thead => 1,
3286     }->{$token->{tag_name}}) {
3287     ## Clear back to table context
3288 wakaba 1.3 while ($self->{open_elements}->[-1]->[1] ne 'table' and
3289     $self->{open_elements}->[-1]->[1] ne 'html') {
3290     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3291     pop @{$self->{open_elements}};
3292 wakaba 1.1 }
3293    
3294     push @$active_formatting_elements, ['#marker', '']
3295     if $token->{tag_name} eq 'caption';
3296    
3297     !!!insert-element ($token->{tag_name}, $token->{attributes});
3298 wakaba 1.3 $self->{insertion_mode} = {
3299 wakaba 1.1 caption => 'in caption',
3300     colgroup => 'in column group',
3301     tbody => 'in table body',
3302     tfoot => 'in table body',
3303     thead => 'in table body',
3304     }->{$token->{tag_name}};
3305     !!!next-token;
3306     redo B;
3307     } elsif ({
3308     col => 1,
3309     td => 1, th => 1, tr => 1,
3310     }->{$token->{tag_name}}) {
3311     ## Clear back to table context
3312 wakaba 1.3 while ($self->{open_elements}->[-1]->[1] ne 'table' and
3313     $self->{open_elements}->[-1]->[1] ne 'html') {
3314     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3315     pop @{$self->{open_elements}};
3316 wakaba 1.1 }
3317    
3318     !!!insert-element ($token->{tag_name} eq 'col' ? 'colgroup' : 'tbody');
3319 wakaba 1.3 $self->{insertion_mode} = $token->{tag_name} eq 'col'
3320 wakaba 1.1 ? 'in column group' : 'in table body';
3321     ## reprocess
3322     redo B;
3323     } elsif ($token->{tag_name} eq 'table') {
3324     ## NOTE: There are code clones for this "table in table"
3325 wakaba 1.3 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3326 wakaba 1.1
3327     ## As if </table>
3328     ## have a table element in table scope
3329     my $i;
3330 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3331     my $node = $self->{open_elements}->[$_];
3332 wakaba 1.1 if ($node->[1] eq 'table') {
3333     $i = $_;
3334     last INSCOPE;
3335     } elsif ({
3336     table => 1, html => 1,
3337     }->{$node->[1]}) {
3338     last INSCOPE;
3339     }
3340     } # INSCOPE
3341     unless (defined $i) {
3342 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:table');
3343 wakaba 1.1 ## Ignore tokens </table><table>
3344     !!!next-token;
3345     redo B;
3346     }
3347    
3348     ## generate implied end tags
3349     if ({
3350     dd => 1, dt => 1, li => 1, p => 1,
3351     td => 1, th => 1, tr => 1,
3352 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
3353 wakaba 1.1 !!!back-token; # <table>
3354     $token = {type => 'end tag', tag_name => 'table'};
3355     !!!back-token;
3356     $token = {type => 'end tag',
3357 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3358 wakaba 1.1 redo B;
3359     }
3360    
3361 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne 'table') {
3362     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3363 wakaba 1.1 }
3364    
3365 wakaba 1.3 splice @{$self->{open_elements}}, $i;
3366 wakaba 1.1
3367 wakaba 1.3 $self->_reset_insertion_mode;
3368 wakaba 1.1
3369     ## reprocess
3370     redo B;
3371     } else {
3372     #
3373     }
3374     } elsif ($token->{type} eq 'end tag') {
3375     if ($token->{tag_name} eq 'table') {
3376     ## have a table element in table scope
3377     my $i;
3378 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3379     my $node = $self->{open_elements}->[$_];
3380 wakaba 1.1 if ($node->[1] eq $token->{tag_name}) {
3381     $i = $_;
3382     last INSCOPE;
3383     } elsif ({
3384     table => 1, html => 1,
3385     }->{$node->[1]}) {
3386     last INSCOPE;
3387     }
3388     } # INSCOPE
3389     unless (defined $i) {
3390 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3391 wakaba 1.1 ## Ignore the token
3392     !!!next-token;
3393     redo B;
3394     }
3395    
3396     ## generate implied end tags
3397     if ({
3398     dd => 1, dt => 1, li => 1, p => 1,
3399     td => 1, th => 1, tr => 1,
3400 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
3401 wakaba 1.1 !!!back-token;
3402     $token = {type => 'end tag',
3403 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3404 wakaba 1.1 redo B;
3405     }
3406    
3407 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne 'table') {
3408     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3409 wakaba 1.1 }
3410    
3411 wakaba 1.3 splice @{$self->{open_elements}}, $i;
3412 wakaba 1.1
3413 wakaba 1.3 $self->_reset_insertion_mode;
3414 wakaba 1.1
3415     !!!next-token;
3416     redo B;
3417     } elsif ({
3418     body => 1, caption => 1, col => 1, colgroup => 1,
3419     html => 1, tbody => 1, td => 1, tfoot => 1, th => 1,
3420     thead => 1, tr => 1,
3421     }->{$token->{tag_name}}) {
3422 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3423 wakaba 1.1 ## Ignore the token
3424     !!!next-token;
3425     redo B;
3426     } else {
3427     #
3428     }
3429     } else {
3430     #
3431     }
3432    
3433 wakaba 1.3 !!!parse-error (type => 'in table:'.$token->{tag_name});
3434 wakaba 1.1 $in_body->($insert_to_foster);
3435     redo B;
3436 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'in caption') {
3437 wakaba 1.1 if ($token->{type} eq 'character') {
3438     ## NOTE: This is a code clone of "character in body".
3439     $reconstruct_active_formatting_elements->($insert_to_current);
3440    
3441 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
3442 wakaba 1.1
3443     !!!next-token;
3444     redo B;
3445     } elsif ($token->{type} eq 'comment') {
3446     ## NOTE: This is a code clone of "comment in body".
3447     my $comment = $self->{document}->create_comment ($token->{data});
3448 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
3449 wakaba 1.1 !!!next-token;
3450     redo B;
3451     } elsif ($token->{type} eq 'start tag') {
3452     if ({
3453     caption => 1, col => 1, colgroup => 1, tbody => 1,
3454     td => 1, tfoot => 1, th => 1, thead => 1, tr => 1,
3455     }->{$token->{tag_name}}) {
3456 wakaba 1.3 !!!parse-error (type => 'not closed:caption');
3457 wakaba 1.1
3458     ## As if </caption>
3459     ## have a table element in table scope
3460     my $i;
3461 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3462     my $node = $self->{open_elements}->[$_];
3463 wakaba 1.1 if ($node->[1] eq 'caption') {
3464     $i = $_;
3465     last INSCOPE;
3466     } elsif ({
3467     table => 1, html => 1,
3468     }->{$node->[1]}) {
3469     last INSCOPE;
3470     }
3471     } # INSCOPE
3472     unless (defined $i) {
3473 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:caption');
3474 wakaba 1.1 ## Ignore the token
3475     !!!next-token;
3476     redo B;
3477     }
3478    
3479     ## generate implied end tags
3480     if ({
3481     dd => 1, dt => 1, li => 1, p => 1,
3482     td => 1, th => 1, tr => 1,
3483 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
3484 wakaba 1.1 !!!back-token; # <?>
3485     $token = {type => 'end tag', tag_name => 'caption'};
3486     !!!back-token;
3487     $token = {type => 'end tag',
3488 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3489 wakaba 1.1 redo B;
3490     }
3491    
3492 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne 'caption') {
3493     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3494 wakaba 1.1 }
3495    
3496 wakaba 1.3 splice @{$self->{open_elements}}, $i;
3497 wakaba 1.1
3498     $clear_up_to_marker->();
3499    
3500 wakaba 1.3 $self->{insertion_mode} = 'in table';
3501 wakaba 1.1
3502     ## reprocess
3503     redo B;
3504     } else {
3505     #
3506     }
3507     } elsif ($token->{type} eq 'end tag') {
3508     if ($token->{tag_name} eq 'caption') {
3509     ## have a table element in table scope
3510     my $i;
3511 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3512     my $node = $self->{open_elements}->[$_];
3513 wakaba 1.1 if ($node->[1] eq $token->{tag_name}) {
3514     $i = $_;
3515     last INSCOPE;
3516     } elsif ({
3517     table => 1, html => 1,
3518     }->{$node->[1]}) {
3519     last INSCOPE;
3520     }
3521     } # INSCOPE
3522     unless (defined $i) {
3523 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3524 wakaba 1.1 ## Ignore the token
3525     !!!next-token;
3526     redo B;
3527     }
3528    
3529     ## generate implied end tags
3530     if ({
3531     dd => 1, dt => 1, li => 1, p => 1,
3532     td => 1, th => 1, tr => 1,
3533 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
3534 wakaba 1.1 !!!back-token;
3535     $token = {type => 'end tag',
3536 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3537 wakaba 1.1 redo B;
3538     }
3539    
3540 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne 'caption') {
3541     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3542 wakaba 1.1 }
3543    
3544 wakaba 1.3 splice @{$self->{open_elements}}, $i;
3545 wakaba 1.1
3546     $clear_up_to_marker->();
3547    
3548 wakaba 1.3 $self->{insertion_mode} = 'in table';
3549 wakaba 1.1
3550     !!!next-token;
3551     redo B;
3552     } elsif ($token->{tag_name} eq 'table') {
3553 wakaba 1.3 !!!parse-error (type => 'not closed:caption');
3554 wakaba 1.1
3555     ## As if </caption>
3556     ## have a table element in table scope
3557     my $i;
3558 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3559     my $node = $self->{open_elements}->[$_];
3560 wakaba 1.1 if ($node->[1] eq 'caption') {
3561     $i = $_;
3562     last INSCOPE;
3563     } elsif ({
3564     table => 1, html => 1,
3565     }->{$node->[1]}) {
3566     last INSCOPE;
3567     }
3568     } # INSCOPE
3569     unless (defined $i) {
3570 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:caption');
3571 wakaba 1.1 ## Ignore the token
3572     !!!next-token;
3573     redo B;
3574     }
3575    
3576     ## generate implied end tags
3577     if ({
3578     dd => 1, dt => 1, li => 1, p => 1,
3579     td => 1, th => 1, tr => 1,
3580 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
3581 wakaba 1.1 !!!back-token; # </table>
3582     $token = {type => 'end tag', tag_name => 'caption'};
3583     !!!back-token;
3584     $token = {type => 'end tag',
3585 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3586 wakaba 1.1 redo B;
3587     }
3588    
3589 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne 'caption') {
3590     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3591 wakaba 1.1 }
3592    
3593 wakaba 1.3 splice @{$self->{open_elements}}, $i;
3594 wakaba 1.1
3595     $clear_up_to_marker->();
3596    
3597 wakaba 1.3 $self->{insertion_mode} = 'in table';
3598 wakaba 1.1
3599     ## reprocess
3600     redo B;
3601     } elsif ({
3602     body => 1, col => 1, colgroup => 1,
3603     html => 1, tbody => 1, td => 1, tfoot => 1,
3604     th => 1, thead => 1, tr => 1,
3605     }->{$token->{tag_name}}) {
3606 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3607 wakaba 1.1 ## Ignore the token
3608     redo B;
3609     } else {
3610     #
3611     }
3612     } else {
3613     #
3614     }
3615    
3616     $in_body->($insert_to_current);
3617     redo B;
3618 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'in column group') {
3619 wakaba 1.1 if ($token->{type} eq 'character') {
3620     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
3621 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
3622 wakaba 1.1 unless (length $token->{data}) {
3623     !!!next-token;
3624     redo B;
3625     }
3626     }
3627    
3628     #
3629     } elsif ($token->{type} eq 'comment') {
3630     my $comment = $self->{document}->create_comment ($token->{data});
3631 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
3632 wakaba 1.1 !!!next-token;
3633     redo B;
3634     } elsif ($token->{type} eq 'start tag') {
3635     if ($token->{tag_name} eq 'col') {
3636     !!!insert-element ($token->{tag_name}, $token->{attributes});
3637 wakaba 1.3 pop @{$self->{open_elements}};
3638 wakaba 1.1 !!!next-token;
3639     redo B;
3640     } else {
3641     #
3642     }
3643     } elsif ($token->{type} eq 'end tag') {
3644     if ($token->{tag_name} eq 'colgroup') {
3645 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] eq 'html') {
3646     !!!parse-error (type => 'unmatched end tag:colgroup');
3647 wakaba 1.1 ## Ignore the token
3648     !!!next-token;
3649     redo B;
3650     } else {
3651 wakaba 1.3 pop @{$self->{open_elements}}; # colgroup
3652     $self->{insertion_mode} = 'in table';
3653 wakaba 1.1 !!!next-token;
3654     redo B;
3655     }
3656     } elsif ($token->{tag_name} eq 'col') {
3657 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:col');
3658 wakaba 1.1 ## Ignore the token
3659     !!!next-token;
3660     redo B;
3661     } else {
3662     #
3663     }
3664     } else {
3665     #
3666     }
3667    
3668     ## As if </colgroup>
3669 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] eq 'html') {
3670     !!!parse-error (type => 'unmatched end tag:colgroup');
3671 wakaba 1.1 ## Ignore the token
3672     !!!next-token;
3673     redo B;
3674     } else {
3675 wakaba 1.3 pop @{$self->{open_elements}}; # colgroup
3676     $self->{insertion_mode} = 'in table';
3677 wakaba 1.1 ## reprocess
3678     redo B;
3679     }
3680 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'in table body') {
3681 wakaba 1.1 if ($token->{type} eq 'character') {
3682     ## NOTE: This is a "character in table" code clone.
3683     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
3684 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
3685 wakaba 1.1
3686     unless (length $token->{data}) {
3687     !!!next-token;
3688     redo B;
3689     }
3690     }
3691    
3692 wakaba 1.3 !!!parse-error (type => 'in table:#character');
3693    
3694 wakaba 1.1 ## As if in body, but insert into foster parent element
3695     ## ISSUE: Spec says that "whenever a node would be inserted
3696     ## into the current node" while characters might not be
3697     ## result in a new Text node.
3698     $reconstruct_active_formatting_elements->($insert_to_foster);
3699    
3700     if ({
3701     table => 1, tbody => 1, tfoot => 1,
3702     thead => 1, tr => 1,
3703 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
3704 wakaba 1.1 # MUST
3705     my $foster_parent_element;
3706     my $next_sibling;
3707     my $prev_sibling;
3708 wakaba 1.3 OE: for (reverse 0..$#{$self->{open_elements}}) {
3709     if ($self->{open_elements}->[$_]->[1] eq 'table') {
3710     my $parent = $self->{open_elements}->[$_]->[0]->parent_node;
3711 wakaba 1.1 if (defined $parent and $parent->node_type == 1) {
3712     $foster_parent_element = $parent;
3713 wakaba 1.3 $next_sibling = $self->{open_elements}->[$_]->[0];
3714 wakaba 1.1 $prev_sibling = $next_sibling->previous_sibling;
3715     } else {
3716 wakaba 1.3 $foster_parent_element = $self->{open_elements}->[$_ - 1]->[0];
3717 wakaba 1.1 $prev_sibling = $foster_parent_element->last_child;
3718     }
3719     last OE;
3720     }
3721     } # OE
3722 wakaba 1.3 $foster_parent_element = $self->{open_elements}->[0]->[0] and
3723 wakaba 1.1 $prev_sibling = $foster_parent_element->last_child
3724     unless defined $foster_parent_element;
3725     if (defined $prev_sibling and
3726     $prev_sibling->node_type == 3) {
3727     $prev_sibling->manakai_append_text ($token->{data});
3728     } else {
3729     $foster_parent_element->insert_before
3730     ($self->{document}->create_text_node ($token->{data}),
3731     $next_sibling);
3732     }
3733     } else {
3734 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
3735 wakaba 1.1 }
3736    
3737     !!!next-token;
3738     redo B;
3739     } elsif ($token->{type} eq 'comment') {
3740     ## Copied from 'in table'
3741     my $comment = $self->{document}->create_comment ($token->{data});
3742 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
3743 wakaba 1.1 !!!next-token;
3744     redo B;
3745     } elsif ($token->{type} eq 'start tag') {
3746     if ({
3747     tr => 1,
3748     th => 1, td => 1,
3749     }->{$token->{tag_name}}) {
3750 wakaba 1.3 unless ($token->{tag_name} eq 'tr') {
3751     !!!parse-error (type => 'missing start tag:tr');
3752     }
3753    
3754 wakaba 1.1 ## Clear back to table body context
3755     while (not {
3756     tbody => 1, tfoot => 1, thead => 1, html => 1,
3757 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
3758     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3759     pop @{$self->{open_elements}};
3760 wakaba 1.1 }
3761    
3762 wakaba 1.3 $self->{insertion_mode} = 'in row';
3763 wakaba 1.1 if ($token->{tag_name} eq 'tr') {
3764     !!!insert-element ($token->{tag_name}, $token->{attributes});
3765     !!!next-token;
3766     } else {
3767     !!!insert-element ('tr');
3768     ## reprocess
3769     }
3770     redo B;
3771     } elsif ({
3772     caption => 1, col => 1, colgroup => 1,
3773     tbody => 1, tfoot => 1, thead => 1,
3774     }->{$token->{tag_name}}) {
3775     ## have an element in table scope
3776     my $i;
3777 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3778     my $node = $self->{open_elements}->[$_];
3779 wakaba 1.1 if ({
3780     tbody => 1, thead => 1, tfoot => 1,
3781     }->{$node->[1]}) {
3782     $i = $_;
3783     last INSCOPE;
3784     } elsif ({
3785     table => 1, html => 1,
3786     }->{$node->[1]}) {
3787     last INSCOPE;
3788     }
3789     } # INSCOPE
3790     unless (defined $i) {
3791 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3792 wakaba 1.1 ## Ignore the token
3793     !!!next-token;
3794     redo B;
3795     }
3796    
3797     ## Clear back to table body context
3798     while (not {
3799     tbody => 1, tfoot => 1, thead => 1, html => 1,
3800 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
3801     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3802     pop @{$self->{open_elements}};
3803 wakaba 1.1 }
3804    
3805     ## As if <{current node}>
3806     ## have an element in table scope
3807     ## true by definition
3808    
3809     ## Clear back to table body context
3810     ## nop by definition
3811    
3812 wakaba 1.3 pop @{$self->{open_elements}};
3813     $self->{insertion_mode} = 'in table';
3814 wakaba 1.1 ## reprocess
3815     redo B;
3816     } elsif ($token->{tag_name} eq 'table') {
3817     ## NOTE: This is a code clone of "table in table"
3818 wakaba 1.3 !!!parse-error (type => 'not closed:table');
3819 wakaba 1.1
3820     ## As if </table>
3821     ## have a table element in table scope
3822     my $i;
3823 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3824     my $node = $self->{open_elements}->[$_];
3825 wakaba 1.1 if ($node->[1] eq 'table') {
3826     $i = $_;
3827     last INSCOPE;
3828     } elsif ({
3829     table => 1, html => 1,
3830     }->{$node->[1]}) {
3831     last INSCOPE;
3832     }
3833     } # INSCOPE
3834     unless (defined $i) {
3835 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:table');
3836 wakaba 1.1 ## Ignore tokens </table><table>
3837     !!!next-token;
3838     redo B;
3839     }
3840    
3841     ## generate implied end tags
3842     if ({
3843     dd => 1, dt => 1, li => 1, p => 1,
3844     td => 1, th => 1, tr => 1,
3845 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
3846 wakaba 1.1 !!!back-token; # <table>
3847     $token = {type => 'end tag', tag_name => 'table'};
3848     !!!back-token;
3849     $token = {type => 'end tag',
3850 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3851 wakaba 1.1 redo B;
3852     }
3853    
3854 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne 'table') {
3855     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3856 wakaba 1.1 }
3857    
3858 wakaba 1.3 splice @{$self->{open_elements}}, $i;
3859 wakaba 1.1
3860 wakaba 1.3 $self->_reset_insertion_mode;
3861 wakaba 1.1
3862     ## reprocess
3863     redo B;
3864     } else {
3865     #
3866     }
3867     } elsif ($token->{type} eq 'end tag') {
3868     if ({
3869     tbody => 1, tfoot => 1, thead => 1,
3870     }->{$token->{tag_name}}) {
3871     ## have an element in table scope
3872     my $i;
3873 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3874     my $node = $self->{open_elements}->[$_];
3875 wakaba 1.1 if ($node->[1] eq $token->{tag_name}) {
3876     $i = $_;
3877     last INSCOPE;
3878     } elsif ({
3879     table => 1, html => 1,
3880     }->{$node->[1]}) {
3881     last INSCOPE;
3882     }
3883     } # INSCOPE
3884     unless (defined $i) {
3885 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3886 wakaba 1.1 ## Ignore the token
3887     !!!next-token;
3888     redo B;
3889     }
3890    
3891     ## Clear back to table body context
3892     while (not {
3893     tbody => 1, tfoot => 1, thead => 1, html => 1,
3894 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
3895     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3896     pop @{$self->{open_elements}};
3897 wakaba 1.1 }
3898    
3899 wakaba 1.3 pop @{$self->{open_elements}};
3900     $self->{insertion_mode} = 'in table';
3901 wakaba 1.1 !!!next-token;
3902     redo B;
3903     } elsif ($token->{tag_name} eq 'table') {
3904     ## have an element in table scope
3905     my $i;
3906 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3907     my $node = $self->{open_elements}->[$_];
3908 wakaba 1.1 if ({
3909     tbody => 1, thead => 1, tfoot => 1,
3910     }->{$node->[1]}) {
3911     $i = $_;
3912     last INSCOPE;
3913     } elsif ({
3914     table => 1, html => 1,
3915     }->{$node->[1]}) {
3916     last INSCOPE;
3917     }
3918     } # INSCOPE
3919     unless (defined $i) {
3920 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3921 wakaba 1.1 ## Ignore the token
3922     !!!next-token;
3923     redo B;
3924     }
3925    
3926     ## Clear back to table body context
3927     while (not {
3928     tbody => 1, tfoot => 1, thead => 1, html => 1,
3929 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
3930     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3931     pop @{$self->{open_elements}};
3932 wakaba 1.1 }
3933    
3934     ## As if <{current node}>
3935     ## have an element in table scope
3936     ## true by definition
3937    
3938     ## Clear back to table body context
3939     ## nop by definition
3940    
3941 wakaba 1.3 pop @{$self->{open_elements}};
3942     $self->{insertion_mode} = 'in table';
3943 wakaba 1.1 ## reprocess
3944     redo B;
3945     } elsif ({
3946     body => 1, caption => 1, col => 1, colgroup => 1,
3947     html => 1, td => 1, th => 1, tr => 1,
3948     }->{$token->{tag_name}}) {
3949 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
3950 wakaba 1.1 ## Ignore the token
3951     !!!next-token;
3952     redo B;
3953     } else {
3954     #
3955     }
3956     } else {
3957     #
3958     }
3959    
3960     ## As if in table
3961 wakaba 1.3 !!!parse-error (type => 'in table:'.$token->{tag_name});
3962 wakaba 1.1 $in_body->($insert_to_foster);
3963     redo B;
3964 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'in row') {
3965 wakaba 1.1 if ($token->{type} eq 'character') {
3966     ## NOTE: This is a "character in table" code clone.
3967     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
3968 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
3969 wakaba 1.1
3970     unless (length $token->{data}) {
3971     !!!next-token;
3972     redo B;
3973     }
3974     }
3975    
3976 wakaba 1.3 !!!parse-error (type => 'in table:#character');
3977    
3978 wakaba 1.1 ## As if in body, but insert into foster parent element
3979     ## ISSUE: Spec says that "whenever a node would be inserted
3980     ## into the current node" while characters might not be
3981     ## result in a new Text node.
3982     $reconstruct_active_formatting_elements->($insert_to_foster);
3983    
3984     if ({
3985     table => 1, tbody => 1, tfoot => 1,
3986     thead => 1, tr => 1,
3987 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
3988 wakaba 1.1 # MUST
3989     my $foster_parent_element;
3990     my $next_sibling;
3991     my $prev_sibling;
3992 wakaba 1.3 OE: for (reverse 0..$#{$self->{open_elements}}) {
3993     if ($self->{open_elements}->[$_]->[1] eq 'table') {
3994     my $parent = $self->{open_elements}->[$_]->[0]->parent_node;
3995 wakaba 1.1 if (defined $parent and $parent->node_type == 1) {
3996     $foster_parent_element = $parent;
3997 wakaba 1.3 $next_sibling = $self->{open_elements}->[$_]->[0];
3998 wakaba 1.1 $prev_sibling = $next_sibling->previous_sibling;
3999     } else {
4000 wakaba 1.3 $foster_parent_element = $self->{open_elements}->[$_ - 1]->[0];
4001 wakaba 1.1 $prev_sibling = $foster_parent_element->last_child;
4002     }
4003     last OE;
4004     }
4005     } # OE
4006 wakaba 1.3 $foster_parent_element = $self->{open_elements}->[0]->[0] and
4007 wakaba 1.1 $prev_sibling = $foster_parent_element->last_child
4008     unless defined $foster_parent_element;
4009     if (defined $prev_sibling and
4010     $prev_sibling->node_type == 3) {
4011     $prev_sibling->manakai_append_text ($token->{data});
4012     } else {
4013     $foster_parent_element->insert_before
4014     ($self->{document}->create_text_node ($token->{data}),
4015     $next_sibling);
4016     }
4017     } else {
4018 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
4019 wakaba 1.1 }
4020    
4021     !!!next-token;
4022     redo B;
4023     } elsif ($token->{type} eq 'comment') {
4024     ## Copied from 'in table'
4025     my $comment = $self->{document}->create_comment ($token->{data});
4026 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
4027 wakaba 1.1 !!!next-token;
4028     redo B;
4029     } elsif ($token->{type} eq 'start tag') {
4030     if ($token->{tag_name} eq 'th' or
4031     $token->{tag_name} eq 'td') {
4032     ## Clear back to table row context
4033     while (not {
4034     tr => 1, html => 1,
4035 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
4036     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4037     pop @{$self->{open_elements}};
4038 wakaba 1.1 }
4039    
4040     !!!insert-element ($token->{tag_name}, $token->{attributes});
4041 wakaba 1.3 $self->{insertion_mode} = 'in cell';
4042 wakaba 1.1
4043     push @$active_formatting_elements, ['#marker', ''];
4044    
4045     !!!next-token;
4046     redo B;
4047     } elsif ({
4048     caption => 1, col => 1, colgroup => 1,
4049     tbody => 1, tfoot => 1, thead => 1, tr => 1,
4050     }->{$token->{tag_name}}) {
4051     ## As if </tr>
4052     ## have an element in table scope
4053     my $i;
4054 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4055     my $node = $self->{open_elements}->[$_];
4056 wakaba 1.1 if ($node->[1] eq 'tr') {
4057     $i = $_;
4058     last INSCOPE;
4059     } elsif ({
4060     table => 1, html => 1,
4061     }->{$node->[1]}) {
4062     last INSCOPE;
4063     }
4064     } # INSCOPE
4065     unless (defined $i) {
4066 wakaba 1.3 !!!parse-error (type => 'unmacthed end tag:'.$token->{tag_name});
4067 wakaba 1.1 ## Ignore the token
4068     !!!next-token;
4069     redo B;
4070     }
4071    
4072     ## Clear back to table row context
4073     while (not {
4074     tr => 1, html => 1,
4075 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
4076     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4077     pop @{$self->{open_elements}};
4078 wakaba 1.1 }
4079    
4080 wakaba 1.3 pop @{$self->{open_elements}}; # tr
4081     $self->{insertion_mode} = 'in table body';
4082 wakaba 1.1 ## reprocess
4083     redo B;
4084     } elsif ($token->{tag_name} eq 'table') {
4085     ## NOTE: This is a code clone of "table in table"
4086 wakaba 1.3 !!!parse-error (type => 'not closed:table');
4087 wakaba 1.1
4088     ## As if </table>
4089     ## have a table element in table scope
4090     my $i;
4091 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4092     my $node = $self->{open_elements}->[$_];
4093 wakaba 1.1 if ($node->[1] eq 'table') {
4094     $i = $_;
4095     last INSCOPE;
4096     } elsif ({
4097     table => 1, html => 1,
4098     }->{$node->[1]}) {
4099     last INSCOPE;
4100     }
4101     } # INSCOPE
4102     unless (defined $i) {
4103 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:table');
4104 wakaba 1.1 ## Ignore tokens </table><table>
4105     !!!next-token;
4106     redo B;
4107     }
4108    
4109     ## generate implied end tags
4110     if ({
4111     dd => 1, dt => 1, li => 1, p => 1,
4112     td => 1, th => 1, tr => 1,
4113 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
4114 wakaba 1.1 !!!back-token; # <table>
4115     $token = {type => 'end tag', tag_name => 'table'};
4116     !!!back-token;
4117     $token = {type => 'end tag',
4118 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
4119 wakaba 1.1 redo B;
4120     }
4121    
4122 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne 'table') {
4123     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4124 wakaba 1.1 }
4125    
4126 wakaba 1.3 splice @{$self->{open_elements}}, $i;
4127 wakaba 1.1
4128 wakaba 1.3 $self->_reset_insertion_mode;
4129 wakaba 1.1
4130     ## reprocess
4131     redo B;
4132     } else {
4133     #
4134     }
4135     } elsif ($token->{type} eq 'end tag') {
4136     if ($token->{tag_name} eq 'tr') {
4137     ## have an element in table scope
4138     my $i;
4139 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4140     my $node = $self->{open_elements}->[$_];
4141 wakaba 1.1 if ($node->[1] eq $token->{tag_name}) {
4142     $i = $_;
4143     last INSCOPE;
4144     } elsif ({
4145     table => 1, html => 1,
4146     }->{$node->[1]}) {
4147     last INSCOPE;
4148     }
4149     } # INSCOPE
4150     unless (defined $i) {
4151 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4152 wakaba 1.1 ## Ignore the token
4153     !!!next-token;
4154     redo B;
4155     }
4156    
4157     ## Clear back to table row context
4158     while (not {
4159     tr => 1, html => 1,
4160 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
4161     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4162     pop @{$self->{open_elements}};
4163 wakaba 1.1 }
4164    
4165 wakaba 1.3 pop @{$self->{open_elements}}; # tr
4166     $self->{insertion_mode} = 'in table body';
4167 wakaba 1.1 !!!next-token;
4168     redo B;
4169     } elsif ($token->{tag_name} eq 'table') {
4170     ## As if </tr>
4171     ## have an element in table scope
4172     my $i;
4173 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4174     my $node = $self->{open_elements}->[$_];
4175 wakaba 1.1 if ($node->[1] eq 'tr') {
4176     $i = $_;
4177     last INSCOPE;
4178     } elsif ({
4179     table => 1, html => 1,
4180     }->{$node->[1]}) {
4181     last INSCOPE;
4182     }
4183     } # INSCOPE
4184     unless (defined $i) {
4185 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{type});
4186 wakaba 1.1 ## Ignore the token
4187     !!!next-token;
4188     redo B;
4189     }
4190    
4191     ## Clear back to table row context
4192     while (not {
4193     tr => 1, html => 1,
4194 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
4195     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4196     pop @{$self->{open_elements}};
4197 wakaba 1.1 }
4198    
4199 wakaba 1.3 pop @{$self->{open_elements}}; # tr
4200     $self->{insertion_mode} = 'in table body';
4201 wakaba 1.1 ## reprocess
4202     redo B;
4203     } elsif ({
4204     tbody => 1, tfoot => 1, thead => 1,
4205     }->{$token->{tag_name}}) {
4206     ## have an element in table scope
4207     my $i;
4208 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4209     my $node = $self->{open_elements}->[$_];
4210 wakaba 1.1 if ($node->[1] eq $token->{tag_name}) {
4211     $i = $_;
4212     last INSCOPE;
4213     } elsif ({
4214     table => 1, html => 1,
4215     }->{$node->[1]}) {
4216     last INSCOPE;
4217     }
4218     } # INSCOPE
4219     unless (defined $i) {
4220 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4221 wakaba 1.1 ## Ignore the token
4222     !!!next-token;
4223     redo B;
4224     }
4225    
4226     ## As if </tr>
4227     ## have an element in table scope
4228     my $i;
4229 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4230     my $node = $self->{open_elements}->[$_];
4231 wakaba 1.1 if ($node->[1] eq 'tr') {
4232     $i = $_;
4233     last INSCOPE;
4234     } elsif ({
4235     table => 1, html => 1,
4236     }->{$node->[1]}) {
4237     last INSCOPE;
4238     }
4239     } # INSCOPE
4240     unless (defined $i) {
4241 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:tr');
4242 wakaba 1.1 ## Ignore the token
4243     !!!next-token;
4244     redo B;
4245     }
4246    
4247     ## Clear back to table row context
4248     while (not {
4249     tr => 1, html => 1,
4250 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
4251     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4252     pop @{$self->{open_elements}};
4253 wakaba 1.1 }
4254    
4255 wakaba 1.3 pop @{$self->{open_elements}}; # tr
4256     $self->{insertion_mode} = 'in table body';
4257 wakaba 1.1 ## reprocess
4258     redo B;
4259     } elsif ({
4260     body => 1, caption => 1, col => 1,
4261     colgroup => 1, html => 1, td => 1, th => 1,
4262     }->{$token->{tag_name}}) {
4263 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4264 wakaba 1.1 ## Ignore the token
4265     !!!next-token;
4266     redo B;
4267     } else {
4268     #
4269     }
4270     } else {
4271     #
4272     }
4273    
4274     ## As if in table
4275 wakaba 1.3 !!!parse-error (type => 'in table:'.$token->{tag_name});
4276 wakaba 1.1 $in_body->($insert_to_foster);
4277     redo B;
4278 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'in cell') {
4279 wakaba 1.1 if ($token->{type} eq 'character') {
4280     ## NOTE: This is a code clone of "character in body".
4281     $reconstruct_active_formatting_elements->($insert_to_current);
4282    
4283 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
4284 wakaba 1.1
4285     !!!next-token;
4286     redo B;
4287     } elsif ($token->{type} eq 'comment') {
4288     ## NOTE: This is a code clone of "comment in body".
4289     my $comment = $self->{document}->create_comment ($token->{data});
4290 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
4291 wakaba 1.1 !!!next-token;
4292     redo B;
4293     } elsif ($token->{type} eq 'start tag') {
4294     if ({
4295     caption => 1, col => 1, colgroup => 1,
4296     tbody => 1, td => 1, tfoot => 1, th => 1,
4297     thead => 1, tr => 1,
4298     }->{$token->{tag_name}}) {
4299     ## have an element in table scope
4300     my $tn;
4301 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4302     my $node = $self->{open_elements}->[$_];
4303 wakaba 1.1 if ($node->[1] eq 'td' or $node->[1] eq 'th') {
4304     $tn = $node->[1];
4305     last INSCOPE;
4306     } elsif ({
4307     table => 1, html => 1,
4308     }->{$node->[1]}) {
4309     last INSCOPE;
4310     }
4311     } # INSCOPE
4312     unless (defined $tn) {
4313 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4314 wakaba 1.1 ## Ignore the token
4315     !!!next-token;
4316     redo B;
4317     }
4318    
4319     ## Close the cell
4320     !!!back-token; # <?>
4321     $token = {type => 'end tag', tag_name => $tn};
4322     redo B;
4323     } else {
4324     #
4325     }
4326     } elsif ($token->{type} eq 'end tag') {
4327     if ($token->{tag_name} eq 'td' or $token->{tag_name} eq 'th') {
4328     ## have an element in table scope
4329     my $i;
4330 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4331     my $node = $self->{open_elements}->[$_];
4332 wakaba 1.1 if ($node->[1] eq $token->{tag_name}) {
4333     $i = $_;
4334     last INSCOPE;
4335     } elsif ({
4336     table => 1, html => 1,
4337     }->{$node->[1]}) {
4338     last INSCOPE;
4339     }
4340     } # INSCOPE
4341     unless (defined $i) {
4342 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4343 wakaba 1.1 ## Ignore the token
4344     !!!next-token;
4345     redo B;
4346     }
4347    
4348     ## generate implied end tags
4349     if ({
4350     dd => 1, dt => 1, li => 1, p => 1,
4351     td => ($token->{tag_name} eq 'th'),
4352     th => ($token->{tag_name} eq 'td'),
4353     tr => 1,
4354 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
4355 wakaba 1.1 !!!back-token;
4356     $token = {type => 'end tag',
4357 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
4358 wakaba 1.1 redo B;
4359     }
4360    
4361 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne $token->{tag_name}) {
4362     !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4363 wakaba 1.1 }
4364    
4365 wakaba 1.3 splice @{$self->{open_elements}}, $i;
4366 wakaba 1.1
4367     $clear_up_to_marker->();
4368    
4369 wakaba 1.3 $self->{insertion_mode} = 'in row';
4370 wakaba 1.1
4371     !!!next-token;
4372     redo B;
4373     } elsif ({
4374     body => 1, caption => 1, col => 1,
4375     colgroup => 1, html => 1,
4376     }->{$token->{tag_name}}) {
4377 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4378 wakaba 1.1 ## Ignore the token
4379     !!!next-token;
4380     redo B;
4381     } elsif ({
4382     table => 1, tbody => 1, tfoot => 1,
4383     thead => 1, tr => 1,
4384     }->{$token->{tag_name}}) {
4385     ## have an element in table scope
4386     my $i;
4387     my $tn;
4388 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4389     my $node = $self->{open_elements}->[$_];
4390 wakaba 1.1 if ($node->[1] eq $token->{tag_name}) {
4391     $i = $_;
4392     last INSCOPE;
4393     } elsif ($node->[1] eq 'td' or $node->[1] eq 'th') {
4394     $tn = $node->[1];
4395     ## NOTE: There is exactly one |td| or |th| element
4396     ## in scope in the stack of open elements by definition.
4397     } elsif ({
4398     table => 1, html => 1,
4399     }->{$node->[1]}) {
4400     last INSCOPE;
4401     }
4402     } # INSCOPE
4403     unless (defined $i) {
4404 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4405 wakaba 1.1 ## Ignore the token
4406     !!!next-token;
4407     redo B;
4408     }
4409    
4410     ## Close the cell
4411     !!!back-token; # </?>
4412     $token = {type => 'end tag', tag_name => $tn};
4413     redo B;
4414     } else {
4415     #
4416     }
4417     } else {
4418     #
4419     }
4420    
4421     $in_body->($insert_to_current);
4422     redo B;
4423 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'in select') {
4424 wakaba 1.1 if ($token->{type} eq 'character') {
4425 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
4426 wakaba 1.1 !!!next-token;
4427     redo B;
4428     } elsif ($token->{type} eq 'comment') {
4429     my $comment = $self->{document}->create_comment ($token->{data});
4430 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
4431 wakaba 1.1 !!!next-token;
4432     redo B;
4433     } elsif ($token->{type} eq 'start tag') {
4434     if ($token->{tag_name} eq 'option') {
4435 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] eq 'option') {
4436 wakaba 1.1 ## As if </option>
4437 wakaba 1.3 pop @{$self->{open_elements}};
4438 wakaba 1.1 }
4439    
4440     !!!insert-element ($token->{tag_name}, $token->{attributes});
4441     !!!next-token;
4442     redo B;
4443     } elsif ($token->{tag_name} eq 'optgroup') {
4444 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] eq 'option') {
4445 wakaba 1.1 ## As if </option>
4446 wakaba 1.3 pop @{$self->{open_elements}};
4447 wakaba 1.1 }
4448    
4449 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] eq 'optgroup') {
4450 wakaba 1.1 ## As if </optgroup>
4451 wakaba 1.3 pop @{$self->{open_elements}};
4452 wakaba 1.1 }
4453    
4454     !!!insert-element ($token->{tag_name}, $token->{attributes});
4455     !!!next-token;
4456     redo B;
4457     } elsif ($token->{tag_name} eq 'select') {
4458 wakaba 1.3 !!!parse-error (type => 'not closed:select');
4459 wakaba 1.1 ## As if </select> instead
4460     ## have an element in table scope
4461     my $i;
4462 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4463     my $node = $self->{open_elements}->[$_];
4464 wakaba 1.1 if ($node->[1] eq $token->{tag_name}) {
4465     $i = $_;
4466     last INSCOPE;
4467     } elsif ({
4468     table => 1, html => 1,
4469     }->{$node->[1]}) {
4470     last INSCOPE;
4471     }
4472     } # INSCOPE
4473     unless (defined $i) {
4474 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:select');
4475 wakaba 1.1 ## Ignore the token
4476     !!!next-token;
4477     redo B;
4478     }
4479    
4480 wakaba 1.3 splice @{$self->{open_elements}}, $i;
4481 wakaba 1.1
4482 wakaba 1.3 $self->_reset_insertion_mode;
4483 wakaba 1.1
4484     !!!next-token;
4485     redo B;
4486     } else {
4487     #
4488     }
4489     } elsif ($token->{type} eq 'end tag') {
4490     if ($token->{tag_name} eq 'optgroup') {
4491 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] eq 'option' and
4492     $self->{open_elements}->[-2]->[1] eq 'optgroup') {
4493 wakaba 1.1 ## As if </option>
4494 wakaba 1.3 splice @{$self->{open_elements}}, -2;
4495     } elsif ($self->{open_elements}->[-1]->[1] eq 'optgroup') {
4496     pop @{$self->{open_elements}};
4497 wakaba 1.1 } else {
4498 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4499 wakaba 1.1 ## Ignore the token
4500     }
4501     !!!next-token;
4502     redo B;
4503     } elsif ($token->{tag_name} eq 'option') {
4504 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] eq 'option') {
4505     pop @{$self->{open_elements}};
4506 wakaba 1.1 } else {
4507 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4508 wakaba 1.1 ## Ignore the token
4509     }
4510     !!!next-token;
4511     redo B;
4512     } elsif ($token->{tag_name} eq 'select') {
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:'.$token->{tag_name});
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     } elsif ({
4540     caption => 1, table => 1, tbody => 1,
4541     tfoot => 1, thead => 1, tr => 1, td => 1, th => 1,
4542     }->{$token->{tag_name}}) {
4543 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4544 wakaba 1.1
4545     ## have an element in table scope
4546     my $i;
4547 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4548     my $node = $self->{open_elements}->[$_];
4549 wakaba 1.1 if ($node->[1] eq $token->{tag_name}) {
4550     $i = $_;
4551     last INSCOPE;
4552     } elsif ({
4553     table => 1, html => 1,
4554     }->{$node->[1]}) {
4555     last INSCOPE;
4556     }
4557     } # INSCOPE
4558     unless (defined $i) {
4559     ## Ignore the token
4560     !!!next-token;
4561     redo B;
4562     }
4563    
4564     ## As if </select>
4565     ## have an element in table scope
4566     undef $i;
4567 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4568     my $node = $self->{open_elements}->[$_];
4569 wakaba 1.1 if ($node->[1] eq 'select') {
4570     $i = $_;
4571     last INSCOPE;
4572     } elsif ({
4573     table => 1, html => 1,
4574     }->{$node->[1]}) {
4575     last INSCOPE;
4576     }
4577     } # INSCOPE
4578     unless (defined $i) {
4579 wakaba 1.3 !!!parse-error (type => 'unmatched end tag:select');
4580 wakaba 1.1 ## Ignore the </select> token
4581     !!!next-token; ## TODO: ok?
4582     redo B;
4583     }
4584    
4585 wakaba 1.3 splice @{$self->{open_elements}}, $i;
4586 wakaba 1.1
4587 wakaba 1.3 $self->_reset_insertion_mode;
4588 wakaba 1.1
4589     ## reprocess
4590     redo B;
4591     } else {
4592     #
4593     }
4594     } else {
4595     #
4596     }
4597    
4598 wakaba 1.3 !!!parse-error (type => 'in select:'.$token->{tag_name});
4599 wakaba 1.1 ## Ignore the token
4600     !!!next-token;
4601     redo B;
4602 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'after body') {
4603 wakaba 1.1 if ($token->{type} eq 'character') {
4604     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
4605     ## As if in body
4606     $reconstruct_active_formatting_elements->($insert_to_current);
4607    
4608 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
4609 wakaba 1.1
4610     unless (length $token->{data}) {
4611     !!!next-token;
4612     redo B;
4613     }
4614     }
4615    
4616     #
4617 wakaba 1.3 !!!parse-error (type => 'after body:#'.$token->{type});
4618 wakaba 1.1 } elsif ($token->{type} eq 'comment') {
4619     my $comment = $self->{document}->create_comment ($token->{data});
4620 wakaba 1.3 $self->{open_elements}->[0]->[0]->append_child ($comment);
4621 wakaba 1.1 !!!next-token;
4622     redo B;
4623 wakaba 1.3 } elsif ($token->{type} eq 'start tag') {
4624     !!!parse-error (type => 'after body:'.$token->{tag_name});
4625     #
4626 wakaba 1.1 } elsif ($token->{type} eq 'end tag') {
4627     if ($token->{tag_name} eq 'html') {
4628 wakaba 1.3 if (defined $self->{inner_html_node}) {
4629     !!!parse-error (type => 'unmatched end tag:html');
4630     ## Ignore the token
4631     !!!next-token;
4632     redo B;
4633     } else {
4634     $phase = 'trailing end';
4635     !!!next-token;
4636     redo B;
4637     }
4638 wakaba 1.1 } else {
4639 wakaba 1.3 !!!parse-error (type => 'after body:/'.$token->{tag_name});
4640 wakaba 1.1 }
4641     } else {
4642 wakaba 1.3 !!!parse-error (type => 'after body:#'.$token->{type});
4643 wakaba 1.1 }
4644    
4645 wakaba 1.3 $self->{insertion_mode} = 'in body';
4646 wakaba 1.1 ## reprocess
4647     redo B;
4648 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'in frameset') {
4649 wakaba 1.1 if ($token->{type} eq 'character') {
4650     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
4651 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
4652 wakaba 1.1
4653     unless (length $token->{data}) {
4654     !!!next-token;
4655     redo B;
4656     }
4657     }
4658    
4659     #
4660     } elsif ($token->{type} eq 'comment') {
4661     my $comment = $self->{document}->create_comment ($token->{data});
4662 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
4663 wakaba 1.1 !!!next-token;
4664     redo B;
4665     } elsif ($token->{type} eq 'start tag') {
4666     if ($token->{tag_name} eq 'frameset') {
4667     !!!insert-element ($token->{tag_name}, $token->{attributes});
4668     !!!next-token;
4669     redo B;
4670     } elsif ($token->{tag_name} eq 'frame') {
4671     !!!insert-element ($token->{tag_name}, $token->{attributes});
4672 wakaba 1.3 pop @{$self->{open_elements}};
4673 wakaba 1.1 !!!next-token;
4674     redo B;
4675     } elsif ($token->{tag_name} eq 'noframes') {
4676     $in_body->($insert_to_current);
4677     redo B;
4678     } else {
4679     #
4680     }
4681     } elsif ($token->{type} eq 'end tag') {
4682     if ($token->{tag_name} eq 'frameset') {
4683 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] eq 'html' and
4684     @{$self->{open_elements}} == 1) {
4685     !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});
4686 wakaba 1.1 ## Ignore the token
4687     !!!next-token;
4688     } else {
4689 wakaba 1.3 pop @{$self->{open_elements}};
4690 wakaba 1.1 !!!next-token;
4691     }
4692    
4693     ## if not inner_html and
4694 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne 'frameset') {
4695     $self->{insertion_mode} = 'after frameset';
4696 wakaba 1.1 }
4697     redo B;
4698     } else {
4699     #
4700     }
4701     } else {
4702     #
4703     }
4704    
4705 wakaba 1.3 if (defined $token->{tag_name}) {
4706     !!!parse-error (type => 'in frameset:'.$token->{tag_name});
4707     } else {
4708     !!!parse-error (type => 'in frameset:#'.$token->{type});
4709     }
4710 wakaba 1.1 ## Ignore the token
4711     !!!next-token;
4712     redo B;
4713 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'after frameset') {
4714 wakaba 1.1 if ($token->{type} eq 'character') {
4715     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
4716 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
4717 wakaba 1.1
4718     unless (length $token->{data}) {
4719     !!!next-token;
4720     redo B;
4721     }
4722     }
4723    
4724     #
4725     } elsif ($token->{type} eq 'comment') {
4726     my $comment = $self->{document}->create_comment ($token->{data});
4727 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
4728 wakaba 1.1 !!!next-token;
4729     redo B;
4730     } elsif ($token->{type} eq 'start tag') {
4731     if ($token->{tag_name} eq 'noframes') {
4732     $in_body->($insert_to_current);
4733     redo B;
4734     } else {
4735     #
4736     }
4737     } elsif ($token->{type} eq 'end tag') {
4738     if ($token->{tag_name} eq 'html') {
4739     $phase = 'trailing end';
4740     !!!next-token;
4741     redo B;
4742     } else {
4743     #
4744     }
4745     } else {
4746     #
4747     }
4748    
4749 wakaba 1.3 if (defined $token->{tag_name}) {
4750     !!!parse-error (type => 'after frameset:'.$token->{tag_name});
4751     } else {
4752     !!!parse-error (type => 'after frameset:#'.$token->{type});
4753     }
4754 wakaba 1.1 ## Ignore the token
4755     !!!next-token;
4756     redo B;
4757    
4758     ## ISSUE: An issue in spec there
4759     } else {
4760 wakaba 1.3 die "$0: $self->{insertion_mode}: Unknown insertion mode";
4761 wakaba 1.1 }
4762     }
4763     } elsif ($phase eq 'trailing end') {
4764     ## states in the main stage is preserved yet # MUST
4765    
4766     if ($token->{type} eq 'DOCTYPE') {
4767 wakaba 1.3 !!!parse-error (type => 'after html:#DOCTYPE');
4768 wakaba 1.1 ## Ignore the token
4769     !!!next-token;
4770     redo B;
4771     } elsif ($token->{type} eq 'comment') {
4772     my $comment = $self->{document}->create_comment ($token->{data});
4773     $self->{document}->append_child ($comment);
4774     !!!next-token;
4775     redo B;
4776     } elsif ($token->{type} eq 'character') {
4777     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
4778     my $data = $1;
4779     ## As if in the main phase.
4780     ## NOTE: The insertion mode in the main phase
4781     ## just before the phase has been changed to the trailing
4782     ## end phase is either "after body" or "after frameset".
4783     $reconstruct_active_formatting_elements->($insert_to_current)
4784     if $phase eq 'main';
4785    
4786 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($data);
4787 wakaba 1.1
4788     unless (length $token->{data}) {
4789     !!!next-token;
4790     redo B;
4791     }
4792     }
4793    
4794 wakaba 1.3 !!!parse-error (type => 'after html:#character');
4795 wakaba 1.1 $phase = 'main';
4796     ## reprocess
4797     redo B;
4798     } elsif ($token->{type} eq 'start tag' or
4799     $token->{type} eq 'end tag') {
4800 wakaba 1.3 !!!parse-error (type => 'after html:'.$token->{tag_name});
4801 wakaba 1.1 $phase = 'main';
4802     ## reprocess
4803     redo B;
4804     } elsif ($token->{type} eq 'end-of-file') {
4805     ## Stop parsing
4806     last B;
4807     } else {
4808     die "$0: $token->{type}: Unknown token";
4809     }
4810     }
4811     } # B
4812    
4813     ## Stop parsing # MUST
4814    
4815     ## TODO: script stuffs
4816 wakaba 1.3 } # _tree_construct_main
4817    
4818     sub set_inner_html ($$$) {
4819     my $class = shift;
4820     my $node = shift;
4821     my $s = \$_[0];
4822     my $onerror = $_[1];
4823    
4824     my $nt = $node->node_type;
4825     if ($nt == 9) {
4826     # MUST
4827    
4828     ## Step 1 # MUST
4829     ## TODO: If the document has an active parser, ...
4830     ## ISSUE: There is an issue in the spec.
4831    
4832     ## Step 2 # MUST
4833     my @cn = @{$node->child_nodes};
4834     for (@cn) {
4835     $node->remove_child ($_);
4836     }
4837    
4838     ## Step 3, 4, 5 # MUST
4839     $class->parse_string ($$s => $node, $onerror);
4840     } elsif ($nt == 1) {
4841     ## TODO: If non-html element
4842    
4843     ## NOTE: Most of this code is copied from |parse_string|
4844    
4845     ## Step 1 # MUST
4846     my $doc = $node->owner_document->implementation->create_document;
4847     ## TODO: Mark as HTML document
4848     my $p = $class->new;
4849     $p->{document} = $doc;
4850    
4851     ## Step 9 # MUST
4852     my $i = 0;
4853     my $line = 1;
4854     my $column = 0;
4855     $p->{set_next_input_character} = sub {
4856     my $self = shift;
4857     $self->{next_input_character} = -1 and return if $i >= length $$s;
4858     $self->{next_input_character} = ord substr $$s, $i++, 1;
4859     $column++;
4860 wakaba 1.4
4861     if ($self->{next_input_character} == 0x000A) { # LF
4862     $line++;
4863     $column = 0;
4864     } elsif ($self->{next_input_character} == 0x000D) { # CR
4865 wakaba 1.3 if ($i >= length $$s) {
4866     #
4867     } else {
4868     my $next_char = ord substr $$s, $i++, 1;
4869     if ($next_char == 0x000A) { # LF
4870     #
4871     } else {
4872     push @{$self->{char}}, $next_char;
4873     }
4874     }
4875     $self->{next_input_character} = 0x000A; # LF # MUST
4876     $line++;
4877 wakaba 1.4 $column = 0;
4878 wakaba 1.3 } elsif ($self->{next_input_character} > 0x10FFFF) {
4879     $self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
4880     } elsif ($self->{next_input_character} == 0x0000) { # NULL
4881     $self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
4882     }
4883     };
4884    
4885     my $ponerror = $onerror || sub {
4886     my (%opt) = @_;
4887     warn "Parse error ($opt{type}) at line $opt{line} column $opt{column}\n";
4888     };
4889     $p->{parse_error} = sub {
4890     $ponerror->(@_, line => $line, column => $column);
4891     };
4892    
4893     $p->_initialize_tokenizer;
4894     $p->_initialize_tree_constructor;
4895    
4896     ## Step 2
4897     my $node_ln = $node->local_name;
4898     $p->{content_model_flag} = {
4899     title => 'RCDATA',
4900     textarea => 'RCDATA',
4901     style => 'CDATA',
4902     script => 'CDATA',
4903     xmp => 'CDATA',
4904     iframe => 'CDATA',
4905     noembed => 'CDATA',
4906     noframes => 'CDATA',
4907     noscript => 'CDATA',
4908     plaintext => 'PLAINTEXT',
4909     }->{$node_ln} || 'PCDATA';
4910     ## ISSUE: What is "the name of the element"? local name?
4911    
4912     $p->{inner_html_node} = [$node, $node_ln];
4913    
4914     ## Step 4
4915     my $root = $doc->create_element_ns
4916     ('http://www.w3.org/1999/xhtml', [undef, 'html']);
4917    
4918     ## Step 5 # MUST
4919     $doc->append_child ($root);
4920    
4921     ## Step 6 # MUST
4922     push @{$p->{open_elements}}, [$root, 'html'];
4923    
4924     undef $p->{head_element};
4925    
4926     ## Step 7 # MUST
4927     $p->_reset_insertion_mode;
4928    
4929     ## Step 8 # MUST
4930     my $anode = $node;
4931     AN: while (defined $anode) {
4932     if ($anode->node_type == 1) {
4933     my $nsuri = $anode->namespace_uri;
4934     if (defined $nsuri and $nsuri eq 'http://www.w3.org/1999/xhtml') {
4935     if ($anode->local_name eq 'form') { ## TODO: case?
4936     $p->{form_element} = $anode;
4937     last AN;
4938     }
4939     }
4940     }
4941     $anode = $anode->parent_node;
4942     } # AN
4943    
4944     ## Step 3 # MUST
4945     ## Step 10 # MUST
4946     {
4947     my $self = $p;
4948     !!!next-token;
4949     }
4950     $p->_tree_construction_main;
4951    
4952     ## Step 11 # MUST
4953     my @cn = @{$node->child_nodes};
4954     for (@cn) {
4955     $node->remove_child ($_);
4956     }
4957     ## ISSUE: mutation events? read-only?
4958    
4959     ## Step 12 # MUST
4960     @cn = @{$root->child_nodes};
4961     for (@cn) {
4962     $node->append_child ($_);
4963     }
4964     ## ISSUE: adopt_node? mutation events?
4965    
4966     $p->_terminate_tree_constructor;
4967     } else {
4968     die "$0: |set_inner_html| is not defined for node of type $nt";
4969     }
4970     } # set_inner_html
4971    
4972     } # tree construction stage
4973 wakaba 1.1
4974     sub get_inner_html ($$$) {
4975 wakaba 1.3 my (undef, $node, $on_error) = @_;
4976 wakaba 1.1
4977     ## Step 1
4978     my $s = '';
4979    
4980     my $in_cdata;
4981     my $parent = $node;
4982     while (defined $parent) {
4983     if ($parent->node_type == 1 and
4984     $parent->namespace_uri eq 'http://www.w3.org/1999/xhtml' and
4985     {
4986     style => 1, script => 1, xmp => 1, iframe => 1,
4987     noembed => 1, noframes => 1, noscript => 1,
4988     }->{$parent->local_name}) { ## TODO: case thingy
4989     $in_cdata = 1;
4990     }
4991     $parent = $parent->parent_node;
4992     }
4993    
4994     ## Step 2
4995     my @node = @{$node->child_nodes};
4996     C: while (@node) {
4997     my $child = shift @node;
4998     unless (ref $child) {
4999     if ($child eq 'cdata-out') {
5000     $in_cdata = 0;
5001     } else {
5002     $s .= $child; # end tag
5003     }
5004     next C;
5005     }
5006    
5007     my $nt = $child->node_type;
5008     if ($nt == 1) { # Element
5009     my $tag_name = lc $child->tag_name; ## ISSUE: Definition of "lowercase"
5010     $s .= '<' . $tag_name;
5011    
5012     ## ISSUE: Non-html elements
5013    
5014     my @attrs = @{$child->attributes}; # sort order MUST be stable
5015     for my $attr (@attrs) { # order is implementation dependent
5016     my $attr_name = lc $attr->name; ## ISSUE: Definition of "lowercase"
5017     $s .= ' ' . $attr_name . '="';
5018     my $attr_value = $attr->value;
5019     ## escape
5020     $attr_value =~ s/&/&amp;/g;
5021     $attr_value =~ s/</&lt;/g;
5022     $attr_value =~ s/>/&gt;/g;
5023     $attr_value =~ s/"/&quot;/g;
5024     $s .= $attr_value . '"';
5025     }
5026     $s .= '>';
5027    
5028     next C if {
5029     area => 1, base => 1, basefont => 1, bgsound => 1,
5030     br => 1, col => 1, embed => 1, frame => 1, hr => 1,
5031     img => 1, input => 1, link => 1, meta => 1, param => 1,
5032     spacer => 1, wbr => 1,
5033     }->{$tag_name};
5034    
5035     if (not $in_cdata and {
5036     style => 1, script => 1, xmp => 1, iframe => 1,
5037     noembed => 1, noframes => 1, noscript => 1,
5038     }->{$tag_name}) {
5039     unshift @node, 'cdata-out';
5040     $in_cdata = 1;
5041     }
5042    
5043     unshift @node, @{$child->child_nodes}, '</' . $tag_name . '>';
5044     } elsif ($nt == 3 or $nt == 4) {
5045     if ($in_cdata) {
5046     $s .= $child->data;
5047     } else {
5048     my $value = $child->data;
5049     $value =~ s/&/&amp;/g;
5050     $value =~ s/</&lt;/g;
5051     $value =~ s/>/&gt;/g;
5052     $value =~ s/"/&quot;/g;
5053     $s .= $value;
5054     }
5055     } elsif ($nt == 8) {
5056     $s .= '<!--' . $child->data . '-->';
5057     } elsif ($nt == 10) {
5058     $s .= '<!DOCTYPE ' . $child->name . '>';
5059     } elsif ($nt == 5) { # entrefs
5060     push @node, @{$child->child_nodes};
5061     } else {
5062     $on_error->($child) if defined $on_error;
5063     }
5064     ## ISSUE: This code does not support PIs.
5065     } # C
5066    
5067     ## Step 3
5068     return \$s;
5069     } # get_inner_html
5070    
5071     1;
5072 wakaba 1.4 # $Date: 2007/05/02 13:44:34 $

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24