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

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

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

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

	* LICENSE: New document.

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

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

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

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

1 wakaba 1.2 package Whatpm::HTML;
2 wakaba 1.1 use strict;
3 wakaba 1.4 our $VERSION=do{my @r=(q$Revision: 1.3 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
4 wakaba 1.1
5     ## This is an early version of an HTML parser.
6    
7     my $permitted_slash_tag_name = {
8     base => 1,
9     link => 1,
10     meta => 1,
11     hr => 1,
12     br => 1,
13     img=> 1,
14     embed => 1,
15     param => 1,
16     area => 1,
17     col => 1,
18     input => 1,
19     };
20    
21     my $entity_char = {
22     AElig => "\x{00C6}",
23     Aacute => "\x{00C1}",
24     Acirc => "\x{00C2}",
25     Agrave => "\x{00C0}",
26     Alpha => "\x{0391}",
27     Aring => "\x{00C5}",
28     Atilde => "\x{00C3}",
29     Auml => "\x{00C4}",
30     Beta => "\x{0392}",
31     Ccedil => "\x{00C7}",
32     Chi => "\x{03A7}",
33     Dagger => "\x{2021}",
34     Delta => "\x{0394}",
35     ETH => "\x{00D0}",
36     Eacute => "\x{00C9}",
37     Ecirc => "\x{00CA}",
38     Egrave => "\x{00C8}",
39     Epsilon => "\x{0395}",
40     Eta => "\x{0397}",
41     Euml => "\x{00CB}",
42     Gamma => "\x{0393}",
43     Iacute => "\x{00CD}",
44     Icirc => "\x{00CE}",
45     Igrave => "\x{00CC}",
46     Iota => "\x{0399}",
47     Iuml => "\x{00CF}",
48     Kappa => "\x{039A}",
49     Lambda => "\x{039B}",
50     Mu => "\x{039C}",
51     Ntilde => "\x{00D1}",
52     Nu => "\x{039D}",
53     OElig => "\x{0152}",
54     Oacute => "\x{00D3}",
55     Ocirc => "\x{00D4}",
56     Ograve => "\x{00D2}",
57     Omega => "\x{03A9}",
58     Omicron => "\x{039F}",
59     Oslash => "\x{00D8}",
60     Otilde => "\x{00D5}",
61     Ouml => "\x{00D6}",
62     Phi => "\x{03A6}",
63     Pi => "\x{03A0}",
64     Prime => "\x{2033}",
65     Psi => "\x{03A8}",
66     Rho => "\x{03A1}",
67     Scaron => "\x{0160}",
68     Sigma => "\x{03A3}",
69     THORN => "\x{00DE}",
70     Tau => "\x{03A4}",
71     Theta => "\x{0398}",
72     Uacute => "\x{00DA}",
73     Ucirc => "\x{00DB}",
74     Ugrave => "\x{00D9}",
75     Upsilon => "\x{03A5}",
76     Uuml => "\x{00DC}",
77     Xi => "\x{039E}",
78     Yacute => "\x{00DD}",
79     Yuml => "\x{0178}",
80     Zeta => "\x{0396}",
81     aacute => "\x{00E1}",
82     acirc => "\x{00E2}",
83     acute => "\x{00B4}",
84     aelig => "\x{00E6}",
85     agrave => "\x{00E0}",
86     alefsym => "\x{2135}",
87     alpha => "\x{03B1}",
88     amp => "\x{0026}",
89     AMP => "\x{0026}",
90     and => "\x{2227}",
91     ang => "\x{2220}",
92     apos => "\x{0027}",
93     aring => "\x{00E5}",
94     asymp => "\x{2248}",
95     atilde => "\x{00E3}",
96     auml => "\x{00E4}",
97     bdquo => "\x{201E}",
98     beta => "\x{03B2}",
99     brvbar => "\x{00A6}",
100     bull => "\x{2022}",
101     cap => "\x{2229}",
102     ccedil => "\x{00E7}",
103     cedil => "\x{00B8}",
104     cent => "\x{00A2}",
105     chi => "\x{03C7}",
106     circ => "\x{02C6}",
107     clubs => "\x{2663}",
108     cong => "\x{2245}",
109     copy => "\x{00A9}",
110     COPY => "\x{00A9}",
111     crarr => "\x{21B5}",
112     cup => "\x{222A}",
113     curren => "\x{00A4}",
114     dArr => "\x{21D3}",
115     dagger => "\x{2020}",
116     darr => "\x{2193}",
117     deg => "\x{00B0}",
118     delta => "\x{03B4}",
119     diams => "\x{2666}",
120     divide => "\x{00F7}",
121     eacute => "\x{00E9}",
122     ecirc => "\x{00EA}",
123     egrave => "\x{00E8}",
124     empty => "\x{2205}",
125     emsp => "\x{2003}",
126     ensp => "\x{2002}",
127     epsilon => "\x{03B5}",
128     equiv => "\x{2261}",
129     eta => "\x{03B7}",
130     eth => "\x{00F0}",
131     euml => "\x{00EB}",
132     euro => "\x{20AC}",
133     exist => "\x{2203}",
134     fnof => "\x{0192}",
135     forall => "\x{2200}",
136     frac12 => "\x{00BD}",
137     frac14 => "\x{00BC}",
138     frac34 => "\x{00BE}",
139     frasl => "\x{2044}",
140     gamma => "\x{03B3}",
141     ge => "\x{2265}",
142     gt => "\x{003E}",
143     GT => "\x{003E}",
144     hArr => "\x{21D4}",
145     harr => "\x{2194}",
146     hearts => "\x{2665}",
147     hellip => "\x{2026}",
148     iacute => "\x{00ED}",
149     icirc => "\x{00EE}",
150     iexcl => "\x{00A1}",
151     igrave => "\x{00EC}",
152     image => "\x{2111}",
153     infin => "\x{221E}",
154     int => "\x{222B}",
155     iota => "\x{03B9}",
156     iquest => "\x{00BF}",
157     isin => "\x{2208}",
158     iuml => "\x{00EF}",
159     kappa => "\x{03BA}",
160     lArr => "\x{21D0}",
161     lambda => "\x{03BB}",
162     lang => "\x{2329}",
163     laquo => "\x{00AB}",
164     larr => "\x{2190}",
165     lceil => "\x{2308}",
166     ldquo => "\x{201C}",
167     le => "\x{2264}",
168     lfloor => "\x{230A}",
169     lowast => "\x{2217}",
170     loz => "\x{25CA}",
171     lrm => "\x{200E}",
172     lsaquo => "\x{2039}",
173     lsquo => "\x{2018}",
174     lt => "\x{003C}",
175     LT => "\x{003C}",
176     macr => "\x{00AF}",
177     mdash => "\x{2014}",
178     micro => "\x{00B5}",
179     middot => "\x{00B7}",
180     minus => "\x{2212}",
181     mu => "\x{03BC}",
182     nabla => "\x{2207}",
183     nbsp => "\x{00A0}",
184     ndash => "\x{2013}",
185     ne => "\x{2260}",
186     ni => "\x{220B}",
187     not => "\x{00AC}",
188     notin => "\x{2209}",
189     nsub => "\x{2284}",
190     ntilde => "\x{00F1}",
191     nu => "\x{03BD}",
192     oacute => "\x{00F3}",
193     ocirc => "\x{00F4}",
194     oelig => "\x{0153}",
195     ograve => "\x{00F2}",
196     oline => "\x{203E}",
197     omega => "\x{03C9}",
198     omicron => "\x{03BF}",
199     oplus => "\x{2295}",
200     or => "\x{2228}",
201     ordf => "\x{00AA}",
202     ordm => "\x{00BA}",
203     oslash => "\x{00F8}",
204     otilde => "\x{00F5}",
205     otimes => "\x{2297}",
206     ouml => "\x{00F6}",
207     para => "\x{00B6}",
208     part => "\x{2202}",
209     permil => "\x{2030}",
210     perp => "\x{22A5}",
211     phi => "\x{03C6}",
212     pi => "\x{03C0}",
213     piv => "\x{03D6}",
214     plusmn => "\x{00B1}",
215     pound => "\x{00A3}",
216     prime => "\x{2032}",
217     prod => "\x{220F}",
218     prop => "\x{221D}",
219     psi => "\x{03C8}",
220     quot => "\x{0022}",
221     QUOT => "\x{0022}",
222     rArr => "\x{21D2}",
223     radic => "\x{221A}",
224     rang => "\x{232A}",
225     raquo => "\x{00BB}",
226     rarr => "\x{2192}",
227     rceil => "\x{2309}",
228     rdquo => "\x{201D}",
229     real => "\x{211C}",
230     reg => "\x{00AE}",
231     REG => "\x{00AE}",
232     rfloor => "\x{230B}",
233     rho => "\x{03C1}",
234     rlm => "\x{200F}",
235     rsaquo => "\x{203A}",
236     rsquo => "\x{2019}",
237     sbquo => "\x{201A}",
238     scaron => "\x{0161}",
239     sdot => "\x{22C5}",
240     sect => "\x{00A7}",
241     shy => "\x{00AD}",
242     sigma => "\x{03C3}",
243     sigmaf => "\x{03C2}",
244     sim => "\x{223C}",
245     spades => "\x{2660}",
246     sub => "\x{2282}",
247     sube => "\x{2286}",
248     sum => "\x{2211}",
249     sup => "\x{2283}",
250     sup1 => "\x{00B9}",
251     sup2 => "\x{00B2}",
252     sup3 => "\x{00B3}",
253     supe => "\x{2287}",
254     szlig => "\x{00DF}",
255     tau => "\x{03C4}",
256     there4 => "\x{2234}",
257     theta => "\x{03B8}",
258     thetasym => "\x{03D1}",
259     thinsp => "\x{2009}",
260     thorn => "\x{00FE}",
261     tilde => "\x{02DC}",
262     times => "\x{00D7}",
263     trade => "\x{2122}",
264     uArr => "\x{21D1}",
265     uacute => "\x{00FA}",
266     uarr => "\x{2191}",
267     ucirc => "\x{00FB}",
268     ugrave => "\x{00F9}",
269     uml => "\x{00A8}",
270     upsih => "\x{03D2}",
271     upsilon => "\x{03C5}",
272     uuml => "\x{00FC}",
273     weierp => "\x{2118}",
274     xi => "\x{03BE}",
275     yacute => "\x{00FD}",
276     yen => "\x{00A5}",
277     yuml => "\x{00FF}",
278     zeta => "\x{03B6}",
279     zwj => "\x{200D}",
280     zwnj => "\x{200C}",
281 wakaba 1.4 }; # $entity_char
282    
283     ## <http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2006-December/thread.html#8562>
284     my $c1_entity_char = {
285     128, 8364,
286     129, 65533,
287     130, 8218,
288     131, 402,
289     132, 8222,
290     133, 8230,
291     134, 8224,
292     135, 8225,
293     136, 710,
294     137, 8240,
295     138, 352,
296     139, 8249,
297     140, 338,
298     141, 65533,
299     142, 381,
300     143, 65533,
301     144, 65533,
302     145, 8216,
303     146, 8217,
304     147, 8220,
305     148, 8221,
306     149, 8226,
307     150, 8211,
308     151, 8212,
309     152, 732,
310     153, 8482,
311     154, 353,
312     155, 8250,
313     156, 339,
314     157, 65533,
315     158, 382,
316     159, 376,
317     }; # $c1_entity_char
318 wakaba 1.1
319     my $special_category = {
320     address => 1, area => 1, base => 1, basefont => 1, bgsound => 1,
321     blockquote => 1, body => 1, br => 1, center => 1, col => 1, colgroup => 1,
322     dd => 1, dir => 1, div => 1, dl => 1, dt => 1, embed => 1, fieldset => 1,
323     form => 1, frame => 1, frameset => 1, h1 => 1, h2 => 1, h3 => 1,
324     h4 => 1, h5 => 1, h6 => 1, head => 1, hr => 1, iframe => 1, image => 1,
325     img => 1, input => 1, isindex => 1, li => 1, link => 1, listing => 1,
326     menu => 1, meta => 1, noembed => 1, noframes => 1, noscript => 1,
327     ol => 1, optgroup => 1, option => 1, p => 1, param => 1, plaintext => 1,
328     pre => 1, script => 1, select => 1, spacer => 1, style => 1, tbody => 1,
329     textarea => 1, tfoot => 1, thead => 1, title => 1, tr => 1, ul => 1, wbr => 1,
330     };
331     my $scoping_category = {
332     button => 1, caption => 1, html => 1, marquee => 1, object => 1,
333     table => 1, td => 1, th => 1,
334     };
335     my $formatting_category = {
336     a => 1, b => 1, big => 1, em => 1, font => 1, i => 1, nobr => 1,
337     s => 1, small => 1, strile => 1, strong => 1, tt => 1, u => 1,
338     };
339     # $phrasing_category: all other elements
340    
341     sub parse_string ($$$;$) {
342     my $self = shift->new;
343     my $s = \$_[0];
344     $self->{document} = $_[1];
345    
346 wakaba 1.3 ## NOTE: |set_inner_html| copies most of this method's code
347    
348 wakaba 1.1 my $i = 0;
349 wakaba 1.3 my $line = 1;
350     my $column = 0;
351 wakaba 1.1 $self->{set_next_input_character} = sub {
352     my $self = shift;
353     $self->{next_input_character} = -1 and return if $i >= length $$s;
354     $self->{next_input_character} = ord substr $$s, $i++, 1;
355 wakaba 1.3 $column++;
356 wakaba 1.1
357 wakaba 1.4 if ($self->{next_input_character} == 0x000A) { # LF
358     $line++;
359     $column = 0;
360     } elsif ($self->{next_input_character} == 0x000D) { # CR
361 wakaba 1.1 if ($i >= length $$s) {
362     #
363     } else {
364     my $next_char = ord substr $$s, $i++, 1;
365     if ($next_char == 0x000A) { # LF
366     #
367     } else {
368     push @{$self->{char}}, $next_char;
369     }
370     }
371     $self->{next_input_character} = 0x000A; # LF # MUST
372 wakaba 1.3 $line++;
373 wakaba 1.4 $column = 0;
374 wakaba 1.1 } elsif ($self->{next_input_character} > 0x10FFFF) {
375     $self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
376     } elsif ($self->{next_input_character} == 0x0000) { # NULL
377     $self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
378     }
379     };
380    
381 wakaba 1.3 my $onerror = $_[2] || sub {
382     my (%opt) = @_;
383     warn "Parse error ($opt{type}) at line $opt{line} column $opt{column}\n";
384     };
385     $self->{parse_error} = sub {
386     $onerror->(@_, line => $line, column => $column);
387 wakaba 1.1 };
388    
389     $self->_initialize_tokenizer;
390     $self->_initialize_tree_constructor;
391     $self->_construct_tree;
392     $self->_terminate_tree_constructor;
393    
394     return $self->{document};
395     } # parse_string
396    
397     sub new ($) {
398     my $class = shift;
399     my $self = bless {}, $class;
400     $self->{set_next_input_character} = sub {
401     $self->{next_input_character} = -1;
402     };
403     $self->{parse_error} = sub {
404     #
405     };
406     return $self;
407     } # new
408    
409     ## Implementations MUST act as if state machine in the spec
410    
411     sub _initialize_tokenizer ($) {
412     my $self = shift;
413     $self->{state} = 'data'; # MUST
414     $self->{content_model_flag} = 'PCDATA'; # be
415     undef $self->{current_token}; # start tag, end tag, comment, or DOCTYPE
416     undef $self->{current_attribute};
417     undef $self->{last_emitted_start_tag_name};
418     undef $self->{last_attribute_value_state};
419     $self->{char} = [];
420     # $self->{next_input_character}
421    
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     ## NOTE: <http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2006-December/thread.html#8562>
2058     ## ISSUE: Not in the spec yet; parse error?
2059     $num = $c1_entity_char->{$num};
2060 wakaba 1.1 }
2061    
2062     return {type => 'character', data => chr $num};
2063     } # X
2064     } elsif (0x0030 <= $self->{next_input_character} and
2065     $self->{next_input_character} <= 0x0039) { # 0..9
2066     my $code = $self->{next_input_character} - 0x0030;
2067    
2068     if (@{$self->{char}}) {
2069     $self->{next_input_character} = shift @{$self->{char}};
2070     } else {
2071     $self->{set_next_input_character}->($self);
2072     }
2073    
2074    
2075     while (0x0030 <= $self->{next_input_character} and
2076     $self->{next_input_character} <= 0x0039) { # 0..9
2077     $code *= 10;
2078     $code += $self->{next_input_character} - 0x0030;
2079    
2080    
2081     if (@{$self->{char}}) {
2082     $self->{next_input_character} = shift @{$self->{char}};
2083     } else {
2084     $self->{set_next_input_character}->($self);
2085     }
2086    
2087     }
2088    
2089     if ($self->{next_input_character} == 0x003B) { # ;
2090    
2091     if (@{$self->{char}}) {
2092     $self->{next_input_character} = shift @{$self->{char}};
2093     } else {
2094     $self->{set_next_input_character}->($self);
2095     }
2096    
2097     } else {
2098 wakaba 1.3 $self->{parse_error}-> (type => 'no refc');
2099 wakaba 1.1 }
2100    
2101     ## TODO: check the definition for |a valid Unicode character|.
2102     if ($code > 1114111 or $code == 0) {
2103     $code = 0xFFFD; # REPLACEMENT CHARACTER
2104     ## ISSUE: Why this is not an error?
2105 wakaba 1.4 } elsif (0x80 <= $code and $code <= 0x9F) {
2106     ## NOTE: <http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2006-December/thread.html#8562>
2107     ## ISSUE: Not in the spec yet; parse error?
2108     $code = $c1_entity_char->{$code};
2109 wakaba 1.1 }
2110    
2111     return {type => 'character', data => chr $code};
2112     } else {
2113 wakaba 1.3 $self->{parse_error}-> (type => 'bare nero');
2114 wakaba 1.1 unshift @{$self->{char}}, ($self->{next_input_character});
2115     $self->{next_input_character} = 0x0023; # #
2116     return undef;
2117     }
2118     } elsif ((0x0041 <= $self->{next_input_character} and
2119     $self->{next_input_character} <= 0x005A) or
2120     (0x0061 <= $self->{next_input_character} and
2121     $self->{next_input_character} <= 0x007A)) {
2122     my $entity_name = chr $self->{next_input_character};
2123    
2124     if (@{$self->{char}}) {
2125     $self->{next_input_character} = shift @{$self->{char}};
2126     } else {
2127     $self->{set_next_input_character}->($self);
2128     }
2129    
2130    
2131     my $value = $entity_name;
2132     my $match;
2133    
2134     while (length $entity_name < 10 and
2135     ## NOTE: Some number greater than the maximum length of entity name
2136     ((0x0041 <= $self->{next_input_character} and
2137     $self->{next_input_character} <= 0x005A) or
2138     (0x0061 <= $self->{next_input_character} and
2139     $self->{next_input_character} <= 0x007A) or
2140     (0x0030 <= $self->{next_input_character} and
2141     $self->{next_input_character} <= 0x0039))) {
2142     $entity_name .= chr $self->{next_input_character};
2143     if (defined $entity_char->{$entity_name}) {
2144     $value = $entity_char->{$entity_name};
2145     $match = 1;
2146     } else {
2147     $value .= chr $self->{next_input_character};
2148     }
2149    
2150     if (@{$self->{char}}) {
2151     $self->{next_input_character} = shift @{$self->{char}};
2152     } else {
2153     $self->{set_next_input_character}->($self);
2154     }
2155    
2156     }
2157    
2158     if ($match) {
2159     if ($self->{next_input_character} == 0x003B) { # ;
2160    
2161     if (@{$self->{char}}) {
2162     $self->{next_input_character} = shift @{$self->{char}};
2163     } else {
2164     $self->{set_next_input_character}->($self);
2165     }
2166    
2167     } else {
2168 wakaba 1.3 $self->{parse_error}-> (type => 'refc');
2169 wakaba 1.1 }
2170    
2171     return {type => 'character', data => $value};
2172     } else {
2173 wakaba 1.3 $self->{parse_error}-> (type => 'bare ero');
2174 wakaba 1.1 ## NOTE: No characters are consumed in the spec.
2175     unshift @{$self->{token}}, ({type => 'character', data => $value});
2176     return undef;
2177     }
2178     } else {
2179     ## no characters are consumed
2180 wakaba 1.3 $self->{parse_error}-> (type => 'bare ero');
2181 wakaba 1.1 return undef;
2182     }
2183     } # _tokenize_attempt_to_consume_an_entity
2184    
2185     sub _initialize_tree_constructor ($) {
2186     my $self = shift;
2187     ## NOTE: $self->{document} MUST be specified before this method is called
2188     $self->{document}->strict_error_checking (0);
2189     ## TODO: Turn mutation events off # MUST
2190     ## TODO: Turn loose Document option (manakai extension) on
2191     ## TODO: Mark the Document as an HTML document # MUST
2192     } # _initialize_tree_constructor
2193    
2194     sub _terminate_tree_constructor ($) {
2195     my $self = shift;
2196     $self->{document}->strict_error_checking (1);
2197     ## TODO: Turn mutation events on
2198     } # _terminate_tree_constructor
2199    
2200     ## ISSUE: Should append_child (for example) in script executed in tree construction stage fire mutation events?
2201    
2202 wakaba 1.3 { # tree construction stage
2203     my $token;
2204    
2205 wakaba 1.1 sub _construct_tree ($) {
2206     my ($self) = @_;
2207    
2208     ## When an interactive UA render the $self->{document} available
2209     ## to the user, or when it begin accepting user input, are
2210     ## not defined.
2211    
2212     ## Append a character: collect it and all subsequent consecutive
2213     ## characters and insert one Text node whose data is concatenation
2214     ## of all those characters. # MUST
2215    
2216     $token = $self->_get_next_token;
2217    
2218 wakaba 1.3 $self->{insertion_mode} = 'before head';
2219     undef $self->{form_element};
2220     undef $self->{head_element};
2221     $self->{open_elements} = [];
2222     undef $self->{inner_html_node};
2223    
2224     $self->_tree_construction_initial; # MUST
2225     $self->_tree_construction_root_element;
2226     $self->_tree_construction_main;
2227     } # _construct_tree
2228    
2229     sub _tree_construction_initial ($) {
2230     my $self = shift;
2231     B: {
2232     if ($token->{type} eq 'DOCTYPE') {
2233     if ($token->{error}) {
2234     ## ISSUE: Spec currently left this case undefined.
2235     $self->{parse_error}-> (type => 'bogus DOCTYPE');
2236     }
2237     my $doctype = $self->{document}->create_document_type_definition
2238     ($token->{name});
2239     $self->{document}->append_child ($doctype);
2240     #$phase = 'root element';
2241     $token = $self->_get_next_token;
2242     #redo B;
2243     return;
2244     } elsif ({
2245     comment => 1,
2246     'start tag' => 1,
2247     'end tag' => 1,
2248     'end-of-file' => 1,
2249     }->{$token->{type}}) {
2250     ## ISSUE: Spec currently left this case undefined.
2251     $self->{parse_error}-> (type => 'missing DOCTYPE');
2252     #$phase = 'root element';
2253     ## reprocess
2254     #redo B;
2255     return;
2256     } elsif ($token->{type} eq 'character') {
2257     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
2258     $self->{document}->manakai_append_text ($1);
2259     ## ISSUE: DOM3 Core does not allow Document > Text
2260     unless (length $token->{data}) {
2261     ## Stay in the phase
2262     $token = $self->_get_next_token;
2263     redo B;
2264     }
2265     }
2266     ## ISSUE: Spec currently left this case undefined.
2267     $self->{parse_error}-> (type => 'missing DOCTYPE');
2268     #$phase = 'root element';
2269     ## reprocess
2270     #redo B;
2271     return;
2272     } else {
2273     die "$0: $token->{type}: Unknown token";
2274     }
2275     } # B
2276     } # _tree_construction_initial
2277    
2278     sub _tree_construction_root_element ($) {
2279     my $self = shift;
2280    
2281     B: {
2282     if ($token->{type} eq 'DOCTYPE') {
2283     $self->{parse_error}-> (type => 'in html:#DOCTYPE');
2284     ## Ignore the token
2285     ## Stay in the phase
2286     $token = $self->_get_next_token;
2287     redo B;
2288     } elsif ($token->{type} eq 'comment') {
2289     my $comment = $self->{document}->create_comment ($token->{data});
2290     $self->{document}->append_child ($comment);
2291     ## Stay in the phase
2292     $token = $self->_get_next_token;
2293     redo B;
2294     } elsif ($token->{type} eq 'character') {
2295     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
2296     $self->{document}->manakai_append_text ($1);
2297     ## ISSUE: DOM3 Core does not allow Document > Text
2298     unless (length $token->{data}) {
2299     ## Stay in the phase
2300     $token = $self->_get_next_token;
2301     redo B;
2302     }
2303     }
2304     #
2305     } elsif ({
2306     'start tag' => 1,
2307     'end tag' => 1,
2308     'end-of-file' => 1,
2309     }->{$token->{type}}) {
2310     ## ISSUE: There is an issue in the spec
2311     #
2312     } else {
2313     die "$0: $token->{type}: Unknown token";
2314     }
2315     my $root_element;
2316     $root_element = $self->{document}->create_element_ns
2317     (q<http://www.w3.org/1999/xhtml>, [undef, 'html']);
2318    
2319     $self->{document}->append_child ($root_element);
2320     push @{$self->{open_elements}}, [$root_element, 'html'];
2321     #$phase = 'main';
2322     ## reprocess
2323     #redo B;
2324     return;
2325     } # B
2326     } # _tree_construction_root_element
2327    
2328     sub _reset_insertion_mode ($) {
2329     my $self = shift;
2330    
2331     ## Step 1
2332     my $last;
2333    
2334     ## Step 2
2335     my $i = -1;
2336     my $node = $self->{open_elements}->[$i];
2337    
2338     ## Step 3
2339     S3: {
2340     $last = 1 if $self->{open_elements}->[0]->[0] eq $node->[0];
2341     if (defined $self->{inner_html_node}) {
2342     if ($self->{inner_html_node}->[1] eq 'td' or
2343     $self->{inner_html_node}->[1] eq 'th') {
2344     #
2345     } else {
2346     $node = $self->{inner_html_node};
2347     }
2348     }
2349    
2350     ## Step 4..13
2351     my $new_mode = {
2352     select => 'in select',
2353     td => 'in cell',
2354     th => 'in cell',
2355     tr => 'in row',
2356     tbody => 'in table body',
2357     thead => 'in table head',
2358     tfoot => 'in table foot',
2359     caption => 'in caption',
2360     colgroup => 'in column group',
2361     table => 'in table',
2362     head => 'in body', # not in head!
2363     body => 'in body',
2364     frameset => 'in frameset',
2365     }->{$node->[1]};
2366     $self->{insertion_mode} = $new_mode and return if defined $new_mode;
2367    
2368     ## Step 14
2369     if ($node->[1] eq 'html') {
2370     unless (defined $self->{head_element}) {
2371     $self->{insertion_mode} = 'before head';
2372     } else {
2373     $self->{insertion_mode} = 'after head';
2374     }
2375     return;
2376     }
2377    
2378     ## Step 15
2379     $self->{insertion_mode} = 'in body' and return if $last;
2380    
2381     ## Step 16
2382     $i--;
2383     $node = $self->{open_elements}->[$i];
2384    
2385     ## Step 17
2386     redo S3;
2387     } # S3
2388     } # _reset_insertion_mode
2389    
2390     sub _tree_construction_main ($) {
2391     my $self = shift;
2392    
2393     my $phase = 'main';
2394 wakaba 1.1
2395     my $active_formatting_elements = [];
2396    
2397     my $reconstruct_active_formatting_elements = sub { # MUST
2398     my $insert = shift;
2399    
2400     ## Step 1
2401     return unless @$active_formatting_elements;
2402    
2403     ## Step 3
2404     my $i = -1;
2405     my $entry = $active_formatting_elements->[$i];
2406    
2407     ## Step 2
2408     return if $entry->[0] eq '#marker';
2409 wakaba 1.3 for (@{$self->{open_elements}}) {
2410 wakaba 1.1 if ($entry->[0] eq $_->[0]) {
2411     return;
2412     }
2413     }
2414    
2415     S4: {
2416     ## Step 4
2417     last S4 if $active_formatting_elements->[0]->[0] eq $entry->[0];
2418    
2419     ## Step 5
2420     $i--;
2421     $entry = $active_formatting_elements->[$i];
2422    
2423     ## Step 6
2424     if ($entry->[0] eq '#marker') {
2425     #
2426     } else {
2427     my $in_open_elements;
2428 wakaba 1.3 OE: for (@{$self->{open_elements}}) {
2429 wakaba 1.1 if ($entry->[0] eq $_->[0]) {
2430     $in_open_elements = 1;
2431     last OE;
2432     }
2433     }
2434     if ($in_open_elements) {
2435     #
2436     } else {
2437     redo S4;
2438     }
2439     }
2440    
2441     ## Step 7
2442     $i++;
2443     $entry = $active_formatting_elements->[$i];
2444     } # S4
2445    
2446     S7: {
2447     ## Step 8
2448     my $clone = [$entry->[0]->clone_node (0), $entry->[1]];
2449    
2450     ## Step 9
2451     $insert->($clone->[0]);
2452 wakaba 1.3 push @{$self->{open_elements}}, $clone;
2453 wakaba 1.1
2454     ## Step 10
2455 wakaba 1.3 $active_formatting_elements->[$i] = $self->{open_elements}->[-1];
2456 wakaba 1.1
2457     ## Step 11
2458     unless ($clone->[0] eq $active_formatting_elements->[-1]->[0]) {
2459     ## Step 7'
2460     $i++;
2461     $entry = $active_formatting_elements->[$i];
2462    
2463     redo S7;
2464     }
2465     } # S7
2466     }; # $reconstruct_active_formatting_elements
2467    
2468     my $clear_up_to_marker = sub {
2469     for (reverse 0..$#$active_formatting_elements) {
2470     if ($active_formatting_elements->[$_]->[0] eq '#marker') {
2471     splice @$active_formatting_elements, $_;
2472     return;
2473     }
2474     }
2475     }; # $clear_up_to_marker
2476    
2477     my $style_start_tag = sub {
2478     my $style_el;
2479     $style_el = $self->{document}->create_element_ns
2480     (q<http://www.w3.org/1999/xhtml>, [undef, 'style']);
2481    
2482 wakaba 1.3 ## $self->{insertion_mode} eq 'in head' and ... (always true)
2483     (($self->{insertion_mode} eq 'in head' and defined $self->{head_element})
2484     ? $self->{head_element} : $self->{open_elements}->[-1]->[0])
2485 wakaba 1.1 ->append_child ($style_el);
2486     $self->{content_model_flag} = 'CDATA';
2487    
2488     my $text = '';
2489     $token = $self->_get_next_token;
2490     while ($token->{type} eq 'character') {
2491     $text .= $token->{data};
2492     $token = $self->_get_next_token;
2493     } # stop if non-character token or tokenizer stops tokenising
2494     if (length $text) {
2495     $style_el->manakai_append_text ($text);
2496     }
2497    
2498     $self->{content_model_flag} = 'PCDATA';
2499    
2500     if ($token->{type} eq 'end tag' and $token->{tag_name} eq 'style') {
2501     ## Ignore the token
2502     } else {
2503 wakaba 1.3 $self->{parse_error}-> (type => 'in CDATA:#'.$token->{type});
2504 wakaba 1.1 ## ISSUE: And ignore?
2505     }
2506     $token = $self->_get_next_token;
2507     }; # $style_start_tag
2508    
2509     my $script_start_tag = sub {
2510     my $script_el;
2511    
2512     $script_el = $self->{document}->create_element_ns
2513     (q<http://www.w3.org/1999/xhtml>, [undef, 'script']);
2514    
2515     for my $attr_name (keys %{ $token->{attributes}}) {
2516     $script_el->set_attribute_ns (undef, [undef, $attr_name],
2517     $token->{attributes} ->{$attr_name}->{value});
2518     }
2519    
2520     ## TODO: mark as "parser-inserted"
2521    
2522     $self->{content_model_flag} = 'CDATA';
2523    
2524     my $text = '';
2525     $token = $self->_get_next_token;
2526     while ($token->{type} eq 'character') {
2527     $text .= $token->{data};
2528     $token = $self->_get_next_token;
2529     } # stop if non-character token or tokenizer stops tokenising
2530     if (length $text) {
2531     $script_el->manakai_append_text ($text);
2532     }
2533    
2534     $self->{content_model_flag} = 'PCDATA';
2535    
2536     if ($token->{type} eq 'end tag' and
2537     $token->{tag_name} eq 'script') {
2538     ## Ignore the token
2539     } else {
2540 wakaba 1.3 $self->{parse_error}-> (type => 'in CDATA:#'.$token->{type});
2541 wakaba 1.1 ## ISSUE: And ignore?
2542     ## TODO: mark as "already executed"
2543     }
2544    
2545 wakaba 1.3 if (defined $self->{inner_html_node}) {
2546     ## TODO: mark as "already executed"
2547     } else {
2548 wakaba 1.1 ## TODO: $old_insertion_point = current insertion point
2549     ## TODO: insertion point = just before the next input character
2550    
2551 wakaba 1.3 (($self->{insertion_mode} eq 'in head' and defined $self->{head_element})
2552     ? $self->{head_element} : $self->{open_elements}->[-1]->[0])->append_child ($script_el);
2553 wakaba 1.1
2554     ## TODO: insertion point = $old_insertion_point (might be "undefined")
2555    
2556     ## TODO: if there is a script that will execute as soon as the parser resume, then...
2557     }
2558    
2559     $token = $self->_get_next_token;
2560     }; # $script_start_tag
2561    
2562     my $formatting_end_tag = sub {
2563     my $tag_name = shift;
2564    
2565     FET: {
2566     ## Step 1
2567     my $formatting_element;
2568     my $formatting_element_i_in_active;
2569     AFE: for (reverse 0..$#$active_formatting_elements) {
2570     if ($active_formatting_elements->[$_]->[1] eq $tag_name) {
2571     $formatting_element = $active_formatting_elements->[$_];
2572     $formatting_element_i_in_active = $_;
2573     last AFE;
2574     } elsif ($active_formatting_elements->[$_]->[0] eq '#marker') {
2575     last AFE;
2576     }
2577     } # AFE
2578     unless (defined $formatting_element) {
2579 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$tag_name);
2580 wakaba 1.1 ## Ignore the token
2581     $token = $self->_get_next_token;
2582     return;
2583     }
2584     ## has an element in scope
2585     my $in_scope = 1;
2586     my $formatting_element_i_in_open;
2587 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
2588     my $node = $self->{open_elements}->[$_];
2589 wakaba 1.1 if ($node->[0] eq $formatting_element->[0]) {
2590     if ($in_scope) {
2591     $formatting_element_i_in_open = $_;
2592     last INSCOPE;
2593     } else { # in open elements but not in scope
2594 wakaba 1.4 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
2595 wakaba 1.1 ## Ignore the token
2596     $token = $self->_get_next_token;
2597     return;
2598     }
2599     } elsif ({
2600     table => 1, caption => 1, td => 1, th => 1,
2601     button => 1, marquee => 1, object => 1, html => 1,
2602     }->{$node->[1]}) {
2603     $in_scope = 0;
2604     }
2605     } # INSCOPE
2606     unless (defined $formatting_element_i_in_open) {
2607 wakaba 1.4 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
2608 wakaba 1.1 pop @$active_formatting_elements; # $formatting_element
2609     $token = $self->_get_next_token; ## TODO: ok?
2610     return;
2611     }
2612 wakaba 1.3 if (not $self->{open_elements}->[-1]->[0] eq $formatting_element->[0]) {
2613 wakaba 1.4 $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
2614 wakaba 1.1 }
2615    
2616     ## Step 2
2617     my $furthest_block;
2618     my $furthest_block_i_in_open;
2619 wakaba 1.3 OE: for (reverse 0..$#{$self->{open_elements}}) {
2620     my $node = $self->{open_elements}->[$_];
2621 wakaba 1.1 if (not $formatting_category->{$node->[1]} and
2622     #not $phrasing_category->{$node->[1]} and
2623     ($special_category->{$node->[1]} or
2624     $scoping_category->{$node->[1]})) {
2625     $furthest_block = $node;
2626     $furthest_block_i_in_open = $_;
2627     } elsif ($node->[0] eq $formatting_element->[0]) {
2628     last OE;
2629     }
2630     } # OE
2631    
2632     ## Step 3
2633     unless (defined $furthest_block) { # MUST
2634 wakaba 1.3 splice @{$self->{open_elements}}, $formatting_element_i_in_open;
2635 wakaba 1.1 splice @$active_formatting_elements, $formatting_element_i_in_active, 1;
2636     $token = $self->_get_next_token;
2637     return;
2638     }
2639    
2640     ## Step 4
2641 wakaba 1.3 my $common_ancestor_node = $self->{open_elements}->[$formatting_element_i_in_open - 1];
2642 wakaba 1.1
2643     ## Step 5
2644     my $furthest_block_parent = $furthest_block->[0]->parent_node;
2645     if (defined $furthest_block_parent) {
2646     $furthest_block_parent->remove_child ($furthest_block->[0]);
2647     }
2648    
2649     ## Step 6
2650     my $bookmark_prev_el
2651     = $active_formatting_elements->[$formatting_element_i_in_active - 1]
2652     ->[0];
2653    
2654     ## Step 7
2655     my $node = $furthest_block;
2656     my $node_i_in_open = $furthest_block_i_in_open;
2657     my $last_node = $furthest_block;
2658     S7: {
2659     ## Step 1
2660     $node_i_in_open--;
2661 wakaba 1.3 $node = $self->{open_elements}->[$node_i_in_open];
2662 wakaba 1.1
2663     ## Step 2
2664     my $node_i_in_active;
2665     S7S2: {
2666     for (reverse 0..$#$active_formatting_elements) {
2667     if ($active_formatting_elements->[$_]->[0] eq $node->[0]) {
2668     $node_i_in_active = $_;
2669     last S7S2;
2670     }
2671     }
2672 wakaba 1.3 splice @{$self->{open_elements}}, $node_i_in_open, 1;
2673 wakaba 1.1 redo S7;
2674     } # S7S2
2675    
2676     ## Step 3
2677     last S7 if $node->[0] eq $formatting_element->[0];
2678    
2679     ## Step 4
2680     if ($last_node->[0] eq $furthest_block->[0]) {
2681     $bookmark_prev_el = $node->[0];
2682     }
2683    
2684     ## Step 5
2685     if ($node->[0]->has_child_nodes ()) {
2686     my $clone = [$node->[0]->clone_node (0), $node->[1]];
2687     $active_formatting_elements->[$node_i_in_active] = $clone;
2688 wakaba 1.3 $self->{open_elements}->[$node_i_in_open] = $clone;
2689 wakaba 1.1 $node = $clone;
2690     }
2691    
2692     ## Step 6
2693     $node->[0]->append_child ($last_node->[0]);
2694    
2695     ## Step 7
2696     $last_node = $node;
2697    
2698     ## Step 8
2699     redo S7;
2700     } # S7
2701    
2702     ## Step 8
2703     $common_ancestor_node->[0]->append_child ($last_node->[0]);
2704    
2705     ## Step 9
2706     my $clone = [$formatting_element->[0]->clone_node (0),
2707     $formatting_element->[1]];
2708    
2709     ## Step 10
2710     my @cn = @{$furthest_block->[0]->child_nodes};
2711     $clone->[0]->append_child ($_) for @cn;
2712    
2713     ## Step 11
2714     $furthest_block->[0]->append_child ($clone->[0]);
2715    
2716     ## Step 12
2717     my $i;
2718     AFE: for (reverse 0..$#$active_formatting_elements) {
2719     if ($active_formatting_elements->[$_]->[0] eq $formatting_element->[0]) {
2720     splice @$active_formatting_elements, $_, 1;
2721     $i-- and last AFE if defined $i;
2722     } elsif ($active_formatting_elements->[$_]->[0] eq $bookmark_prev_el) {
2723     $i = $_;
2724     }
2725     } # AFE
2726     splice @$active_formatting_elements, $i + 1, 0, $clone;
2727    
2728     ## Step 13
2729     undef $i;
2730 wakaba 1.3 OE: for (reverse 0..$#{$self->{open_elements}}) {
2731     if ($self->{open_elements}->[$_]->[0] eq $formatting_element->[0]) {
2732     splice @{$self->{open_elements}}, $_, 1;
2733 wakaba 1.1 $i-- and last OE if defined $i;
2734 wakaba 1.3 } elsif ($self->{open_elements}->[$_]->[0] eq $furthest_block->[0]) {
2735 wakaba 1.1 $i = $_;
2736     }
2737     } # OE
2738 wakaba 1.3 splice @{$self->{open_elements}}, $i + 1, 1, $clone;
2739 wakaba 1.1
2740     ## Step 14
2741     redo FET;
2742     } # FET
2743     }; # $formatting_end_tag
2744    
2745     my $insert_to_current = sub {
2746 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child (shift);
2747 wakaba 1.1 }; # $insert_to_current
2748    
2749     my $insert_to_foster = sub {
2750     my $child = shift;
2751     if ({
2752     table => 1, tbody => 1, tfoot => 1,
2753     thead => 1, tr => 1,
2754 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
2755 wakaba 1.1 # MUST
2756     my $foster_parent_element;
2757     my $next_sibling;
2758 wakaba 1.3 OE: for (reverse 0..$#{$self->{open_elements}}) {
2759     if ($self->{open_elements}->[$_]->[1] eq 'table') {
2760     my $parent = $self->{open_elements}->[$_]->[0]->parent_node;
2761 wakaba 1.1 if (defined $parent and $parent->node_type == 1) {
2762     $foster_parent_element = $parent;
2763 wakaba 1.3 $next_sibling = $self->{open_elements}->[$_]->[0];
2764 wakaba 1.1 } else {
2765     $foster_parent_element
2766 wakaba 1.3 = $self->{open_elements}->[$_ - 1]->[0];
2767 wakaba 1.1 }
2768     last OE;
2769     }
2770     } # OE
2771 wakaba 1.3 $foster_parent_element = $self->{open_elements}->[0]->[0]
2772 wakaba 1.1 unless defined $foster_parent_element;
2773     $foster_parent_element->insert_before
2774     ($child, $next_sibling);
2775     } else {
2776 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($child);
2777 wakaba 1.1 }
2778     }; # $insert_to_foster
2779    
2780     my $in_body = sub {
2781     my $insert = shift;
2782     if ($token->{type} eq 'start tag') {
2783     if ($token->{tag_name} eq 'script') {
2784     $script_start_tag->();
2785     return;
2786     } elsif ($token->{tag_name} eq 'style') {
2787     $style_start_tag->();
2788     return;
2789     } elsif ({
2790     base => 1, link => 1, meta => 1,
2791     }->{$token->{tag_name}}) {
2792 wakaba 1.3 $self->{parse_error}-> (type => 'in body:'.$token->{tag_name});
2793 wakaba 1.1 ## NOTE: This is an "as if in head" code clone
2794     my $el;
2795    
2796     $el = $self->{document}->create_element_ns
2797     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
2798    
2799     for my $attr_name (keys %{ $token->{attributes}}) {
2800     $el->set_attribute_ns (undef, [undef, $attr_name],
2801     $token->{attributes} ->{$attr_name}->{value});
2802     }
2803    
2804 wakaba 1.3 if (defined $self->{head_element}) {
2805     $self->{head_element}->append_child ($el);
2806 wakaba 1.1 } else {
2807     $insert->($el);
2808     }
2809    
2810     $token = $self->_get_next_token;
2811     return;
2812     } elsif ($token->{tag_name} eq 'title') {
2813 wakaba 1.3 $self->{parse_error}-> (type => 'in body:title');
2814 wakaba 1.1 ## NOTE: There is an "as if in head" code clone
2815     my $title_el;
2816    
2817     $title_el = $self->{document}->create_element_ns
2818     (q<http://www.w3.org/1999/xhtml>, [undef, 'title']);
2819    
2820     for my $attr_name (keys %{ $token->{attributes}}) {
2821     $title_el->set_attribute_ns (undef, [undef, $attr_name],
2822     $token->{attributes} ->{$attr_name}->{value});
2823     }
2824    
2825 wakaba 1.3 (defined $self->{head_element} ? $self->{head_element} : $self->{open_elements}->[-1]->[0])
2826 wakaba 1.1 ->append_child ($title_el);
2827     $self->{content_model_flag} = 'RCDATA';
2828    
2829     my $text = '';
2830     $token = $self->_get_next_token;
2831     while ($token->{type} eq 'character') {
2832     $text .= $token->{data};
2833     $token = $self->_get_next_token;
2834     }
2835     if (length $text) {
2836     $title_el->manakai_append_text ($text);
2837     }
2838    
2839     $self->{content_model_flag} = 'PCDATA';
2840    
2841     if ($token->{type} eq 'end tag' and
2842     $token->{tag_name} eq 'title') {
2843     ## Ignore the token
2844     } else {
2845 wakaba 1.3 $self->{parse_error}-> (type => 'in RCDATA:#'.$token->{type});
2846 wakaba 1.1 ## ISSUE: And ignore?
2847     }
2848     $token = $self->_get_next_token;
2849     return;
2850     } elsif ($token->{tag_name} eq 'body') {
2851 wakaba 1.3 $self->{parse_error}-> (type => 'in body:body');
2852 wakaba 1.1
2853 wakaba 1.3 if (@{$self->{open_elements}} == 1 or
2854     $self->{open_elements}->[1]->[1] ne 'body') {
2855 wakaba 1.1 ## Ignore the token
2856     } else {
2857 wakaba 1.3 my $body_el = $self->{open_elements}->[1]->[0];
2858 wakaba 1.1 for my $attr_name (keys %{$token->{attributes}}) {
2859     unless ($body_el->has_attribute_ns (undef, $attr_name)) {
2860     $body_el->set_attribute_ns
2861     (undef, [undef, $attr_name],
2862     $token->{attributes}->{$attr_name}->{value});
2863     }
2864     }
2865     }
2866     $token = $self->_get_next_token;
2867     return;
2868     } elsif ({
2869     address => 1, blockquote => 1, center => 1, dir => 1,
2870     div => 1, dl => 1, fieldset => 1, listing => 1,
2871     menu => 1, ol => 1, p => 1, ul => 1,
2872     pre => 1,
2873     }->{$token->{tag_name}}) {
2874     ## has a p element in scope
2875 wakaba 1.3 INSCOPE: for (reverse @{$self->{open_elements}}) {
2876 wakaba 1.1 if ($_->[1] eq 'p') {
2877     unshift @{$self->{token}}, $token;
2878     $token = {type => 'end tag', tag_name => 'p'};
2879     return;
2880     } elsif ({
2881     table => 1, caption => 1, td => 1, th => 1,
2882     button => 1, marquee => 1, object => 1, html => 1,
2883     }->{$_->[1]}) {
2884     last INSCOPE;
2885     }
2886     } # INSCOPE
2887    
2888    
2889     {
2890     my $el;
2891    
2892     $el = $self->{document}->create_element_ns
2893     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
2894    
2895     for my $attr_name (keys %{ $token->{attributes}}) {
2896     $el->set_attribute_ns (undef, [undef, $attr_name],
2897     $token->{attributes} ->{$attr_name}->{value});
2898     }
2899    
2900     $insert->($el);
2901 wakaba 1.3 push @{$self->{open_elements}}, [$el, $token->{tag_name}];
2902 wakaba 1.1 }
2903    
2904     if ($token->{tag_name} eq 'pre') {
2905     $token = $self->_get_next_token;
2906     if ($token->{type} eq 'character') {
2907     $token->{data} =~ s/^\x0A//;
2908     unless (length $token->{data}) {
2909     $token = $self->_get_next_token;
2910     }
2911     }
2912     } else {
2913     $token = $self->_get_next_token;
2914     }
2915     return;
2916     } elsif ($token->{tag_name} eq 'form') {
2917 wakaba 1.3 if (defined $self->{form_element}) {
2918     $self->{parse_error}-> (type => 'in form:form');
2919 wakaba 1.1 ## Ignore the token
2920     } else {
2921     ## has a p element in scope
2922 wakaba 1.3 INSCOPE: for (reverse @{$self->{open_elements}}) {
2923 wakaba 1.1 if ($_->[1] eq 'p') {
2924     unshift @{$self->{token}}, $token;
2925     $token = {type => 'end tag', tag_name => 'p'};
2926     return;
2927     } elsif ({
2928     table => 1, caption => 1, td => 1, th => 1,
2929     button => 1, marquee => 1, object => 1, html => 1,
2930     }->{$_->[1]}) {
2931     last INSCOPE;
2932     }
2933     } # INSCOPE
2934    
2935    
2936     {
2937     my $el;
2938    
2939     $el = $self->{document}->create_element_ns
2940     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
2941    
2942     for my $attr_name (keys %{ $token->{attributes}}) {
2943     $el->set_attribute_ns (undef, [undef, $attr_name],
2944     $token->{attributes} ->{$attr_name}->{value});
2945     }
2946    
2947     $insert->($el);
2948 wakaba 1.3 push @{$self->{open_elements}}, [$el, $token->{tag_name}];
2949 wakaba 1.1 }
2950    
2951 wakaba 1.3 $self->{form_element} = $self->{open_elements}->[-1]->[0];
2952 wakaba 1.1 $token = $self->_get_next_token;
2953     return;
2954     }
2955     } elsif ($token->{tag_name} eq 'li') {
2956     ## has a p element in scope
2957 wakaba 1.3 INSCOPE: for (reverse @{$self->{open_elements}}) {
2958 wakaba 1.1 if ($_->[1] eq 'p') {
2959     unshift @{$self->{token}}, $token;
2960     $token = {type => 'end tag', tag_name => 'p'};
2961     return;
2962     } elsif ({
2963     table => 1, caption => 1, td => 1, th => 1,
2964     button => 1, marquee => 1, object => 1, html => 1,
2965     }->{$_->[1]}) {
2966     last INSCOPE;
2967     }
2968     } # INSCOPE
2969    
2970     ## Step 1
2971     my $i = -1;
2972 wakaba 1.3 my $node = $self->{open_elements}->[$i];
2973 wakaba 1.1 LI: {
2974     ## Step 2
2975     if ($node->[1] eq 'li') {
2976 wakaba 1.3 splice @{$self->{open_elements}}, $i;
2977 wakaba 1.1 last LI;
2978     }
2979    
2980     ## Step 3
2981     if (not $formatting_category->{$node->[1]} and
2982     #not $phrasing_category->{$node->[1]} and
2983     ($special_category->{$node->[1]} or
2984     $scoping_category->{$node->[1]}) and
2985     $node->[1] ne 'address' and $node->[1] ne 'div') {
2986     last LI;
2987     }
2988    
2989     ## Step 4
2990     $i--;
2991 wakaba 1.3 $node = $self->{open_elements}->[$i];
2992 wakaba 1.1 redo LI;
2993     } # LI
2994    
2995    
2996     {
2997     my $el;
2998    
2999     $el = $self->{document}->create_element_ns
3000     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
3001    
3002     for my $attr_name (keys %{ $token->{attributes}}) {
3003     $el->set_attribute_ns (undef, [undef, $attr_name],
3004     $token->{attributes} ->{$attr_name}->{value});
3005     }
3006    
3007     $insert->($el);
3008 wakaba 1.3 push @{$self->{open_elements}}, [$el, $token->{tag_name}];
3009 wakaba 1.1 }
3010    
3011     $token = $self->_get_next_token;
3012     return;
3013     } elsif ($token->{tag_name} eq 'dd' or $token->{tag_name} eq 'dt') {
3014     ## has a p element in scope
3015 wakaba 1.3 INSCOPE: for (reverse @{$self->{open_elements}}) {
3016 wakaba 1.1 if ($_->[1] eq 'p') {
3017     unshift @{$self->{token}}, $token;
3018     $token = {type => 'end tag', tag_name => 'p'};
3019     return;
3020     } elsif ({
3021     table => 1, caption => 1, td => 1, th => 1,
3022     button => 1, marquee => 1, object => 1, html => 1,
3023     }->{$_->[1]}) {
3024     last INSCOPE;
3025     }
3026     } # INSCOPE
3027    
3028     ## Step 1
3029     my $i = -1;
3030 wakaba 1.3 my $node = $self->{open_elements}->[$i];
3031 wakaba 1.1 LI: {
3032     ## Step 2
3033     if ($node->[1] eq 'dt' or $node->[1] eq 'dd') {
3034 wakaba 1.3 splice @{$self->{open_elements}}, $i;
3035 wakaba 1.1 last LI;
3036     }
3037    
3038     ## Step 3
3039     if (not $formatting_category->{$node->[1]} and
3040     #not $phrasing_category->{$node->[1]} and
3041     ($special_category->{$node->[1]} or
3042     $scoping_category->{$node->[1]}) and
3043     $node->[1] ne 'address' and $node->[1] ne 'div') {
3044     last LI;
3045     }
3046    
3047     ## Step 4
3048     $i--;
3049 wakaba 1.3 $node = $self->{open_elements}->[$i];
3050 wakaba 1.1 redo LI;
3051     } # LI
3052    
3053    
3054     {
3055     my $el;
3056    
3057     $el = $self->{document}->create_element_ns
3058     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
3059    
3060     for my $attr_name (keys %{ $token->{attributes}}) {
3061     $el->set_attribute_ns (undef, [undef, $attr_name],
3062     $token->{attributes} ->{$attr_name}->{value});
3063     }
3064    
3065     $insert->($el);
3066 wakaba 1.3 push @{$self->{open_elements}}, [$el, $token->{tag_name}];
3067 wakaba 1.1 }
3068    
3069     $token = $self->_get_next_token;
3070     return;
3071     } elsif ($token->{tag_name} eq 'plaintext') {
3072     ## has a p element in scope
3073 wakaba 1.3 INSCOPE: for (reverse @{$self->{open_elements}}) {
3074 wakaba 1.1 if ($_->[1] eq 'p') {
3075     unshift @{$self->{token}}, $token;
3076     $token = {type => 'end tag', tag_name => 'p'};
3077     return;
3078     } elsif ({
3079     table => 1, caption => 1, td => 1, th => 1,
3080     button => 1, marquee => 1, object => 1, html => 1,
3081     }->{$_->[1]}) {
3082     last INSCOPE;
3083     }
3084     } # INSCOPE
3085    
3086    
3087     {
3088     my $el;
3089    
3090     $el = $self->{document}->create_element_ns
3091     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
3092    
3093     for my $attr_name (keys %{ $token->{attributes}}) {
3094     $el->set_attribute_ns (undef, [undef, $attr_name],
3095     $token->{attributes} ->{$attr_name}->{value});
3096     }
3097    
3098     $insert->($el);
3099 wakaba 1.3 push @{$self->{open_elements}}, [$el, $token->{tag_name}];
3100 wakaba 1.1 }
3101    
3102    
3103     $self->{content_model_flag} = 'PLAINTEXT';
3104    
3105     $token = $self->_get_next_token;
3106     return;
3107     } elsif ({
3108     h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,
3109     }->{$token->{tag_name}}) {
3110     ## has a p element in scope
3111 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3112     my $node = $self->{open_elements}->[$_];
3113 wakaba 1.1 if ($node->[1] eq 'p') {
3114     unshift @{$self->{token}}, $token;
3115     $token = {type => 'end tag', tag_name => 'p'};
3116     return;
3117     } elsif ({
3118     table => 1, caption => 1, td => 1, th => 1,
3119     button => 1, marquee => 1, object => 1, html => 1,
3120     }->{$node->[1]}) {
3121     last INSCOPE;
3122     }
3123     } # INSCOPE
3124    
3125     ## has an element in scope
3126     my $i;
3127 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3128     my $node = $self->{open_elements}->[$_];
3129 wakaba 1.1 if ({
3130     h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,
3131     }->{$node->[1]}) {
3132     $i = $_;
3133     last INSCOPE;
3134     } elsif ({
3135     table => 1, caption => 1, td => 1, th => 1,
3136     button => 1, marquee => 1, object => 1, html => 1,
3137     }->{$node->[1]}) {
3138     last INSCOPE;
3139     }
3140     } # INSCOPE
3141    
3142     if (defined $i) {
3143 wakaba 1.3 $self->{parse_error}-> (type => 'in hn:hn');
3144     splice @{$self->{open_elements}}, $i;
3145 wakaba 1.1 }
3146    
3147    
3148     {
3149     my $el;
3150    
3151     $el = $self->{document}->create_element_ns
3152     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
3153    
3154     for my $attr_name (keys %{ $token->{attributes}}) {
3155     $el->set_attribute_ns (undef, [undef, $attr_name],
3156     $token->{attributes} ->{$attr_name}->{value});
3157     }
3158    
3159     $insert->($el);
3160 wakaba 1.3 push @{$self->{open_elements}}, [$el, $token->{tag_name}];
3161 wakaba 1.1 }
3162    
3163    
3164     $token = $self->_get_next_token;
3165     return;
3166     } elsif ($token->{tag_name} eq 'a') {
3167     AFE: for my $i (reverse 0..$#$active_formatting_elements) {
3168     my $node = $active_formatting_elements->[$i];
3169     if ($node->[1] eq 'a') {
3170 wakaba 1.3 $self->{parse_error}-> (type => 'in a:a');
3171 wakaba 1.1
3172     unshift @{$self->{token}}, $token;
3173     $token = {type => 'end tag', tag_name => 'a'};
3174     $formatting_end_tag->($token->{tag_name});
3175    
3176     AFE2: for (reverse 0..$#$active_formatting_elements) {
3177     if ($active_formatting_elements->[$_]->[0] eq $node->[0]) {
3178     splice @$active_formatting_elements, $_, 1;
3179     last AFE2;
3180     }
3181     } # AFE2
3182 wakaba 1.3 OE: for (reverse 0..$#{$self->{open_elements}}) {
3183     if ($self->{open_elements}->[$_]->[0] eq $node->[0]) {
3184     splice @{$self->{open_elements}}, $_, 1;
3185 wakaba 1.1 last OE;
3186     }
3187     } # OE
3188     last AFE;
3189     } elsif ($node->[0] eq '#marker') {
3190     last AFE;
3191     }
3192     } # AFE
3193    
3194     $reconstruct_active_formatting_elements->($insert_to_current);
3195    
3196    
3197     {
3198     my $el;
3199    
3200     $el = $self->{document}->create_element_ns
3201     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
3202    
3203     for my $attr_name (keys %{ $token->{attributes}}) {
3204     $el->set_attribute_ns (undef, [undef, $attr_name],
3205     $token->{attributes} ->{$attr_name}->{value});
3206     }
3207    
3208     $insert->($el);
3209 wakaba 1.3 push @{$self->{open_elements}}, [$el, $token->{tag_name}];
3210 wakaba 1.1 }
3211    
3212 wakaba 1.3 push @$active_formatting_elements, $self->{open_elements}->[-1];
3213 wakaba 1.1
3214     $token = $self->_get_next_token;
3215     return;
3216     } elsif ({
3217     b => 1, big => 1, em => 1, font => 1, i => 1,
3218     nobr => 1, s => 1, small => 1, strile => 1,
3219     strong => 1, tt => 1, u => 1,
3220     }->{$token->{tag_name}}) {
3221     $reconstruct_active_formatting_elements->($insert_to_current);
3222    
3223    
3224     {
3225     my $el;
3226    
3227     $el = $self->{document}->create_element_ns
3228     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
3229    
3230     for my $attr_name (keys %{ $token->{attributes}}) {
3231     $el->set_attribute_ns (undef, [undef, $attr_name],
3232     $token->{attributes} ->{$attr_name}->{value});
3233     }
3234    
3235     $insert->($el);
3236 wakaba 1.3 push @{$self->{open_elements}}, [$el, $token->{tag_name}];
3237 wakaba 1.1 }
3238    
3239 wakaba 1.3 push @$active_formatting_elements, $self->{open_elements}->[-1];
3240 wakaba 1.1
3241     $token = $self->_get_next_token;
3242     return;
3243     } elsif ($token->{tag_name} eq 'button') {
3244     ## has a button element in scope
3245 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3246     my $node = $self->{open_elements}->[$_];
3247 wakaba 1.1 if ($node->[1] eq 'button') {
3248 wakaba 1.3 $self->{parse_error}-> (type => 'in button:button');
3249 wakaba 1.1 unshift @{$self->{token}}, $token;
3250     $token = {type => 'end tag', tag_name => 'button'};
3251     return;
3252     } elsif ({
3253     table => 1, caption => 1, td => 1, th => 1,
3254     button => 1, marquee => 1, object => 1, html => 1,
3255     }->{$node->[1]}) {
3256     last INSCOPE;
3257     }
3258     } # INSCOPE
3259    
3260     $reconstruct_active_formatting_elements->($insert_to_current);
3261    
3262    
3263     {
3264     my $el;
3265    
3266     $el = $self->{document}->create_element_ns
3267     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
3268    
3269     for my $attr_name (keys %{ $token->{attributes}}) {
3270     $el->set_attribute_ns (undef, [undef, $attr_name],
3271     $token->{attributes} ->{$attr_name}->{value});
3272     }
3273    
3274     $insert->($el);
3275 wakaba 1.3 push @{$self->{open_elements}}, [$el, $token->{tag_name}];
3276 wakaba 1.1 }
3277    
3278     push @$active_formatting_elements, ['#marker', ''];
3279    
3280     $token = $self->_get_next_token;
3281     return;
3282     } elsif ($token->{tag_name} eq 'marquee' or
3283     $token->{tag_name} eq 'object') {
3284     $reconstruct_active_formatting_elements->($insert_to_current);
3285    
3286    
3287     {
3288     my $el;
3289    
3290     $el = $self->{document}->create_element_ns
3291     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
3292    
3293     for my $attr_name (keys %{ $token->{attributes}}) {
3294     $el->set_attribute_ns (undef, [undef, $attr_name],
3295     $token->{attributes} ->{$attr_name}->{value});
3296     }
3297    
3298     $insert->($el);
3299 wakaba 1.3 push @{$self->{open_elements}}, [$el, $token->{tag_name}];
3300 wakaba 1.1 }
3301    
3302     push @$active_formatting_elements, ['#marker', ''];
3303    
3304     $token = $self->_get_next_token;
3305     return;
3306     } elsif ($token->{tag_name} eq 'xmp') {
3307     $reconstruct_active_formatting_elements->($insert_to_current);
3308    
3309    
3310     {
3311     my $el;
3312    
3313     $el = $self->{document}->create_element_ns
3314     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
3315    
3316     for my $attr_name (keys %{ $token->{attributes}}) {
3317     $el->set_attribute_ns (undef, [undef, $attr_name],
3318     $token->{attributes} ->{$attr_name}->{value});
3319     }
3320    
3321     $insert->($el);
3322 wakaba 1.3 push @{$self->{open_elements}}, [$el, $token->{tag_name}];
3323 wakaba 1.1 }
3324    
3325    
3326     $self->{content_model_flag} = 'CDATA';
3327    
3328     $token = $self->_get_next_token;
3329     return;
3330     } elsif ($token->{tag_name} eq 'table') {
3331     ## has a p element in scope
3332 wakaba 1.3 INSCOPE: for (reverse @{$self->{open_elements}}) {
3333 wakaba 1.1 if ($_->[1] eq 'p') {
3334     unshift @{$self->{token}}, $token;
3335     $token = {type => 'end tag', tag_name => 'p'};
3336     return;
3337     } elsif ({
3338     table => 1, caption => 1, td => 1, th => 1,
3339     button => 1, marquee => 1, object => 1, html => 1,
3340     }->{$_->[1]}) {
3341     last INSCOPE;
3342     }
3343     } # INSCOPE
3344    
3345    
3346     {
3347     my $el;
3348    
3349     $el = $self->{document}->create_element_ns
3350     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
3351    
3352     for my $attr_name (keys %{ $token->{attributes}}) {
3353     $el->set_attribute_ns (undef, [undef, $attr_name],
3354     $token->{attributes} ->{$attr_name}->{value});
3355     }
3356    
3357     $insert->($el);
3358 wakaba 1.3 push @{$self->{open_elements}}, [$el, $token->{tag_name}];
3359 wakaba 1.1 }
3360    
3361    
3362 wakaba 1.3 $self->{insertion_mode} = 'in table';
3363 wakaba 1.1
3364     $token = $self->_get_next_token;
3365     return;
3366     } elsif ({
3367     area => 1, basefont => 1, bgsound => 1, br => 1,
3368     embed => 1, img => 1, param => 1, spacer => 1, wbr => 1,
3369     image => 1,
3370     }->{$token->{tag_name}}) {
3371     if ($token->{tag_name} eq 'image') {
3372 wakaba 1.3 $self->{parse_error}-> (type => 'image');
3373 wakaba 1.1 $token->{tag_name} = 'img';
3374     }
3375    
3376     $reconstruct_active_formatting_elements->($insert_to_current);
3377    
3378    
3379     {
3380     my $el;
3381    
3382     $el = $self->{document}->create_element_ns
3383     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
3384    
3385     for my $attr_name (keys %{ $token->{attributes}}) {
3386     $el->set_attribute_ns (undef, [undef, $attr_name],
3387     $token->{attributes} ->{$attr_name}->{value});
3388     }
3389    
3390     $insert->($el);
3391 wakaba 1.3 push @{$self->{open_elements}}, [$el, $token->{tag_name}];
3392 wakaba 1.1 }
3393    
3394 wakaba 1.3 pop @{$self->{open_elements}};
3395 wakaba 1.1
3396     $token = $self->_get_next_token;
3397     return;
3398     } elsif ($token->{tag_name} eq 'hr') {
3399     ## has a p element in scope
3400 wakaba 1.3 INSCOPE: for (reverse @{$self->{open_elements}}) {
3401 wakaba 1.1 if ($_->[1] eq 'p') {
3402     unshift @{$self->{token}}, $token;
3403     $token = {type => 'end tag', tag_name => 'p'};
3404     return;
3405     } elsif ({
3406     table => 1, caption => 1, td => 1, th => 1,
3407     button => 1, marquee => 1, object => 1, html => 1,
3408     }->{$_->[1]}) {
3409     last INSCOPE;
3410     }
3411     } # INSCOPE
3412    
3413    
3414     {
3415     my $el;
3416    
3417     $el = $self->{document}->create_element_ns
3418     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
3419    
3420     for my $attr_name (keys %{ $token->{attributes}}) {
3421     $el->set_attribute_ns (undef, [undef, $attr_name],
3422     $token->{attributes} ->{$attr_name}->{value});
3423     }
3424    
3425     $insert->($el);
3426 wakaba 1.3 push @{$self->{open_elements}}, [$el, $token->{tag_name}];
3427 wakaba 1.1 }
3428    
3429 wakaba 1.3 pop @{$self->{open_elements}};
3430 wakaba 1.1
3431     $token = $self->_get_next_token;
3432     return;
3433     } elsif ($token->{tag_name} eq 'input') {
3434     $reconstruct_active_formatting_elements->($insert_to_current);
3435    
3436    
3437     {
3438     my $el;
3439    
3440     $el = $self->{document}->create_element_ns
3441     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
3442    
3443     for my $attr_name (keys %{ $token->{attributes}}) {
3444     $el->set_attribute_ns (undef, [undef, $attr_name],
3445     $token->{attributes} ->{$attr_name}->{value});
3446     }
3447    
3448     $insert->($el);
3449 wakaba 1.3 push @{$self->{open_elements}}, [$el, $token->{tag_name}];
3450 wakaba 1.1 }
3451    
3452 wakaba 1.3 ## TODO: associate with $self->{form_element} if defined
3453     pop @{$self->{open_elements}};
3454 wakaba 1.1
3455     $token = $self->_get_next_token;
3456     return;
3457     } elsif ($token->{tag_name} eq 'isindex') {
3458 wakaba 1.3 $self->{parse_error}-> (type => 'isindex');
3459 wakaba 1.1
3460 wakaba 1.3 if (defined $self->{form_element}) {
3461 wakaba 1.1 ## Ignore the token
3462     $token = $self->_get_next_token;
3463     return;
3464     } else {
3465     my $at = $token->{attributes};
3466     $at->{name} = {name => 'name', value => 'isindex'};
3467     my @tokens = (
3468     {type => 'start tag', tag_name => 'form'},
3469     {type => 'start tag', tag_name => 'hr'},
3470     {type => 'start tag', tag_name => 'p'},
3471     {type => 'start tag', tag_name => 'label'},
3472     {type => 'character',
3473     data => 'This is a searchable index. Insert your search keywords here: '}, # SHOULD
3474     ## TODO: make this configurable
3475     {type => 'start tag', tag_name => 'input', attributes => $at},
3476     #{type => 'character', data => ''}, # SHOULD
3477     {type => 'end tag', tag_name => 'label'},
3478     {type => 'end tag', tag_name => 'p'},
3479     {type => 'start tag', tag_name => 'hr'},
3480     {type => 'end tag', tag_name => 'form'},
3481     );
3482     $token = shift @tokens;
3483     unshift @{$self->{token}}, (@tokens);
3484     return;
3485     }
3486     } elsif ({
3487     textarea => 1,
3488     noembed => 1,
3489     noframes => 1,
3490     noscript => 0, ## TODO: 1 if scripting is enabled
3491     }->{$token->{tag_name}}) {
3492     my $tag_name = $token->{tag_name};
3493     my $el;
3494    
3495     $el = $self->{document}->create_element_ns
3496     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
3497    
3498     for my $attr_name (keys %{ $token->{attributes}}) {
3499     $el->set_attribute_ns (undef, [undef, $attr_name],
3500     $token->{attributes} ->{$attr_name}->{value});
3501     }
3502    
3503    
3504     if ($token->{tag_name} eq 'textarea') {
3505 wakaba 1.3 ## TODO: $self->{form_element} if defined
3506 wakaba 1.1 $self->{content_model_flag} = 'RCDATA';
3507     } else {
3508     $self->{content_model_flag} = 'CDATA';
3509     }
3510    
3511     $insert->($el);
3512    
3513     my $text = '';
3514     $token = $self->_get_next_token;
3515     while ($token->{type} eq 'character') {
3516     $text .= $token->{data};
3517     $token = $self->_get_next_token;
3518     }
3519     if (length $text) {
3520     $el->manakai_append_text ($text);
3521     }
3522    
3523     $self->{content_model_flag} = 'PCDATA';
3524    
3525     if ($token->{type} eq 'end tag' and
3526     $token->{tag_name} eq $tag_name) {
3527     ## Ignore the token
3528     } else {
3529 wakaba 1.3 if ($token->{tag_name} eq 'textarea') {
3530     $self->{parse_error}-> (type => 'in CDATA:#'.$token->{type});
3531     } else {
3532     $self->{parse_error}-> (type => 'in RCDATA:#'.$token->{type});
3533     }
3534 wakaba 1.1 ## ISSUE: And ignore?
3535     }
3536     $token = $self->_get_next_token;
3537     return;
3538     } elsif ($token->{tag_name} eq 'select') {
3539     $reconstruct_active_formatting_elements->($insert_to_current);
3540    
3541    
3542     {
3543     my $el;
3544    
3545     $el = $self->{document}->create_element_ns
3546     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
3547    
3548     for my $attr_name (keys %{ $token->{attributes}}) {
3549     $el->set_attribute_ns (undef, [undef, $attr_name],
3550     $token->{attributes} ->{$attr_name}->{value});
3551     }
3552    
3553     $insert->($el);
3554 wakaba 1.3 push @{$self->{open_elements}}, [$el, $token->{tag_name}];
3555 wakaba 1.1 }
3556    
3557    
3558 wakaba 1.3 $self->{insertion_mode} = 'in select';
3559 wakaba 1.1 $token = $self->_get_next_token;
3560     return;
3561     } elsif ({
3562     caption => 1, col => 1, colgroup => 1, frame => 1,
3563     frameset => 1, head => 1, option => 1, optgroup => 1,
3564     tbody => 1, td => 1, tfoot => 1, th => 1,
3565     thead => 1, tr => 1,
3566     }->{$token->{tag_name}}) {
3567 wakaba 1.3 $self->{parse_error}-> (type => 'in body:'.$token->{tag_name});
3568 wakaba 1.1 ## Ignore the token
3569     $token = $self->_get_next_token;
3570     return;
3571    
3572     ## ISSUE: An issue on HTML5 new elements in the spec.
3573     } else {
3574     $reconstruct_active_formatting_elements->($insert_to_current);
3575    
3576    
3577     {
3578     my $el;
3579    
3580     $el = $self->{document}->create_element_ns
3581     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
3582    
3583     for my $attr_name (keys %{ $token->{attributes}}) {
3584     $el->set_attribute_ns (undef, [undef, $attr_name],
3585     $token->{attributes} ->{$attr_name}->{value});
3586     }
3587    
3588     $insert->($el);
3589 wakaba 1.3 push @{$self->{open_elements}}, [$el, $token->{tag_name}];
3590 wakaba 1.1 }
3591    
3592    
3593     $token = $self->_get_next_token;
3594     return;
3595     }
3596     } elsif ($token->{type} eq 'end tag') {
3597     if ($token->{tag_name} eq 'body') {
3598 wakaba 1.3 if (@{$self->{open_elements}} > 1 and $self->{open_elements}->[1]->[1] eq 'body') {
3599 wakaba 1.1 ## ISSUE: There is an issue in the spec.
3600 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne 'body') {
3601     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3602 wakaba 1.1 }
3603 wakaba 1.3 $self->{insertion_mode} = 'after body';
3604 wakaba 1.1 $token = $self->_get_next_token;
3605     return;
3606     } else {
3607 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
3608 wakaba 1.1 ## Ignore the token
3609     $token = $self->_get_next_token;
3610     return;
3611     }
3612     } elsif ($token->{tag_name} eq 'html') {
3613 wakaba 1.3 if (@{$self->{open_elements}} > 1 and $self->{open_elements}->[1]->[1] eq 'body') {
3614 wakaba 1.1 ## ISSUE: There is an issue in the spec.
3615 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne 'body') {
3616     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[1]->[1]);
3617 wakaba 1.1 }
3618 wakaba 1.3 $self->{insertion_mode} = 'after body';
3619 wakaba 1.1 ## reprocess
3620     return;
3621     } else {
3622 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
3623 wakaba 1.1 ## Ignore the token
3624     $token = $self->_get_next_token;
3625     return;
3626     }
3627     } elsif ({
3628     address => 1, blockquote => 1, center => 1, dir => 1,
3629     div => 1, dl => 1, fieldset => 1, listing => 1,
3630     menu => 1, ol => 1, pre => 1, ul => 1,
3631     form => 1,
3632     p => 1,
3633     dd => 1, dt => 1, li => 1,
3634     button => 1, marquee => 1, object => 1,
3635     }->{$token->{tag_name}}) {
3636     ## has an element in scope
3637     my $i;
3638 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3639     my $node = $self->{open_elements}->[$_];
3640 wakaba 1.1 if ($node->[1] eq $token->{tag_name}) {
3641     ## generate implied end tags
3642     if ({
3643     dd => ($token->{tag_name} ne 'dd'),
3644     dt => ($token->{tag_name} ne 'dt'),
3645     li => ($token->{tag_name} ne 'li'),
3646     p => ($token->{tag_name} ne 'p'),
3647     td => 1, th => 1, tr => 1,
3648 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
3649 wakaba 1.1 unshift @{$self->{token}}, $token;
3650     $token = {type => 'end tag',
3651 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3652 wakaba 1.1 return;
3653     }
3654     $i = $_;
3655     last INSCOPE unless $token->{tag_name} eq 'p';
3656     } elsif ({
3657     table => 1, caption => 1, td => 1, th => 1,
3658     button => 1, marquee => 1, object => 1, html => 1,
3659     }->{$node->[1]}) {
3660     last INSCOPE;
3661     }
3662     } # INSCOPE
3663    
3664 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne $token->{tag_name}) {
3665     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3666 wakaba 1.1 }
3667    
3668 wakaba 1.3 splice @{$self->{open_elements}}, $i if defined $i;
3669     undef $self->{form_element} if $token->{tag_name} eq 'form';
3670 wakaba 1.1 $clear_up_to_marker->()
3671     if {
3672     button => 1, marquee => 1, object => 1,
3673     }->{$token->{tag_name}};
3674     $token = $self->_get_next_token;
3675     return;
3676     } elsif ({
3677     h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,
3678     }->{$token->{tag_name}}) {
3679     ## has an element in scope
3680     my $i;
3681 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3682     my $node = $self->{open_elements}->[$_];
3683 wakaba 1.1 if ({
3684     h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,
3685     }->{$node->[1]}) {
3686     ## generate implied end tags
3687     if ({
3688     dd => 1, dt => 1, li => 1, p => 1,
3689     td => 1, th => 1, tr => 1,
3690 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
3691 wakaba 1.1 unshift @{$self->{token}}, $token;
3692     $token = {type => 'end tag',
3693 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3694 wakaba 1.1 return;
3695     }
3696     $i = $_;
3697     last INSCOPE;
3698     } elsif ({
3699     table => 1, caption => 1, td => 1, th => 1,
3700     button => 1, marquee => 1, object => 1, html => 1,
3701     }->{$node->[1]}) {
3702     last INSCOPE;
3703     }
3704     } # INSCOPE
3705    
3706 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne $token->{tag_name}) {
3707     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3708 wakaba 1.1 }
3709    
3710 wakaba 1.3 splice @{$self->{open_elements}}, $i if defined $i;
3711 wakaba 1.1 $token = $self->_get_next_token;
3712     return;
3713     } elsif ({
3714     a => 1,
3715     b => 1, big => 1, em => 1, font => 1, i => 1,
3716     nobr => 1, s => 1, small => 1, strile => 1,
3717     strong => 1, tt => 1, u => 1,
3718     }->{$token->{tag_name}}) {
3719     $formatting_end_tag->($token->{tag_name});
3720     return;
3721     } elsif ({
3722     caption => 1, col => 1, colgroup => 1, frame => 1,
3723     frameset => 1, head => 1, option => 1, optgroup => 1,
3724     tbody => 1, td => 1, tfoot => 1, th => 1,
3725     thead => 1, tr => 1,
3726     area => 1, basefont => 1, bgsound => 1, br => 1,
3727     embed => 1, hr => 1, iframe => 1, image => 1,
3728     img => 1, input => 1, isindex=> 1, noembed => 1,
3729     noframes => 1, param => 1, select => 1, spacer => 1,
3730     table => 1, textarea => 1, wbr => 1,
3731     noscript => 0, ## TODO: if scripting is enabled
3732     }->{$token->{tag_name}}) {
3733 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
3734 wakaba 1.1 ## Ignore the token
3735     $token = $self->_get_next_token;
3736     return;
3737    
3738     ## ISSUE: Issue on HTML5 new elements in spec
3739    
3740     } else {
3741     ## Step 1
3742     my $node_i = -1;
3743 wakaba 1.3 my $node = $self->{open_elements}->[$node_i];
3744 wakaba 1.1
3745     ## Step 2
3746     S2: {
3747     if ($node->[1] eq $token->{tag_name}) {
3748     ## Step 1
3749     ## generate implied end tags
3750     if ({
3751     dd => 1, dt => 1, li => 1, p => 1,
3752     td => 1, th => 1, tr => 1,
3753 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
3754 wakaba 1.1 unshift @{$self->{token}}, $token;
3755     $token = {type => 'end tag',
3756 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
3757 wakaba 1.1 return;
3758     }
3759    
3760     ## Step 2
3761 wakaba 1.3 if ($token->{tag_name} ne $self->{open_elements}->[-1]->[1]) {
3762     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3763 wakaba 1.1 }
3764    
3765     ## Step 3
3766 wakaba 1.3 splice @{$self->{open_elements}}, $node_i;
3767    
3768     $token = $self->_get_next_token;
3769 wakaba 1.1 last S2;
3770     } else {
3771     ## Step 3
3772     if (not $formatting_category->{$node->[1]} and
3773     #not $phrasing_category->{$node->[1]} and
3774     ($special_category->{$node->[1]} or
3775     $scoping_category->{$node->[1]})) {
3776 wakaba 1.3 $self->{parse_error}-> (type => 'not closed:'.$node->[1]);
3777 wakaba 1.1 ## Ignore the token
3778     $token = $self->_get_next_token;
3779     last S2;
3780     }
3781     }
3782    
3783     ## Step 4
3784     $node_i--;
3785 wakaba 1.3 $node = $self->{open_elements}->[$node_i];
3786 wakaba 1.1
3787     ## Step 5;
3788     redo S2;
3789     } # S2
3790 wakaba 1.3 return;
3791 wakaba 1.1 }
3792     }
3793     }; # $in_body
3794    
3795     B: {
3796 wakaba 1.3 if ($phase eq 'main') {
3797 wakaba 1.1 if ($token->{type} eq 'DOCTYPE') {
3798 wakaba 1.3 $self->{parse_error}-> (type => 'in html:#DOCTYPE');
3799 wakaba 1.1 ## Ignore the token
3800     ## Stay in the phase
3801     $token = $self->_get_next_token;
3802     redo B;
3803     } elsif ($token->{type} eq 'start tag' and
3804     $token->{tag_name} eq 'html') {
3805     ## TODO: unless it is the first start tag token, parse-error
3806 wakaba 1.3 my $top_el = $self->{open_elements}->[0]->[0];
3807 wakaba 1.1 for my $attr_name (keys %{$token->{attributes}}) {
3808     unless ($top_el->has_attribute_ns (undef, $attr_name)) {
3809     $top_el->set_attribute_ns
3810     (undef, [undef, $attr_name],
3811     $token->{attributes}->{$attr_name}->{value});
3812     }
3813     }
3814     $token = $self->_get_next_token;
3815     redo B;
3816     } elsif ($token->{type} eq 'end-of-file') {
3817     ## Generate implied end tags
3818     if ({
3819     dd => 1, dt => 1, li => 1, p => 1, td => 1, th => 1, tr => 1,
3820 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
3821 wakaba 1.1 unshift @{$self->{token}}, $token;
3822 wakaba 1.3 $token = {type => 'end tag', tag_name => $self->{open_elements}->[-1]->[1]};
3823 wakaba 1.1 redo B;
3824     }
3825    
3826 wakaba 1.3 if (@{$self->{open_elements}} > 2 or
3827     (@{$self->{open_elements}} == 2 and $self->{open_elements}->[1]->[1] ne 'body')) {
3828     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3829     } elsif (defined $self->{inner_html_node} and
3830     @{$self->{open_elements}} > 1 and
3831     $self->{open_elements}->[1]->[1] ne 'body') {
3832     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
3833 wakaba 1.1 }
3834    
3835     ## Stop parsing
3836     last B;
3837    
3838     ## ISSUE: There is an issue in the spec.
3839     } else {
3840 wakaba 1.3 if ($self->{insertion_mode} eq 'before head') {
3841 wakaba 1.1 if ($token->{type} eq 'character') {
3842     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
3843 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
3844 wakaba 1.1 unless (length $token->{data}) {
3845     $token = $self->_get_next_token;
3846     redo B;
3847     }
3848     }
3849     ## As if <head>
3850    
3851 wakaba 1.3 $self->{head_element} = $self->{document}->create_element_ns
3852 wakaba 1.1 (q<http://www.w3.org/1999/xhtml>, [undef, 'head']);
3853    
3854 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
3855     push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3856     $self->{insertion_mode} = 'in head';
3857 wakaba 1.1 ## reprocess
3858     redo B;
3859     } elsif ($token->{type} eq 'comment') {
3860     my $comment = $self->{document}->create_comment ($token->{data});
3861 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
3862 wakaba 1.1 $token = $self->_get_next_token;
3863     redo B;
3864     } elsif ($token->{type} eq 'start tag') {
3865     my $attr = $token->{tag_name} eq 'head' ? $token->{attributes} : {};
3866    
3867 wakaba 1.3 $self->{head_element} = $self->{document}->create_element_ns
3868 wakaba 1.1 (q<http://www.w3.org/1999/xhtml>, [undef, 'head']);
3869    
3870     for my $attr_name (keys %{ $attr}) {
3871 wakaba 1.3 $self->{head_element}->set_attribute_ns (undef, [undef, $attr_name],
3872 wakaba 1.1 $attr ->{$attr_name}->{value});
3873     }
3874    
3875 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
3876     push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3877     $self->{insertion_mode} = 'in head';
3878 wakaba 1.1 if ($token->{tag_name} eq 'head') {
3879     $token = $self->_get_next_token;
3880     #} elsif ({
3881     # base => 1, link => 1, meta => 1,
3882     # script => 1, style => 1, title => 1,
3883     # }->{$token->{tag_name}}) {
3884     # ## reprocess
3885     } else {
3886     ## reprocess
3887     }
3888     redo B;
3889     } elsif ($token->{type} eq 'end tag') {
3890     if ($token->{tag_name} eq 'html') {
3891     ## As if <head>
3892    
3893 wakaba 1.3 $self->{head_element} = $self->{document}->create_element_ns
3894 wakaba 1.1 (q<http://www.w3.org/1999/xhtml>, [undef, 'head']);
3895    
3896 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
3897     push @{$self->{open_elements}}, [$self->{head_element}, 'head'];
3898     $self->{insertion_mode} = 'in head';
3899 wakaba 1.1 ## reprocess
3900     redo B;
3901     } else {
3902 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
3903 wakaba 1.1 ## Ignore the token
3904     $token = $self->_get_next_token;
3905     redo B;
3906     }
3907     } else {
3908     die "$0: $token->{type}: Unknown type";
3909     }
3910 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'in head') {
3911 wakaba 1.1 if ($token->{type} eq 'character') {
3912     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
3913 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
3914 wakaba 1.1 unless (length $token->{data}) {
3915     $token = $self->_get_next_token;
3916     redo B;
3917     }
3918     }
3919    
3920     #
3921     } elsif ($token->{type} eq 'comment') {
3922     my $comment = $self->{document}->create_comment ($token->{data});
3923 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
3924 wakaba 1.1 $token = $self->_get_next_token;
3925     redo B;
3926     } elsif ($token->{type} eq 'start tag') {
3927     if ($token->{tag_name} eq 'title') {
3928     ## NOTE: There is an "as if in head" code clone
3929     my $title_el;
3930    
3931     $title_el = $self->{document}->create_element_ns
3932     (q<http://www.w3.org/1999/xhtml>, [undef, 'title']);
3933    
3934     for my $attr_name (keys %{ $token->{attributes}}) {
3935     $title_el->set_attribute_ns (undef, [undef, $attr_name],
3936     $token->{attributes} ->{$attr_name}->{value});
3937     }
3938    
3939 wakaba 1.3 (defined $self->{head_element} ? $self->{head_element} : $self->{open_elements}->[-1]->[0])
3940 wakaba 1.1 ->append_child ($title_el);
3941     $self->{content_model_flag} = 'RCDATA';
3942    
3943     my $text = '';
3944     $token = $self->_get_next_token;
3945     while ($token->{type} eq 'character') {
3946     $text .= $token->{data};
3947     $token = $self->_get_next_token;
3948     }
3949     if (length $text) {
3950     $title_el->manakai_append_text ($text);
3951     }
3952    
3953     $self->{content_model_flag} = 'PCDATA';
3954    
3955     if ($token->{type} eq 'end tag' and
3956     $token->{tag_name} eq 'title') {
3957     ## Ignore the token
3958     } else {
3959 wakaba 1.3 $self->{parse_error}-> (type => 'in RCDATA:#'.$token->{type});
3960 wakaba 1.1 ## ISSUE: And ignore?
3961     }
3962     $token = $self->_get_next_token;
3963     redo B;
3964     } elsif ($token->{tag_name} eq 'style') {
3965     $style_start_tag->();
3966     redo B;
3967     } elsif ($token->{tag_name} eq 'script') {
3968     $script_start_tag->();
3969     redo B;
3970     } elsif ({base => 1, link => 1, meta => 1}->{$token->{tag_name}}) {
3971     ## NOTE: There are "as if in head" code clones
3972     my $el;
3973    
3974     $el = $self->{document}->create_element_ns
3975     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
3976    
3977     for my $attr_name (keys %{ $token->{attributes}}) {
3978     $el->set_attribute_ns (undef, [undef, $attr_name],
3979     $token->{attributes} ->{$attr_name}->{value});
3980     }
3981    
3982 wakaba 1.3 (defined $self->{head_element} ? $self->{head_element} : $self->{open_elements}->[-1]->[0])
3983 wakaba 1.1 ->append_child ($el);
3984    
3985     $token = $self->_get_next_token;
3986     redo B;
3987     } elsif ($token->{tag_name} eq 'head') {
3988 wakaba 1.3 $self->{parse_error}-> (type => 'in head:head');
3989 wakaba 1.1 ## Ignore the token
3990     $token = $self->_get_next_token;
3991     redo B;
3992     } else {
3993     #
3994     }
3995     } elsif ($token->{type} eq 'end tag') {
3996     if ($token->{tag_name} eq 'head') {
3997 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] eq 'head') {
3998     pop @{$self->{open_elements}};
3999 wakaba 1.1 } else {
4000 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:head');
4001 wakaba 1.1 }
4002 wakaba 1.3 $self->{insertion_mode} = 'after head';
4003 wakaba 1.1 $token = $self->_get_next_token;
4004     redo B;
4005     } elsif ($token->{tag_name} eq 'html') {
4006     #
4007     } else {
4008 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
4009 wakaba 1.1 ## Ignore the token
4010     $token = $self->_get_next_token;
4011     redo B;
4012     }
4013     } else {
4014     #
4015     }
4016    
4017 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] eq 'head') {
4018 wakaba 1.1 ## As if </head>
4019 wakaba 1.3 pop @{$self->{open_elements}};
4020 wakaba 1.1 }
4021 wakaba 1.3 $self->{insertion_mode} = 'after head';
4022 wakaba 1.1 ## reprocess
4023     redo B;
4024    
4025     ## ISSUE: An issue in the spec.
4026 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'after head') {
4027 wakaba 1.1 if ($token->{type} eq 'character') {
4028     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
4029 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
4030 wakaba 1.1 unless (length $token->{data}) {
4031     $token = $self->_get_next_token;
4032     redo B;
4033     }
4034     }
4035    
4036     #
4037     } elsif ($token->{type} eq 'comment') {
4038     my $comment = $self->{document}->create_comment ($token->{data});
4039 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
4040 wakaba 1.1 $token = $self->_get_next_token;
4041     redo B;
4042     } elsif ($token->{type} eq 'start tag') {
4043     if ($token->{tag_name} eq 'body') {
4044    
4045     {
4046     my $el;
4047    
4048     $el = $self->{document}->create_element_ns
4049     (q<http://www.w3.org/1999/xhtml>, [undef, 'body']);
4050    
4051     for my $attr_name (keys %{ $token->{attributes}}) {
4052     $el->set_attribute_ns (undef, [undef, $attr_name],
4053     $token->{attributes} ->{$attr_name}->{value});
4054     }
4055    
4056 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($el);
4057     push @{$self->{open_elements}}, [$el, 'body'];
4058 wakaba 1.1 }
4059    
4060 wakaba 1.3 $self->{insertion_mode} = 'in body';
4061 wakaba 1.1 $token = $self->_get_next_token;
4062     redo B;
4063     } elsif ($token->{tag_name} eq 'frameset') {
4064    
4065     {
4066     my $el;
4067    
4068     $el = $self->{document}->create_element_ns
4069     (q<http://www.w3.org/1999/xhtml>, [undef, 'frameset']);
4070    
4071     for my $attr_name (keys %{ $token->{attributes}}) {
4072     $el->set_attribute_ns (undef, [undef, $attr_name],
4073     $token->{attributes} ->{$attr_name}->{value});
4074     }
4075    
4076 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($el);
4077     push @{$self->{open_elements}}, [$el, 'frameset'];
4078 wakaba 1.1 }
4079    
4080 wakaba 1.3 $self->{insertion_mode} = 'in frameset';
4081 wakaba 1.1 $token = $self->_get_next_token;
4082     redo B;
4083     } elsif ({
4084     base => 1, link => 1, meta => 1,
4085 wakaba 1.3 script => 1, style => 1, title => 1,
4086 wakaba 1.1 }->{$token->{tag_name}}) {
4087 wakaba 1.3 $self->{parse_error}-> (type => 'after head:'.$token->{tag_name});
4088     $self->{insertion_mode} = 'in head';
4089 wakaba 1.1 ## reprocess
4090     redo B;
4091     } else {
4092     #
4093     }
4094     } else {
4095     #
4096     }
4097    
4098     ## As if <body>
4099    
4100     {
4101     my $el;
4102    
4103     $el = $self->{document}->create_element_ns
4104     (q<http://www.w3.org/1999/xhtml>, [undef, 'body']);
4105    
4106 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($el);
4107     push @{$self->{open_elements}}, [$el, 'body'];
4108 wakaba 1.1 }
4109    
4110 wakaba 1.3 $self->{insertion_mode} = 'in body';
4111 wakaba 1.1 ## reprocess
4112     redo B;
4113 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'in body') {
4114 wakaba 1.1 if ($token->{type} eq 'character') {
4115     ## NOTE: There is a code clone of "character in body".
4116     $reconstruct_active_formatting_elements->($insert_to_current);
4117    
4118 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
4119 wakaba 1.1
4120     $token = $self->_get_next_token;
4121     redo B;
4122     } elsif ($token->{type} eq 'comment') {
4123     ## NOTE: There is a code clone of "comment in body".
4124     my $comment = $self->{document}->create_comment ($token->{data});
4125 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
4126 wakaba 1.1 $token = $self->_get_next_token;
4127     redo B;
4128     } else {
4129     $in_body->($insert_to_current);
4130     redo B;
4131     }
4132 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'in table') {
4133 wakaba 1.1 if ($token->{type} eq 'character') {
4134     ## NOTE: There are "character in table" code clones.
4135     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
4136 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
4137 wakaba 1.1
4138     unless (length $token->{data}) {
4139     $token = $self->_get_next_token;
4140     redo B;
4141     }
4142     }
4143    
4144 wakaba 1.3 $self->{parse_error}-> (type => 'in table:#character');
4145    
4146 wakaba 1.1 ## As if in body, but insert into foster parent element
4147     ## ISSUE: Spec says that "whenever a node would be inserted
4148     ## into the current node" while characters might not be
4149     ## result in a new Text node.
4150     $reconstruct_active_formatting_elements->($insert_to_foster);
4151    
4152     if ({
4153     table => 1, tbody => 1, tfoot => 1,
4154     thead => 1, tr => 1,
4155 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
4156 wakaba 1.1 # MUST
4157     my $foster_parent_element;
4158     my $next_sibling;
4159     my $prev_sibling;
4160 wakaba 1.3 OE: for (reverse 0..$#{$self->{open_elements}}) {
4161     if ($self->{open_elements}->[$_]->[1] eq 'table') {
4162     my $parent = $self->{open_elements}->[$_]->[0]->parent_node;
4163 wakaba 1.1 if (defined $parent and $parent->node_type == 1) {
4164     $foster_parent_element = $parent;
4165 wakaba 1.3 $next_sibling = $self->{open_elements}->[$_]->[0];
4166 wakaba 1.1 $prev_sibling = $next_sibling->previous_sibling;
4167     } else {
4168 wakaba 1.3 $foster_parent_element = $self->{open_elements}->[$_ - 1]->[0];
4169 wakaba 1.1 $prev_sibling = $foster_parent_element->last_child;
4170     }
4171     last OE;
4172     }
4173     } # OE
4174 wakaba 1.3 $foster_parent_element = $self->{open_elements}->[0]->[0] and
4175 wakaba 1.1 $prev_sibling = $foster_parent_element->last_child
4176     unless defined $foster_parent_element;
4177     if (defined $prev_sibling and
4178     $prev_sibling->node_type == 3) {
4179     $prev_sibling->manakai_append_text ($token->{data});
4180     } else {
4181     $foster_parent_element->insert_before
4182     ($self->{document}->create_text_node ($token->{data}),
4183     $next_sibling);
4184     }
4185     } else {
4186 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
4187 wakaba 1.1 }
4188    
4189     $token = $self->_get_next_token;
4190     redo B;
4191     } elsif ($token->{type} eq 'comment') {
4192     my $comment = $self->{document}->create_comment ($token->{data});
4193 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
4194 wakaba 1.1 $token = $self->_get_next_token;
4195     redo B;
4196     } elsif ($token->{type} eq 'start tag') {
4197     if ({
4198     caption => 1,
4199     colgroup => 1,
4200     tbody => 1, tfoot => 1, thead => 1,
4201     }->{$token->{tag_name}}) {
4202     ## Clear back to table context
4203 wakaba 1.3 while ($self->{open_elements}->[-1]->[1] ne 'table' and
4204     $self->{open_elements}->[-1]->[1] ne 'html') {
4205     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4206     pop @{$self->{open_elements}};
4207 wakaba 1.1 }
4208    
4209     push @$active_formatting_elements, ['#marker', '']
4210     if $token->{tag_name} eq 'caption';
4211    
4212    
4213     {
4214     my $el;
4215    
4216     $el = $self->{document}->create_element_ns
4217     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
4218    
4219     for my $attr_name (keys %{ $token->{attributes}}) {
4220     $el->set_attribute_ns (undef, [undef, $attr_name],
4221     $token->{attributes} ->{$attr_name}->{value});
4222     }
4223    
4224 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($el);
4225     push @{$self->{open_elements}}, [$el, $token->{tag_name}];
4226 wakaba 1.1 }
4227    
4228 wakaba 1.3 $self->{insertion_mode} = {
4229 wakaba 1.1 caption => 'in caption',
4230     colgroup => 'in column group',
4231     tbody => 'in table body',
4232     tfoot => 'in table body',
4233     thead => 'in table body',
4234     }->{$token->{tag_name}};
4235     $token = $self->_get_next_token;
4236     redo B;
4237     } elsif ({
4238     col => 1,
4239     td => 1, th => 1, tr => 1,
4240     }->{$token->{tag_name}}) {
4241     ## Clear back to table context
4242 wakaba 1.3 while ($self->{open_elements}->[-1]->[1] ne 'table' and
4243     $self->{open_elements}->[-1]->[1] ne 'html') {
4244     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4245     pop @{$self->{open_elements}};
4246 wakaba 1.1 }
4247    
4248    
4249     {
4250     my $el;
4251    
4252     $el = $self->{document}->create_element_ns
4253     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name} eq 'col' ? 'colgroup' : 'tbody']);
4254    
4255 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($el);
4256     push @{$self->{open_elements}}, [$el, $token->{tag_name} eq 'col' ? 'colgroup' : 'tbody'];
4257 wakaba 1.1 }
4258    
4259 wakaba 1.3 $self->{insertion_mode} = $token->{tag_name} eq 'col'
4260 wakaba 1.1 ? 'in column group' : 'in table body';
4261     ## reprocess
4262     redo B;
4263     } elsif ($token->{tag_name} eq 'table') {
4264     ## NOTE: There are code clones for this "table in table"
4265 wakaba 1.3 $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4266 wakaba 1.1
4267     ## As if </table>
4268     ## have a table element in table scope
4269     my $i;
4270 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4271     my $node = $self->{open_elements}->[$_];
4272 wakaba 1.1 if ($node->[1] eq 'table') {
4273     $i = $_;
4274     last INSCOPE;
4275     } elsif ({
4276     table => 1, html => 1,
4277     }->{$node->[1]}) {
4278     last INSCOPE;
4279     }
4280     } # INSCOPE
4281     unless (defined $i) {
4282 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:table');
4283 wakaba 1.1 ## Ignore tokens </table><table>
4284     $token = $self->_get_next_token;
4285     redo B;
4286     }
4287    
4288     ## generate implied end tags
4289     if ({
4290     dd => 1, dt => 1, li => 1, p => 1,
4291     td => 1, th => 1, tr => 1,
4292 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
4293 wakaba 1.1 unshift @{$self->{token}}, $token; # <table>
4294     $token = {type => 'end tag', tag_name => 'table'};
4295     unshift @{$self->{token}}, $token;
4296     $token = {type => 'end tag',
4297 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
4298 wakaba 1.1 redo B;
4299     }
4300    
4301 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne 'table') {
4302     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4303 wakaba 1.1 }
4304    
4305 wakaba 1.3 splice @{$self->{open_elements}}, $i;
4306 wakaba 1.1
4307 wakaba 1.3 $self->_reset_insertion_mode;
4308 wakaba 1.1
4309     ## reprocess
4310     redo B;
4311     } else {
4312     #
4313     }
4314     } elsif ($token->{type} eq 'end tag') {
4315     if ($token->{tag_name} eq 'table') {
4316     ## have a table element in table scope
4317     my $i;
4318 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4319     my $node = $self->{open_elements}->[$_];
4320 wakaba 1.1 if ($node->[1] eq $token->{tag_name}) {
4321     $i = $_;
4322     last INSCOPE;
4323     } elsif ({
4324     table => 1, html => 1,
4325     }->{$node->[1]}) {
4326     last INSCOPE;
4327     }
4328     } # INSCOPE
4329     unless (defined $i) {
4330 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
4331 wakaba 1.1 ## Ignore the token
4332     $token = $self->_get_next_token;
4333     redo B;
4334     }
4335    
4336     ## generate implied end tags
4337     if ({
4338     dd => 1, dt => 1, li => 1, p => 1,
4339     td => 1, th => 1, tr => 1,
4340 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
4341 wakaba 1.1 unshift @{$self->{token}}, $token;
4342     $token = {type => 'end tag',
4343 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
4344 wakaba 1.1 redo B;
4345     }
4346    
4347 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne 'table') {
4348     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4349 wakaba 1.1 }
4350    
4351 wakaba 1.3 splice @{$self->{open_elements}}, $i;
4352 wakaba 1.1
4353 wakaba 1.3 $self->_reset_insertion_mode;
4354 wakaba 1.1
4355     $token = $self->_get_next_token;
4356     redo B;
4357     } elsif ({
4358     body => 1, caption => 1, col => 1, colgroup => 1,
4359     html => 1, tbody => 1, td => 1, tfoot => 1, th => 1,
4360     thead => 1, tr => 1,
4361     }->{$token->{tag_name}}) {
4362 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
4363 wakaba 1.1 ## Ignore the token
4364     $token = $self->_get_next_token;
4365     redo B;
4366     } else {
4367     #
4368     }
4369     } else {
4370     #
4371     }
4372    
4373 wakaba 1.3 $self->{parse_error}-> (type => 'in table:'.$token->{tag_name});
4374 wakaba 1.1 $in_body->($insert_to_foster);
4375     redo B;
4376 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'in caption') {
4377 wakaba 1.1 if ($token->{type} eq 'character') {
4378     ## NOTE: This is a code clone of "character in body".
4379     $reconstruct_active_formatting_elements->($insert_to_current);
4380    
4381 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
4382 wakaba 1.1
4383     $token = $self->_get_next_token;
4384     redo B;
4385     } elsif ($token->{type} eq 'comment') {
4386     ## NOTE: This is a code clone of "comment in body".
4387     my $comment = $self->{document}->create_comment ($token->{data});
4388 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
4389 wakaba 1.1 $token = $self->_get_next_token;
4390     redo B;
4391     } elsif ($token->{type} eq 'start tag') {
4392     if ({
4393     caption => 1, col => 1, colgroup => 1, tbody => 1,
4394     td => 1, tfoot => 1, th => 1, thead => 1, tr => 1,
4395     }->{$token->{tag_name}}) {
4396 wakaba 1.3 $self->{parse_error}-> (type => 'not closed:caption');
4397 wakaba 1.1
4398     ## As if </caption>
4399     ## have a table element in table scope
4400     my $i;
4401 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4402     my $node = $self->{open_elements}->[$_];
4403 wakaba 1.1 if ($node->[1] eq 'caption') {
4404     $i = $_;
4405     last INSCOPE;
4406     } elsif ({
4407     table => 1, html => 1,
4408     }->{$node->[1]}) {
4409     last INSCOPE;
4410     }
4411     } # INSCOPE
4412     unless (defined $i) {
4413 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:caption');
4414 wakaba 1.1 ## Ignore the token
4415     $token = $self->_get_next_token;
4416     redo B;
4417     }
4418    
4419     ## generate implied end tags
4420     if ({
4421     dd => 1, dt => 1, li => 1, p => 1,
4422     td => 1, th => 1, tr => 1,
4423 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
4424 wakaba 1.1 unshift @{$self->{token}}, $token; # <?>
4425     $token = {type => 'end tag', tag_name => 'caption'};
4426     unshift @{$self->{token}}, $token;
4427     $token = {type => 'end tag',
4428 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
4429 wakaba 1.1 redo B;
4430     }
4431    
4432 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne 'caption') {
4433     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4434 wakaba 1.1 }
4435    
4436 wakaba 1.3 splice @{$self->{open_elements}}, $i;
4437 wakaba 1.1
4438     $clear_up_to_marker->();
4439    
4440 wakaba 1.3 $self->{insertion_mode} = 'in table';
4441 wakaba 1.1
4442     ## reprocess
4443     redo B;
4444     } else {
4445     #
4446     }
4447     } elsif ($token->{type} eq 'end tag') {
4448     if ($token->{tag_name} eq 'caption') {
4449     ## have a table element in table scope
4450     my $i;
4451 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4452     my $node = $self->{open_elements}->[$_];
4453 wakaba 1.1 if ($node->[1] eq $token->{tag_name}) {
4454     $i = $_;
4455     last INSCOPE;
4456     } elsif ({
4457     table => 1, html => 1,
4458     }->{$node->[1]}) {
4459     last INSCOPE;
4460     }
4461     } # INSCOPE
4462     unless (defined $i) {
4463 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
4464 wakaba 1.1 ## Ignore the token
4465     $token = $self->_get_next_token;
4466     redo B;
4467     }
4468    
4469     ## generate implied end tags
4470     if ({
4471     dd => 1, dt => 1, li => 1, p => 1,
4472     td => 1, th => 1, tr => 1,
4473 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
4474 wakaba 1.1 unshift @{$self->{token}}, $token;
4475     $token = {type => 'end tag',
4476 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
4477 wakaba 1.1 redo B;
4478     }
4479    
4480 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne 'caption') {
4481     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4482 wakaba 1.1 }
4483    
4484 wakaba 1.3 splice @{$self->{open_elements}}, $i;
4485 wakaba 1.1
4486     $clear_up_to_marker->();
4487    
4488 wakaba 1.3 $self->{insertion_mode} = 'in table';
4489 wakaba 1.1
4490     $token = $self->_get_next_token;
4491     redo B;
4492     } elsif ($token->{tag_name} eq 'table') {
4493 wakaba 1.3 $self->{parse_error}-> (type => 'not closed:caption');
4494 wakaba 1.1
4495     ## As if </caption>
4496     ## have a table element in table scope
4497     my $i;
4498 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4499     my $node = $self->{open_elements}->[$_];
4500 wakaba 1.1 if ($node->[1] eq 'caption') {
4501     $i = $_;
4502     last INSCOPE;
4503     } elsif ({
4504     table => 1, html => 1,
4505     }->{$node->[1]}) {
4506     last INSCOPE;
4507     }
4508     } # INSCOPE
4509     unless (defined $i) {
4510 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:caption');
4511 wakaba 1.1 ## Ignore the token
4512     $token = $self->_get_next_token;
4513     redo B;
4514     }
4515    
4516     ## generate implied end tags
4517     if ({
4518     dd => 1, dt => 1, li => 1, p => 1,
4519     td => 1, th => 1, tr => 1,
4520 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
4521 wakaba 1.1 unshift @{$self->{token}}, $token; # </table>
4522     $token = {type => 'end tag', tag_name => 'caption'};
4523     unshift @{$self->{token}}, $token;
4524     $token = {type => 'end tag',
4525 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
4526 wakaba 1.1 redo B;
4527     }
4528    
4529 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne 'caption') {
4530     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4531 wakaba 1.1 }
4532    
4533 wakaba 1.3 splice @{$self->{open_elements}}, $i;
4534 wakaba 1.1
4535     $clear_up_to_marker->();
4536    
4537 wakaba 1.3 $self->{insertion_mode} = 'in table';
4538 wakaba 1.1
4539     ## reprocess
4540     redo B;
4541     } elsif ({
4542     body => 1, col => 1, colgroup => 1,
4543     html => 1, tbody => 1, td => 1, tfoot => 1,
4544     th => 1, thead => 1, tr => 1,
4545     }->{$token->{tag_name}}) {
4546 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
4547 wakaba 1.1 ## Ignore the token
4548     redo B;
4549     } else {
4550     #
4551     }
4552     } else {
4553     #
4554     }
4555    
4556     $in_body->($insert_to_current);
4557     redo B;
4558 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'in column group') {
4559 wakaba 1.1 if ($token->{type} eq 'character') {
4560     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
4561 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
4562 wakaba 1.1 unless (length $token->{data}) {
4563     $token = $self->_get_next_token;
4564     redo B;
4565     }
4566     }
4567    
4568     #
4569     } elsif ($token->{type} eq 'comment') {
4570     my $comment = $self->{document}->create_comment ($token->{data});
4571 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
4572 wakaba 1.1 $token = $self->_get_next_token;
4573     redo B;
4574     } elsif ($token->{type} eq 'start tag') {
4575     if ($token->{tag_name} eq 'col') {
4576    
4577     {
4578     my $el;
4579    
4580     $el = $self->{document}->create_element_ns
4581     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
4582    
4583     for my $attr_name (keys %{ $token->{attributes}}) {
4584     $el->set_attribute_ns (undef, [undef, $attr_name],
4585     $token->{attributes} ->{$attr_name}->{value});
4586     }
4587    
4588 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($el);
4589     push @{$self->{open_elements}}, [$el, $token->{tag_name}];
4590 wakaba 1.1 }
4591    
4592 wakaba 1.3 pop @{$self->{open_elements}};
4593 wakaba 1.1 $token = $self->_get_next_token;
4594     redo B;
4595     } else {
4596     #
4597     }
4598     } elsif ($token->{type} eq 'end tag') {
4599     if ($token->{tag_name} eq 'colgroup') {
4600 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] eq 'html') {
4601     $self->{parse_error}-> (type => 'unmatched end tag:colgroup');
4602 wakaba 1.1 ## Ignore the token
4603     $token = $self->_get_next_token;
4604     redo B;
4605     } else {
4606 wakaba 1.3 pop @{$self->{open_elements}}; # colgroup
4607     $self->{insertion_mode} = 'in table';
4608 wakaba 1.1 $token = $self->_get_next_token;
4609     redo B;
4610     }
4611     } elsif ($token->{tag_name} eq 'col') {
4612 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:col');
4613 wakaba 1.1 ## Ignore the token
4614     $token = $self->_get_next_token;
4615     redo B;
4616     } else {
4617     #
4618     }
4619     } else {
4620     #
4621     }
4622    
4623     ## As if </colgroup>
4624 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] eq 'html') {
4625     $self->{parse_error}-> (type => 'unmatched end tag:colgroup');
4626 wakaba 1.1 ## Ignore the token
4627     $token = $self->_get_next_token;
4628     redo B;
4629     } else {
4630 wakaba 1.3 pop @{$self->{open_elements}}; # colgroup
4631     $self->{insertion_mode} = 'in table';
4632 wakaba 1.1 ## reprocess
4633     redo B;
4634     }
4635 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'in table body') {
4636 wakaba 1.1 if ($token->{type} eq 'character') {
4637     ## NOTE: This is a "character in table" code clone.
4638     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
4639 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
4640 wakaba 1.1
4641     unless (length $token->{data}) {
4642     $token = $self->_get_next_token;
4643     redo B;
4644     }
4645     }
4646    
4647 wakaba 1.3 $self->{parse_error}-> (type => 'in table:#character');
4648    
4649 wakaba 1.1 ## As if in body, but insert into foster parent element
4650     ## ISSUE: Spec says that "whenever a node would be inserted
4651     ## into the current node" while characters might not be
4652     ## result in a new Text node.
4653     $reconstruct_active_formatting_elements->($insert_to_foster);
4654    
4655     if ({
4656     table => 1, tbody => 1, tfoot => 1,
4657     thead => 1, tr => 1,
4658 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
4659 wakaba 1.1 # MUST
4660     my $foster_parent_element;
4661     my $next_sibling;
4662     my $prev_sibling;
4663 wakaba 1.3 OE: for (reverse 0..$#{$self->{open_elements}}) {
4664     if ($self->{open_elements}->[$_]->[1] eq 'table') {
4665     my $parent = $self->{open_elements}->[$_]->[0]->parent_node;
4666 wakaba 1.1 if (defined $parent and $parent->node_type == 1) {
4667     $foster_parent_element = $parent;
4668 wakaba 1.3 $next_sibling = $self->{open_elements}->[$_]->[0];
4669 wakaba 1.1 $prev_sibling = $next_sibling->previous_sibling;
4670     } else {
4671 wakaba 1.3 $foster_parent_element = $self->{open_elements}->[$_ - 1]->[0];
4672 wakaba 1.1 $prev_sibling = $foster_parent_element->last_child;
4673     }
4674     last OE;
4675     }
4676     } # OE
4677 wakaba 1.3 $foster_parent_element = $self->{open_elements}->[0]->[0] and
4678 wakaba 1.1 $prev_sibling = $foster_parent_element->last_child
4679     unless defined $foster_parent_element;
4680     if (defined $prev_sibling and
4681     $prev_sibling->node_type == 3) {
4682     $prev_sibling->manakai_append_text ($token->{data});
4683     } else {
4684     $foster_parent_element->insert_before
4685     ($self->{document}->create_text_node ($token->{data}),
4686     $next_sibling);
4687     }
4688     } else {
4689 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
4690 wakaba 1.1 }
4691    
4692     $token = $self->_get_next_token;
4693     redo B;
4694     } elsif ($token->{type} eq 'comment') {
4695     ## Copied from 'in table'
4696     my $comment = $self->{document}->create_comment ($token->{data});
4697 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
4698 wakaba 1.1 $token = $self->_get_next_token;
4699     redo B;
4700     } elsif ($token->{type} eq 'start tag') {
4701     if ({
4702     tr => 1,
4703     th => 1, td => 1,
4704     }->{$token->{tag_name}}) {
4705 wakaba 1.3 unless ($token->{tag_name} eq 'tr') {
4706     $self->{parse_error}-> (type => 'missing start tag:tr');
4707     }
4708    
4709 wakaba 1.1 ## Clear back to table body context
4710     while (not {
4711     tbody => 1, tfoot => 1, thead => 1, html => 1,
4712 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
4713     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4714     pop @{$self->{open_elements}};
4715 wakaba 1.1 }
4716    
4717 wakaba 1.3 $self->{insertion_mode} = 'in row';
4718 wakaba 1.1 if ($token->{tag_name} eq 'tr') {
4719    
4720     {
4721     my $el;
4722    
4723     $el = $self->{document}->create_element_ns
4724     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
4725    
4726     for my $attr_name (keys %{ $token->{attributes}}) {
4727     $el->set_attribute_ns (undef, [undef, $attr_name],
4728     $token->{attributes} ->{$attr_name}->{value});
4729     }
4730    
4731 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($el);
4732     push @{$self->{open_elements}}, [$el, $token->{tag_name}];
4733 wakaba 1.1 }
4734    
4735     $token = $self->_get_next_token;
4736     } else {
4737    
4738     {
4739     my $el;
4740    
4741     $el = $self->{document}->create_element_ns
4742     (q<http://www.w3.org/1999/xhtml>, [undef, 'tr']);
4743    
4744 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($el);
4745     push @{$self->{open_elements}}, [$el, 'tr'];
4746 wakaba 1.1 }
4747    
4748     ## reprocess
4749     }
4750     redo B;
4751     } elsif ({
4752     caption => 1, col => 1, colgroup => 1,
4753     tbody => 1, tfoot => 1, thead => 1,
4754     }->{$token->{tag_name}}) {
4755     ## have an element in table scope
4756     my $i;
4757 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4758     my $node = $self->{open_elements}->[$_];
4759 wakaba 1.1 if ({
4760     tbody => 1, thead => 1, tfoot => 1,
4761     }->{$node->[1]}) {
4762     $i = $_;
4763     last INSCOPE;
4764     } elsif ({
4765     table => 1, html => 1,
4766     }->{$node->[1]}) {
4767     last INSCOPE;
4768     }
4769     } # INSCOPE
4770     unless (defined $i) {
4771 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
4772 wakaba 1.1 ## Ignore the token
4773     $token = $self->_get_next_token;
4774     redo B;
4775     }
4776    
4777     ## Clear back to table body context
4778     while (not {
4779     tbody => 1, tfoot => 1, thead => 1, html => 1,
4780 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
4781     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4782     pop @{$self->{open_elements}};
4783 wakaba 1.1 }
4784    
4785     ## As if <{current node}>
4786     ## have an element in table scope
4787     ## true by definition
4788    
4789     ## Clear back to table body context
4790     ## nop by definition
4791    
4792 wakaba 1.3 pop @{$self->{open_elements}};
4793     $self->{insertion_mode} = 'in table';
4794 wakaba 1.1 ## reprocess
4795     redo B;
4796     } elsif ($token->{tag_name} eq 'table') {
4797     ## NOTE: This is a code clone of "table in table"
4798 wakaba 1.3 $self->{parse_error}-> (type => 'not closed:table');
4799 wakaba 1.1
4800     ## As if </table>
4801     ## have a table element in table scope
4802     my $i;
4803 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4804     my $node = $self->{open_elements}->[$_];
4805 wakaba 1.1 if ($node->[1] eq 'table') {
4806     $i = $_;
4807     last INSCOPE;
4808     } elsif ({
4809     table => 1, html => 1,
4810     }->{$node->[1]}) {
4811     last INSCOPE;
4812     }
4813     } # INSCOPE
4814     unless (defined $i) {
4815 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:table');
4816 wakaba 1.1 ## Ignore tokens </table><table>
4817     $token = $self->_get_next_token;
4818     redo B;
4819     }
4820    
4821     ## generate implied end tags
4822     if ({
4823     dd => 1, dt => 1, li => 1, p => 1,
4824     td => 1, th => 1, tr => 1,
4825 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
4826 wakaba 1.1 unshift @{$self->{token}}, $token; # <table>
4827     $token = {type => 'end tag', tag_name => 'table'};
4828     unshift @{$self->{token}}, $token;
4829     $token = {type => 'end tag',
4830 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
4831 wakaba 1.1 redo B;
4832     }
4833    
4834 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne 'table') {
4835     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4836 wakaba 1.1 }
4837    
4838 wakaba 1.3 splice @{$self->{open_elements}}, $i;
4839 wakaba 1.1
4840 wakaba 1.3 $self->_reset_insertion_mode;
4841 wakaba 1.1
4842     ## reprocess
4843     redo B;
4844     } else {
4845     #
4846     }
4847     } elsif ($token->{type} eq 'end tag') {
4848     if ({
4849     tbody => 1, tfoot => 1, thead => 1,
4850     }->{$token->{tag_name}}) {
4851     ## have an element in table scope
4852     my $i;
4853 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4854     my $node = $self->{open_elements}->[$_];
4855 wakaba 1.1 if ($node->[1] eq $token->{tag_name}) {
4856     $i = $_;
4857     last INSCOPE;
4858     } elsif ({
4859     table => 1, html => 1,
4860     }->{$node->[1]}) {
4861     last INSCOPE;
4862     }
4863     } # INSCOPE
4864     unless (defined $i) {
4865 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
4866 wakaba 1.1 ## Ignore the token
4867     $token = $self->_get_next_token;
4868     redo B;
4869     }
4870    
4871     ## Clear back to table body context
4872     while (not {
4873     tbody => 1, tfoot => 1, thead => 1, html => 1,
4874 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
4875     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4876     pop @{$self->{open_elements}};
4877 wakaba 1.1 }
4878    
4879 wakaba 1.3 pop @{$self->{open_elements}};
4880     $self->{insertion_mode} = 'in table';
4881 wakaba 1.1 $token = $self->_get_next_token;
4882     redo B;
4883     } elsif ($token->{tag_name} eq 'table') {
4884     ## have an element in table scope
4885     my $i;
4886 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4887     my $node = $self->{open_elements}->[$_];
4888 wakaba 1.1 if ({
4889     tbody => 1, thead => 1, tfoot => 1,
4890     }->{$node->[1]}) {
4891     $i = $_;
4892     last INSCOPE;
4893     } elsif ({
4894     table => 1, html => 1,
4895     }->{$node->[1]}) {
4896     last INSCOPE;
4897     }
4898     } # INSCOPE
4899     unless (defined $i) {
4900 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
4901 wakaba 1.1 ## Ignore the token
4902     $token = $self->_get_next_token;
4903     redo B;
4904     }
4905    
4906     ## Clear back to table body context
4907     while (not {
4908     tbody => 1, tfoot => 1, thead => 1, html => 1,
4909 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
4910     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
4911     pop @{$self->{open_elements}};
4912 wakaba 1.1 }
4913    
4914     ## As if <{current node}>
4915     ## have an element in table scope
4916     ## true by definition
4917    
4918     ## Clear back to table body context
4919     ## nop by definition
4920    
4921 wakaba 1.3 pop @{$self->{open_elements}};
4922     $self->{insertion_mode} = 'in table';
4923 wakaba 1.1 ## reprocess
4924     redo B;
4925     } elsif ({
4926     body => 1, caption => 1, col => 1, colgroup => 1,
4927     html => 1, td => 1, th => 1, tr => 1,
4928     }->{$token->{tag_name}}) {
4929 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
4930 wakaba 1.1 ## Ignore the token
4931     $token = $self->_get_next_token;
4932     redo B;
4933     } else {
4934     #
4935     }
4936     } else {
4937     #
4938     }
4939    
4940     ## As if in table
4941 wakaba 1.3 $self->{parse_error}-> (type => 'in table:'.$token->{tag_name});
4942 wakaba 1.1 $in_body->($insert_to_foster);
4943     redo B;
4944 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'in row') {
4945 wakaba 1.1 if ($token->{type} eq 'character') {
4946     ## NOTE: This is a "character in table" code clone.
4947     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
4948 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
4949 wakaba 1.1
4950     unless (length $token->{data}) {
4951     $token = $self->_get_next_token;
4952     redo B;
4953     }
4954     }
4955    
4956 wakaba 1.3 $self->{parse_error}-> (type => 'in table:#character');
4957    
4958 wakaba 1.1 ## As if in body, but insert into foster parent element
4959     ## ISSUE: Spec says that "whenever a node would be inserted
4960     ## into the current node" while characters might not be
4961     ## result in a new Text node.
4962     $reconstruct_active_formatting_elements->($insert_to_foster);
4963    
4964     if ({
4965     table => 1, tbody => 1, tfoot => 1,
4966     thead => 1, tr => 1,
4967 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
4968 wakaba 1.1 # MUST
4969     my $foster_parent_element;
4970     my $next_sibling;
4971     my $prev_sibling;
4972 wakaba 1.3 OE: for (reverse 0..$#{$self->{open_elements}}) {
4973     if ($self->{open_elements}->[$_]->[1] eq 'table') {
4974     my $parent = $self->{open_elements}->[$_]->[0]->parent_node;
4975 wakaba 1.1 if (defined $parent and $parent->node_type == 1) {
4976     $foster_parent_element = $parent;
4977 wakaba 1.3 $next_sibling = $self->{open_elements}->[$_]->[0];
4978 wakaba 1.1 $prev_sibling = $next_sibling->previous_sibling;
4979     } else {
4980 wakaba 1.3 $foster_parent_element = $self->{open_elements}->[$_ - 1]->[0];
4981 wakaba 1.1 $prev_sibling = $foster_parent_element->last_child;
4982     }
4983     last OE;
4984     }
4985     } # OE
4986 wakaba 1.3 $foster_parent_element = $self->{open_elements}->[0]->[0] and
4987 wakaba 1.1 $prev_sibling = $foster_parent_element->last_child
4988     unless defined $foster_parent_element;
4989     if (defined $prev_sibling and
4990     $prev_sibling->node_type == 3) {
4991     $prev_sibling->manakai_append_text ($token->{data});
4992     } else {
4993     $foster_parent_element->insert_before
4994     ($self->{document}->create_text_node ($token->{data}),
4995     $next_sibling);
4996     }
4997     } else {
4998 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
4999 wakaba 1.1 }
5000    
5001     $token = $self->_get_next_token;
5002     redo B;
5003     } elsif ($token->{type} eq 'comment') {
5004     ## Copied from 'in table'
5005     my $comment = $self->{document}->create_comment ($token->{data});
5006 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
5007 wakaba 1.1 $token = $self->_get_next_token;
5008     redo B;
5009     } elsif ($token->{type} eq 'start tag') {
5010     if ($token->{tag_name} eq 'th' or
5011     $token->{tag_name} eq 'td') {
5012     ## Clear back to table row context
5013     while (not {
5014     tr => 1, html => 1,
5015 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
5016     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
5017     pop @{$self->{open_elements}};
5018 wakaba 1.1 }
5019    
5020    
5021     {
5022     my $el;
5023    
5024     $el = $self->{document}->create_element_ns
5025     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
5026    
5027     for my $attr_name (keys %{ $token->{attributes}}) {
5028     $el->set_attribute_ns (undef, [undef, $attr_name],
5029     $token->{attributes} ->{$attr_name}->{value});
5030     }
5031    
5032 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($el);
5033     push @{$self->{open_elements}}, [$el, $token->{tag_name}];
5034 wakaba 1.1 }
5035    
5036 wakaba 1.3 $self->{insertion_mode} = 'in cell';
5037 wakaba 1.1
5038     push @$active_formatting_elements, ['#marker', ''];
5039    
5040     $token = $self->_get_next_token;
5041     redo B;
5042     } elsif ({
5043     caption => 1, col => 1, colgroup => 1,
5044     tbody => 1, tfoot => 1, thead => 1, tr => 1,
5045     }->{$token->{tag_name}}) {
5046     ## As if </tr>
5047     ## have an element in table scope
5048     my $i;
5049 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5050     my $node = $self->{open_elements}->[$_];
5051 wakaba 1.1 if ($node->[1] eq 'tr') {
5052     $i = $_;
5053     last INSCOPE;
5054     } elsif ({
5055     table => 1, html => 1,
5056     }->{$node->[1]}) {
5057     last INSCOPE;
5058     }
5059     } # INSCOPE
5060     unless (defined $i) {
5061 wakaba 1.3 $self->{parse_error}-> (type => 'unmacthed end tag:'.$token->{tag_name});
5062 wakaba 1.1 ## Ignore the token
5063     $token = $self->_get_next_token;
5064     redo B;
5065     }
5066    
5067     ## Clear back to table row context
5068     while (not {
5069     tr => 1, html => 1,
5070 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
5071     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
5072     pop @{$self->{open_elements}};
5073 wakaba 1.1 }
5074    
5075 wakaba 1.3 pop @{$self->{open_elements}}; # tr
5076     $self->{insertion_mode} = 'in table body';
5077 wakaba 1.1 ## reprocess
5078     redo B;
5079     } elsif ($token->{tag_name} eq 'table') {
5080     ## NOTE: This is a code clone of "table in table"
5081 wakaba 1.3 $self->{parse_error}-> (type => 'not closed:table');
5082 wakaba 1.1
5083     ## As if </table>
5084     ## have a table element in table scope
5085     my $i;
5086 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5087     my $node = $self->{open_elements}->[$_];
5088 wakaba 1.1 if ($node->[1] eq 'table') {
5089     $i = $_;
5090     last INSCOPE;
5091     } elsif ({
5092     table => 1, html => 1,
5093     }->{$node->[1]}) {
5094     last INSCOPE;
5095     }
5096     } # INSCOPE
5097     unless (defined $i) {
5098 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:table');
5099 wakaba 1.1 ## Ignore tokens </table><table>
5100     $token = $self->_get_next_token;
5101     redo B;
5102     }
5103    
5104     ## generate implied end tags
5105     if ({
5106     dd => 1, dt => 1, li => 1, p => 1,
5107     td => 1, th => 1, tr => 1,
5108 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
5109 wakaba 1.1 unshift @{$self->{token}}, $token; # <table>
5110     $token = {type => 'end tag', tag_name => 'table'};
5111     unshift @{$self->{token}}, $token;
5112     $token = {type => 'end tag',
5113 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
5114 wakaba 1.1 redo B;
5115     }
5116    
5117 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne 'table') {
5118     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
5119 wakaba 1.1 }
5120    
5121 wakaba 1.3 splice @{$self->{open_elements}}, $i;
5122 wakaba 1.1
5123 wakaba 1.3 $self->_reset_insertion_mode;
5124 wakaba 1.1
5125     ## reprocess
5126     redo B;
5127     } else {
5128     #
5129     }
5130     } elsif ($token->{type} eq 'end tag') {
5131     if ($token->{tag_name} eq 'tr') {
5132     ## have an element in table scope
5133     my $i;
5134 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5135     my $node = $self->{open_elements}->[$_];
5136 wakaba 1.1 if ($node->[1] eq $token->{tag_name}) {
5137     $i = $_;
5138     last INSCOPE;
5139     } elsif ({
5140     table => 1, html => 1,
5141     }->{$node->[1]}) {
5142     last INSCOPE;
5143     }
5144     } # INSCOPE
5145     unless (defined $i) {
5146 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
5147 wakaba 1.1 ## Ignore the token
5148     $token = $self->_get_next_token;
5149     redo B;
5150     }
5151    
5152     ## Clear back to table row context
5153     while (not {
5154     tr => 1, html => 1,
5155 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
5156     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
5157     pop @{$self->{open_elements}};
5158 wakaba 1.1 }
5159    
5160 wakaba 1.3 pop @{$self->{open_elements}}; # tr
5161     $self->{insertion_mode} = 'in table body';
5162 wakaba 1.1 $token = $self->_get_next_token;
5163     redo B;
5164     } elsif ($token->{tag_name} eq 'table') {
5165     ## As if </tr>
5166     ## have an element in table scope
5167     my $i;
5168 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5169     my $node = $self->{open_elements}->[$_];
5170 wakaba 1.1 if ($node->[1] eq 'tr') {
5171     $i = $_;
5172     last INSCOPE;
5173     } elsif ({
5174     table => 1, html => 1,
5175     }->{$node->[1]}) {
5176     last INSCOPE;
5177     }
5178     } # INSCOPE
5179     unless (defined $i) {
5180 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{type});
5181 wakaba 1.1 ## Ignore the token
5182     $token = $self->_get_next_token;
5183     redo B;
5184     }
5185    
5186     ## Clear back to table row context
5187     while (not {
5188     tr => 1, html => 1,
5189 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
5190     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
5191     pop @{$self->{open_elements}};
5192 wakaba 1.1 }
5193    
5194 wakaba 1.3 pop @{$self->{open_elements}}; # tr
5195     $self->{insertion_mode} = 'in table body';
5196 wakaba 1.1 ## reprocess
5197     redo B;
5198     } elsif ({
5199     tbody => 1, tfoot => 1, thead => 1,
5200     }->{$token->{tag_name}}) {
5201     ## have an element in table scope
5202     my $i;
5203 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5204     my $node = $self->{open_elements}->[$_];
5205 wakaba 1.1 if ($node->[1] eq $token->{tag_name}) {
5206     $i = $_;
5207     last INSCOPE;
5208     } elsif ({
5209     table => 1, html => 1,
5210     }->{$node->[1]}) {
5211     last INSCOPE;
5212     }
5213     } # INSCOPE
5214     unless (defined $i) {
5215 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
5216 wakaba 1.1 ## Ignore the token
5217     $token = $self->_get_next_token;
5218     redo B;
5219     }
5220    
5221     ## As if </tr>
5222     ## have an element in table scope
5223     my $i;
5224 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5225     my $node = $self->{open_elements}->[$_];
5226 wakaba 1.1 if ($node->[1] eq 'tr') {
5227     $i = $_;
5228     last INSCOPE;
5229     } elsif ({
5230     table => 1, html => 1,
5231     }->{$node->[1]}) {
5232     last INSCOPE;
5233     }
5234     } # INSCOPE
5235     unless (defined $i) {
5236 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:tr');
5237 wakaba 1.1 ## Ignore the token
5238     $token = $self->_get_next_token;
5239     redo B;
5240     }
5241    
5242     ## Clear back to table row context
5243     while (not {
5244     tr => 1, html => 1,
5245 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
5246     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
5247     pop @{$self->{open_elements}};
5248 wakaba 1.1 }
5249    
5250 wakaba 1.3 pop @{$self->{open_elements}}; # tr
5251     $self->{insertion_mode} = 'in table body';
5252 wakaba 1.1 ## reprocess
5253     redo B;
5254     } elsif ({
5255     body => 1, caption => 1, col => 1,
5256     colgroup => 1, html => 1, td => 1, th => 1,
5257     }->{$token->{tag_name}}) {
5258 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
5259 wakaba 1.1 ## Ignore the token
5260     $token = $self->_get_next_token;
5261     redo B;
5262     } else {
5263     #
5264     }
5265     } else {
5266     #
5267     }
5268    
5269     ## As if in table
5270 wakaba 1.3 $self->{parse_error}-> (type => 'in table:'.$token->{tag_name});
5271 wakaba 1.1 $in_body->($insert_to_foster);
5272     redo B;
5273 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'in cell') {
5274 wakaba 1.1 if ($token->{type} eq 'character') {
5275     ## NOTE: This is a code clone of "character in body".
5276     $reconstruct_active_formatting_elements->($insert_to_current);
5277    
5278 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
5279 wakaba 1.1
5280     $token = $self->_get_next_token;
5281     redo B;
5282     } elsif ($token->{type} eq 'comment') {
5283     ## NOTE: This is a code clone of "comment in body".
5284     my $comment = $self->{document}->create_comment ($token->{data});
5285 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
5286 wakaba 1.1 $token = $self->_get_next_token;
5287     redo B;
5288     } elsif ($token->{type} eq 'start tag') {
5289     if ({
5290     caption => 1, col => 1, colgroup => 1,
5291     tbody => 1, td => 1, tfoot => 1, th => 1,
5292     thead => 1, tr => 1,
5293     }->{$token->{tag_name}}) {
5294     ## have an element in table scope
5295     my $tn;
5296 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5297     my $node = $self->{open_elements}->[$_];
5298 wakaba 1.1 if ($node->[1] eq 'td' or $node->[1] eq 'th') {
5299     $tn = $node->[1];
5300     last INSCOPE;
5301     } elsif ({
5302     table => 1, html => 1,
5303     }->{$node->[1]}) {
5304     last INSCOPE;
5305     }
5306     } # INSCOPE
5307     unless (defined $tn) {
5308 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
5309 wakaba 1.1 ## Ignore the token
5310     $token = $self->_get_next_token;
5311     redo B;
5312     }
5313    
5314     ## Close the cell
5315     unshift @{$self->{token}}, $token; # <?>
5316     $token = {type => 'end tag', tag_name => $tn};
5317     redo B;
5318     } else {
5319     #
5320     }
5321     } elsif ($token->{type} eq 'end tag') {
5322     if ($token->{tag_name} eq 'td' or $token->{tag_name} eq 'th') {
5323     ## have an element in table scope
5324     my $i;
5325 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5326     my $node = $self->{open_elements}->[$_];
5327 wakaba 1.1 if ($node->[1] eq $token->{tag_name}) {
5328     $i = $_;
5329     last INSCOPE;
5330     } elsif ({
5331     table => 1, html => 1,
5332     }->{$node->[1]}) {
5333     last INSCOPE;
5334     }
5335     } # INSCOPE
5336     unless (defined $i) {
5337 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
5338 wakaba 1.1 ## Ignore the token
5339     $token = $self->_get_next_token;
5340     redo B;
5341     }
5342    
5343     ## generate implied end tags
5344     if ({
5345     dd => 1, dt => 1, li => 1, p => 1,
5346     td => ($token->{tag_name} eq 'th'),
5347     th => ($token->{tag_name} eq 'td'),
5348     tr => 1,
5349 wakaba 1.3 }->{$self->{open_elements}->[-1]->[1]}) {
5350 wakaba 1.1 unshift @{$self->{token}}, $token;
5351     $token = {type => 'end tag',
5352 wakaba 1.3 tag_name => $self->{open_elements}->[-1]->[1]}; # MUST
5353 wakaba 1.1 redo B;
5354     }
5355    
5356 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne $token->{tag_name}) {
5357     $self->{parse_error}-> (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);
5358 wakaba 1.1 }
5359    
5360 wakaba 1.3 splice @{$self->{open_elements}}, $i;
5361 wakaba 1.1
5362     $clear_up_to_marker->();
5363    
5364 wakaba 1.3 $self->{insertion_mode} = 'in row';
5365 wakaba 1.1
5366     $token = $self->_get_next_token;
5367     redo B;
5368     } elsif ({
5369     body => 1, caption => 1, col => 1,
5370     colgroup => 1, html => 1,
5371     }->{$token->{tag_name}}) {
5372 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
5373 wakaba 1.1 ## Ignore the token
5374     $token = $self->_get_next_token;
5375     redo B;
5376     } elsif ({
5377     table => 1, tbody => 1, tfoot => 1,
5378     thead => 1, tr => 1,
5379     }->{$token->{tag_name}}) {
5380     ## have an element in table scope
5381     my $i;
5382     my $tn;
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 ($node->[1] eq 'td' or $node->[1] eq 'th') {
5389     $tn = $node->[1];
5390     ## NOTE: There is exactly one |td| or |th| element
5391     ## in scope in the stack of open elements by definition.
5392     } elsif ({
5393     table => 1, html => 1,
5394     }->{$node->[1]}) {
5395     last INSCOPE;
5396     }
5397     } # INSCOPE
5398     unless (defined $i) {
5399 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
5400 wakaba 1.1 ## Ignore the token
5401     $token = $self->_get_next_token;
5402     redo B;
5403     }
5404    
5405     ## Close the cell
5406     unshift @{$self->{token}}, $token; # </?>
5407     $token = {type => 'end tag', tag_name => $tn};
5408     redo B;
5409     } else {
5410     #
5411     }
5412     } else {
5413     #
5414     }
5415    
5416     $in_body->($insert_to_current);
5417     redo B;
5418 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'in select') {
5419 wakaba 1.1 if ($token->{type} eq 'character') {
5420 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
5421 wakaba 1.1 $token = $self->_get_next_token;
5422     redo B;
5423     } elsif ($token->{type} eq 'comment') {
5424     my $comment = $self->{document}->create_comment ($token->{data});
5425 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
5426 wakaba 1.1 $token = $self->_get_next_token;
5427     redo B;
5428     } elsif ($token->{type} eq 'start tag') {
5429     if ($token->{tag_name} eq 'option') {
5430 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] eq 'option') {
5431 wakaba 1.1 ## As if </option>
5432 wakaba 1.3 pop @{$self->{open_elements}};
5433 wakaba 1.1 }
5434    
5435    
5436     {
5437     my $el;
5438    
5439     $el = $self->{document}->create_element_ns
5440     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
5441    
5442     for my $attr_name (keys %{ $token->{attributes}}) {
5443     $el->set_attribute_ns (undef, [undef, $attr_name],
5444     $token->{attributes} ->{$attr_name}->{value});
5445     }
5446    
5447 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($el);
5448     push @{$self->{open_elements}}, [$el, $token->{tag_name}];
5449 wakaba 1.1 }
5450    
5451     $token = $self->_get_next_token;
5452     redo B;
5453     } elsif ($token->{tag_name} eq 'optgroup') {
5454 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] eq 'option') {
5455 wakaba 1.1 ## As if </option>
5456 wakaba 1.3 pop @{$self->{open_elements}};
5457 wakaba 1.1 }
5458    
5459 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] eq 'optgroup') {
5460 wakaba 1.1 ## As if </optgroup>
5461 wakaba 1.3 pop @{$self->{open_elements}};
5462 wakaba 1.1 }
5463    
5464    
5465     {
5466     my $el;
5467    
5468     $el = $self->{document}->create_element_ns
5469     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
5470    
5471     for my $attr_name (keys %{ $token->{attributes}}) {
5472     $el->set_attribute_ns (undef, [undef, $attr_name],
5473     $token->{attributes} ->{$attr_name}->{value});
5474     }
5475    
5476 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($el);
5477     push @{$self->{open_elements}}, [$el, $token->{tag_name}];
5478 wakaba 1.1 }
5479    
5480     $token = $self->_get_next_token;
5481     redo B;
5482     } elsif ($token->{tag_name} eq 'select') {
5483 wakaba 1.3 $self->{parse_error}-> (type => 'not closed:select');
5484 wakaba 1.1 ## As if </select> instead
5485     ## have an element in table scope
5486     my $i;
5487 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5488     my $node = $self->{open_elements}->[$_];
5489 wakaba 1.1 if ($node->[1] eq $token->{tag_name}) {
5490     $i = $_;
5491     last INSCOPE;
5492     } elsif ({
5493     table => 1, html => 1,
5494     }->{$node->[1]}) {
5495     last INSCOPE;
5496     }
5497     } # INSCOPE
5498     unless (defined $i) {
5499 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:select');
5500 wakaba 1.1 ## Ignore the token
5501     $token = $self->_get_next_token;
5502     redo B;
5503     }
5504    
5505 wakaba 1.3 splice @{$self->{open_elements}}, $i;
5506 wakaba 1.1
5507 wakaba 1.3 $self->_reset_insertion_mode;
5508 wakaba 1.1
5509     $token = $self->_get_next_token;
5510     redo B;
5511     } else {
5512     #
5513     }
5514     } elsif ($token->{type} eq 'end tag') {
5515     if ($token->{tag_name} eq 'optgroup') {
5516 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] eq 'option' and
5517     $self->{open_elements}->[-2]->[1] eq 'optgroup') {
5518 wakaba 1.1 ## As if </option>
5519 wakaba 1.3 splice @{$self->{open_elements}}, -2;
5520     } elsif ($self->{open_elements}->[-1]->[1] eq 'optgroup') {
5521     pop @{$self->{open_elements}};
5522 wakaba 1.1 } else {
5523 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
5524 wakaba 1.1 ## Ignore the token
5525     }
5526     $token = $self->_get_next_token;
5527     redo B;
5528     } elsif ($token->{tag_name} eq 'option') {
5529 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] eq 'option') {
5530     pop @{$self->{open_elements}};
5531 wakaba 1.1 } else {
5532 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
5533 wakaba 1.1 ## Ignore the token
5534     }
5535     $token = $self->_get_next_token;
5536     redo B;
5537     } elsif ($token->{tag_name} eq 'select') {
5538     ## have an element in table scope
5539     my $i;
5540 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5541     my $node = $self->{open_elements}->[$_];
5542 wakaba 1.1 if ($node->[1] eq $token->{tag_name}) {
5543     $i = $_;
5544     last INSCOPE;
5545     } elsif ({
5546     table => 1, html => 1,
5547     }->{$node->[1]}) {
5548     last INSCOPE;
5549     }
5550     } # INSCOPE
5551     unless (defined $i) {
5552 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
5553 wakaba 1.1 ## Ignore the token
5554     $token = $self->_get_next_token;
5555     redo B;
5556     }
5557    
5558 wakaba 1.3 splice @{$self->{open_elements}}, $i;
5559 wakaba 1.1
5560 wakaba 1.3 $self->_reset_insertion_mode;
5561 wakaba 1.1
5562     $token = $self->_get_next_token;
5563     redo B;
5564     } elsif ({
5565     caption => 1, table => 1, tbody => 1,
5566     tfoot => 1, thead => 1, tr => 1, td => 1, th => 1,
5567     }->{$token->{tag_name}}) {
5568 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
5569 wakaba 1.1
5570     ## have an element in table scope
5571     my $i;
5572 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5573     my $node = $self->{open_elements}->[$_];
5574 wakaba 1.1 if ($node->[1] eq $token->{tag_name}) {
5575     $i = $_;
5576     last INSCOPE;
5577     } elsif ({
5578     table => 1, html => 1,
5579     }->{$node->[1]}) {
5580     last INSCOPE;
5581     }
5582     } # INSCOPE
5583     unless (defined $i) {
5584     ## Ignore the token
5585     $token = $self->_get_next_token;
5586     redo B;
5587     }
5588    
5589     ## As if </select>
5590     ## have an element in table scope
5591     undef $i;
5592 wakaba 1.3 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5593     my $node = $self->{open_elements}->[$_];
5594 wakaba 1.1 if ($node->[1] eq 'select') {
5595     $i = $_;
5596     last INSCOPE;
5597     } elsif ({
5598     table => 1, html => 1,
5599     }->{$node->[1]}) {
5600     last INSCOPE;
5601     }
5602     } # INSCOPE
5603     unless (defined $i) {
5604 wakaba 1.3 $self->{parse_error}-> (type => 'unmatched end tag:select');
5605 wakaba 1.1 ## Ignore the </select> token
5606     $token = $self->_get_next_token; ## TODO: ok?
5607     redo B;
5608     }
5609    
5610 wakaba 1.3 splice @{$self->{open_elements}}, $i;
5611 wakaba 1.1
5612 wakaba 1.3 $self->_reset_insertion_mode;
5613 wakaba 1.1
5614     ## reprocess
5615     redo B;
5616     } else {
5617     #
5618     }
5619     } else {
5620     #
5621     }
5622    
5623 wakaba 1.3 $self->{parse_error}-> (type => 'in select:'.$token->{tag_name});
5624 wakaba 1.1 ## Ignore the token
5625     $token = $self->_get_next_token;
5626     redo B;
5627 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'after body') {
5628 wakaba 1.1 if ($token->{type} eq 'character') {
5629     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
5630     ## As if in body
5631     $reconstruct_active_formatting_elements->($insert_to_current);
5632    
5633 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
5634 wakaba 1.1
5635     unless (length $token->{data}) {
5636     $token = $self->_get_next_token;
5637     redo B;
5638     }
5639     }
5640    
5641     #
5642 wakaba 1.3 $self->{parse_error}-> (type => 'after body:#'.$token->{type});
5643 wakaba 1.1 } elsif ($token->{type} eq 'comment') {
5644     my $comment = $self->{document}->create_comment ($token->{data});
5645 wakaba 1.3 $self->{open_elements}->[0]->[0]->append_child ($comment);
5646 wakaba 1.1 $token = $self->_get_next_token;
5647     redo B;
5648 wakaba 1.3 } elsif ($token->{type} eq 'start tag') {
5649     $self->{parse_error}-> (type => 'after body:'.$token->{tag_name});
5650     #
5651 wakaba 1.1 } elsif ($token->{type} eq 'end tag') {
5652     if ($token->{tag_name} eq 'html') {
5653 wakaba 1.3 if (defined $self->{inner_html_node}) {
5654     $self->{parse_error}-> (type => 'unmatched end tag:html');
5655     ## Ignore the token
5656     $token = $self->_get_next_token;
5657     redo B;
5658     } else {
5659     $phase = 'trailing end';
5660     $token = $self->_get_next_token;
5661     redo B;
5662     }
5663 wakaba 1.1 } else {
5664 wakaba 1.3 $self->{parse_error}-> (type => 'after body:/'.$token->{tag_name});
5665 wakaba 1.1 }
5666     } else {
5667 wakaba 1.3 $self->{parse_error}-> (type => 'after body:#'.$token->{type});
5668 wakaba 1.1 }
5669    
5670 wakaba 1.3 $self->{insertion_mode} = 'in body';
5671 wakaba 1.1 ## reprocess
5672     redo B;
5673 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'in frameset') {
5674 wakaba 1.1 if ($token->{type} eq 'character') {
5675     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
5676 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
5677 wakaba 1.1
5678     unless (length $token->{data}) {
5679     $token = $self->_get_next_token;
5680     redo B;
5681     }
5682     }
5683    
5684     #
5685     } elsif ($token->{type} eq 'comment') {
5686     my $comment = $self->{document}->create_comment ($token->{data});
5687 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
5688 wakaba 1.1 $token = $self->_get_next_token;
5689     redo B;
5690     } elsif ($token->{type} eq 'start tag') {
5691     if ($token->{tag_name} eq 'frameset') {
5692    
5693     {
5694     my $el;
5695    
5696     $el = $self->{document}->create_element_ns
5697     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
5698    
5699     for my $attr_name (keys %{ $token->{attributes}}) {
5700     $el->set_attribute_ns (undef, [undef, $attr_name],
5701     $token->{attributes} ->{$attr_name}->{value});
5702     }
5703    
5704 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($el);
5705     push @{$self->{open_elements}}, [$el, $token->{tag_name}];
5706 wakaba 1.1 }
5707    
5708     $token = $self->_get_next_token;
5709     redo B;
5710     } elsif ($token->{tag_name} eq 'frame') {
5711    
5712     {
5713     my $el;
5714    
5715     $el = $self->{document}->create_element_ns
5716     (q<http://www.w3.org/1999/xhtml>, [undef, $token->{tag_name}]);
5717    
5718     for my $attr_name (keys %{ $token->{attributes}}) {
5719     $el->set_attribute_ns (undef, [undef, $attr_name],
5720     $token->{attributes} ->{$attr_name}->{value});
5721     }
5722    
5723 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($el);
5724     push @{$self->{open_elements}}, [$el, $token->{tag_name}];
5725 wakaba 1.1 }
5726    
5727 wakaba 1.3 pop @{$self->{open_elements}};
5728 wakaba 1.1 $token = $self->_get_next_token;
5729     redo B;
5730     } elsif ($token->{tag_name} eq 'noframes') {
5731     $in_body->($insert_to_current);
5732     redo B;
5733     } else {
5734     #
5735     }
5736     } elsif ($token->{type} eq 'end tag') {
5737     if ($token->{tag_name} eq 'frameset') {
5738 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] eq 'html' and
5739     @{$self->{open_elements}} == 1) {
5740     $self->{parse_error}-> (type => 'unmatched end tag:'.$token->{tag_name});
5741 wakaba 1.1 ## Ignore the token
5742     $token = $self->_get_next_token;
5743     } else {
5744 wakaba 1.3 pop @{$self->{open_elements}};
5745 wakaba 1.1 $token = $self->_get_next_token;
5746     }
5747    
5748     ## if not inner_html and
5749 wakaba 1.3 if ($self->{open_elements}->[-1]->[1] ne 'frameset') {
5750     $self->{insertion_mode} = 'after frameset';
5751 wakaba 1.1 }
5752     redo B;
5753     } else {
5754     #
5755     }
5756     } else {
5757     #
5758     }
5759    
5760 wakaba 1.3 if (defined $token->{tag_name}) {
5761     $self->{parse_error}-> (type => 'in frameset:'.$token->{tag_name});
5762     } else {
5763     $self->{parse_error}-> (type => 'in frameset:#'.$token->{type});
5764     }
5765 wakaba 1.1 ## Ignore the token
5766     $token = $self->_get_next_token;
5767     redo B;
5768 wakaba 1.3 } elsif ($self->{insertion_mode} eq 'after frameset') {
5769 wakaba 1.1 if ($token->{type} eq 'character') {
5770     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
5771 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
5772 wakaba 1.1
5773     unless (length $token->{data}) {
5774     $token = $self->_get_next_token;
5775     redo B;
5776     }
5777     }
5778    
5779     #
5780     } elsif ($token->{type} eq 'comment') {
5781     my $comment = $self->{document}->create_comment ($token->{data});
5782 wakaba 1.3 $self->{open_elements}->[-1]->[0]->append_child ($comment);
5783 wakaba 1.1 $token = $self->_get_next_token;
5784     redo B;
5785     } elsif ($token->{type} eq 'start tag') {
5786     if ($token->{tag_name} eq 'noframes') {
5787     $in_body->($insert_to_current);
5788     redo B;
5789     } else {
5790     #
5791     }
5792     } elsif ($token->{type} eq 'end tag') {
5793     if ($token->{tag_name} eq 'html') {
5794     $phase = 'trailing end';
5795     $token = $self->_get_next_token;
5796     redo B;
5797     } else {
5798     #
5799     }
5800     } else {
5801     #
5802     }
5803    
5804 wakaba 1.3 if (defined $token->{tag_name}) {
5805     $self->{parse_error}-> (type => 'after frameset:'.$token->{tag_name});
5806     } else {
5807     $self->{parse_error}-> (type => 'after frameset:#'.$token->{type});
5808     }
5809 wakaba 1.1 ## Ignore the token
5810     $token = $self->_get_next_token;
5811     redo B;
5812    
5813     ## ISSUE: An issue in spec there
5814     } else {
5815 wakaba 1.3 die "$0: $self->{insertion_mode}: Unknown insertion mode";
5816 wakaba 1.1 }
5817     }
5818     } elsif ($phase eq 'trailing end') {
5819     ## states in the main stage is preserved yet # MUST
5820    
5821     if ($token->{type} eq 'DOCTYPE') {
5822 wakaba 1.3 $self->{parse_error}-> (type => 'after html:#DOCTYPE');
5823 wakaba 1.1 ## Ignore the token
5824     $token = $self->_get_next_token;
5825     redo B;
5826     } elsif ($token->{type} eq 'comment') {
5827     my $comment = $self->{document}->create_comment ($token->{data});
5828     $self->{document}->append_child ($comment);
5829     $token = $self->_get_next_token;
5830     redo B;
5831     } elsif ($token->{type} eq 'character') {
5832     if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {
5833     my $data = $1;
5834     ## As if in the main phase.
5835     ## NOTE: The insertion mode in the main phase
5836     ## just before the phase has been changed to the trailing
5837     ## end phase is either "after body" or "after frameset".
5838     $reconstruct_active_formatting_elements->($insert_to_current)
5839     if $phase eq 'main';
5840    
5841 wakaba 1.3 $self->{open_elements}->[-1]->[0]->manakai_append_text ($data);
5842 wakaba 1.1
5843     unless (length $token->{data}) {
5844     $token = $self->_get_next_token;
5845     redo B;
5846     }
5847     }
5848    
5849 wakaba 1.3 $self->{parse_error}-> (type => 'after html:#character');
5850 wakaba 1.1 $phase = 'main';
5851     ## reprocess
5852     redo B;
5853     } elsif ($token->{type} eq 'start tag' or
5854     $token->{type} eq 'end tag') {
5855 wakaba 1.3 $self->{parse_error}-> (type => 'after html:'.$token->{tag_name});
5856 wakaba 1.1 $phase = 'main';
5857     ## reprocess
5858     redo B;
5859     } elsif ($token->{type} eq 'end-of-file') {
5860     ## Stop parsing
5861     last B;
5862     } else {
5863     die "$0: $token->{type}: Unknown token";
5864     }
5865     }
5866     } # B
5867    
5868     ## Stop parsing # MUST
5869    
5870     ## TODO: script stuffs
5871 wakaba 1.3 } # _tree_construct_main
5872    
5873     sub set_inner_html ($$$) {
5874     my $class = shift;
5875     my $node = shift;
5876     my $s = \$_[0];
5877     my $onerror = $_[1];
5878    
5879     my $nt = $node->node_type;
5880     if ($nt == 9) {
5881     # MUST
5882    
5883     ## Step 1 # MUST
5884     ## TODO: If the document has an active parser, ...
5885     ## ISSUE: There is an issue in the spec.
5886    
5887     ## Step 2 # MUST
5888     my @cn = @{$node->child_nodes};
5889     for (@cn) {
5890     $node->remove_child ($_);
5891     }
5892    
5893     ## Step 3, 4, 5 # MUST
5894     $class->parse_string ($$s => $node, $onerror);
5895     } elsif ($nt == 1) {
5896     ## TODO: If non-html element
5897    
5898     ## NOTE: Most of this code is copied from |parse_string|
5899    
5900     ## Step 1 # MUST
5901     my $doc = $node->owner_document->implementation->create_document;
5902     ## TODO: Mark as HTML document
5903     my $p = $class->new;
5904     $p->{document} = $doc;
5905    
5906     ## Step 9 # MUST
5907     my $i = 0;
5908     my $line = 1;
5909     my $column = 0;
5910     $p->{set_next_input_character} = sub {
5911     my $self = shift;
5912     $self->{next_input_character} = -1 and return if $i >= length $$s;
5913     $self->{next_input_character} = ord substr $$s, $i++, 1;
5914     $column++;
5915 wakaba 1.4
5916     if ($self->{next_input_character} == 0x000A) { # LF
5917     $line++;
5918     $column = 0;
5919     } elsif ($self->{next_input_character} == 0x000D) { # CR
5920 wakaba 1.3 if ($i >= length $$s) {
5921     #
5922     } else {
5923     my $next_char = ord substr $$s, $i++, 1;
5924     if ($next_char == 0x000A) { # LF
5925     #
5926     } else {
5927     push @{$self->{char}}, $next_char;
5928     }
5929     }
5930     $self->{next_input_character} = 0x000A; # LF # MUST
5931     $line++;
5932 wakaba 1.4 $column = 0;
5933 wakaba 1.3 } elsif ($self->{next_input_character} > 0x10FFFF) {
5934     $self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
5935     } elsif ($self->{next_input_character} == 0x0000) { # NULL
5936     $self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
5937     }
5938     };
5939    
5940     my $ponerror = $onerror || sub {
5941     my (%opt) = @_;
5942     warn "Parse error ($opt{type}) at line $opt{line} column $opt{column}\n";
5943     };
5944     $p->{parse_error} = sub {
5945     $ponerror->(@_, line => $line, column => $column);
5946     };
5947    
5948     $p->_initialize_tokenizer;
5949     $p->_initialize_tree_constructor;
5950    
5951     ## Step 2
5952     my $node_ln = $node->local_name;
5953     $p->{content_model_flag} = {
5954     title => 'RCDATA',
5955     textarea => 'RCDATA',
5956     style => 'CDATA',
5957     script => 'CDATA',
5958     xmp => 'CDATA',
5959     iframe => 'CDATA',
5960     noembed => 'CDATA',
5961     noframes => 'CDATA',
5962     noscript => 'CDATA',
5963     plaintext => 'PLAINTEXT',
5964     }->{$node_ln} || 'PCDATA';
5965     ## ISSUE: What is "the name of the element"? local name?
5966    
5967     $p->{inner_html_node} = [$node, $node_ln];
5968    
5969     ## Step 4
5970     my $root = $doc->create_element_ns
5971     ('http://www.w3.org/1999/xhtml', [undef, 'html']);
5972    
5973     ## Step 5 # MUST
5974     $doc->append_child ($root);
5975    
5976     ## Step 6 # MUST
5977     push @{$p->{open_elements}}, [$root, 'html'];
5978    
5979     undef $p->{head_element};
5980    
5981     ## Step 7 # MUST
5982     $p->_reset_insertion_mode;
5983    
5984     ## Step 8 # MUST
5985     my $anode = $node;
5986     AN: while (defined $anode) {
5987     if ($anode->node_type == 1) {
5988     my $nsuri = $anode->namespace_uri;
5989     if (defined $nsuri and $nsuri eq 'http://www.w3.org/1999/xhtml') {
5990     if ($anode->local_name eq 'form') { ## TODO: case?
5991     $p->{form_element} = $anode;
5992     last AN;
5993     }
5994     }
5995     }
5996     $anode = $anode->parent_node;
5997     } # AN
5998    
5999     ## Step 3 # MUST
6000     ## Step 10 # MUST
6001     {
6002     my $self = $p;
6003     $token = $self->_get_next_token;
6004     }
6005     $p->_tree_construction_main;
6006    
6007     ## Step 11 # MUST
6008     my @cn = @{$node->child_nodes};
6009     for (@cn) {
6010     $node->remove_child ($_);
6011     }
6012     ## ISSUE: mutation events? read-only?
6013    
6014     ## Step 12 # MUST
6015     @cn = @{$root->child_nodes};
6016     for (@cn) {
6017     $node->append_child ($_);
6018     }
6019     ## ISSUE: adopt_node? mutation events?
6020    
6021     $p->_terminate_tree_constructor;
6022     } else {
6023     die "$0: |set_inner_html| is not defined for node of type $nt";
6024     }
6025     } # set_inner_html
6026    
6027     } # tree construction stage
6028 wakaba 1.1
6029     sub get_inner_html ($$$) {
6030 wakaba 1.3 my (undef, $node, $on_error) = @_;
6031 wakaba 1.1
6032     ## Step 1
6033     my $s = '';
6034    
6035     my $in_cdata;
6036     my $parent = $node;
6037     while (defined $parent) {
6038     if ($parent->node_type == 1 and
6039     $parent->namespace_uri eq 'http://www.w3.org/1999/xhtml' and
6040     {
6041     style => 1, script => 1, xmp => 1, iframe => 1,
6042     noembed => 1, noframes => 1, noscript => 1,
6043     }->{$parent->local_name}) { ## TODO: case thingy
6044     $in_cdata = 1;
6045     }
6046     $parent = $parent->parent_node;
6047     }
6048    
6049     ## Step 2
6050     my @node = @{$node->child_nodes};
6051     C: while (@node) {
6052     my $child = shift @node;
6053     unless (ref $child) {
6054     if ($child eq 'cdata-out') {
6055     $in_cdata = 0;
6056     } else {
6057     $s .= $child; # end tag
6058     }
6059     next C;
6060     }
6061    
6062     my $nt = $child->node_type;
6063     if ($nt == 1) { # Element
6064     my $tag_name = lc $child->tag_name; ## ISSUE: Definition of "lowercase"
6065     $s .= '<' . $tag_name;
6066    
6067     ## ISSUE: Non-html elements
6068    
6069     my @attrs = @{$child->attributes}; # sort order MUST be stable
6070     for my $attr (@attrs) { # order is implementation dependent
6071     my $attr_name = lc $attr->name; ## ISSUE: Definition of "lowercase"
6072     $s .= ' ' . $attr_name . '="';
6073     my $attr_value = $attr->value;
6074     ## escape
6075     $attr_value =~ s/&/&amp;/g;
6076     $attr_value =~ s/</&lt;/g;
6077     $attr_value =~ s/>/&gt;/g;
6078     $attr_value =~ s/"/&quot;/g;
6079     $s .= $attr_value . '"';
6080     }
6081     $s .= '>';
6082    
6083     next C if {
6084     area => 1, base => 1, basefont => 1, bgsound => 1,
6085     br => 1, col => 1, embed => 1, frame => 1, hr => 1,
6086     img => 1, input => 1, link => 1, meta => 1, param => 1,
6087     spacer => 1, wbr => 1,
6088     }->{$tag_name};
6089    
6090     if (not $in_cdata and {
6091     style => 1, script => 1, xmp => 1, iframe => 1,
6092     noembed => 1, noframes => 1, noscript => 1,
6093     }->{$tag_name}) {
6094     unshift @node, 'cdata-out';
6095     $in_cdata = 1;
6096     }
6097    
6098     unshift @node, @{$child->child_nodes}, '</' . $tag_name . '>';
6099     } elsif ($nt == 3 or $nt == 4) {
6100     if ($in_cdata) {
6101     $s .= $child->data;
6102     } else {
6103     my $value = $child->data;
6104     $value =~ s/&/&amp;/g;
6105     $value =~ s/</&lt;/g;
6106     $value =~ s/>/&gt;/g;
6107     $value =~ s/"/&quot;/g;
6108     $s .= $value;
6109     }
6110     } elsif ($nt == 8) {
6111     $s .= '<!--' . $child->data . '-->';
6112     } elsif ($nt == 10) {
6113     $s .= '<!DOCTYPE ' . $child->name . '>';
6114     } elsif ($nt == 5) { # entrefs
6115     push @node, @{$child->child_nodes};
6116     } else {
6117     $on_error->($child) if defined $on_error;
6118     }
6119     ## ISSUE: This code does not support PIs.
6120     } # C
6121    
6122     ## Step 3
6123     return \$s;
6124     } # get_inner_html
6125    
6126     1;
6127 wakaba 1.4 # $Date: 2007/05/02 13:44:34 $

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24