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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.12 - (hide annotations) (download)
Sat Jun 23 04:38:50 2007 UTC (19 years, 1 month ago) by wakaba
Branch: MAIN
Changes since 1.11: +33 -2 lines
++ whatpm/t/ChangeLog	23 Jun 2007 04:36:28 -0000
	* tree-test-1.dat: New test for HTML5 revision 884 (</form>
	try to close its children).

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

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

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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24