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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

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

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

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

	* LICENSE: New document.

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

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

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

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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24