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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.11 - (show annotations) (download) (as text)
Sat Jun 23 03:53:35 2007 UTC (19 years, 3 months ago) by wakaba
Branch: MAIN
Changes since 1.10: +2 -5 lines
File MIME type: application/x-wais-source
++ whatpm/t/ChangeLog	23 Jun 2007 03:51:41 -0000
	* tokenizer-test-2.dat: New test.  This test file contains
	some tests that cannot be reliably tested by |HTML-tokenize.t|,
	since they depends on how characeters are supplied to the
	tokenizer.

	* HTML-tree.t: |tokenizer-test-2.dat| added.  |#data escaped|
	etc. supported.

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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24