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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.239 - (show annotations) (download)
Sun Sep 6 23:32:06 2009 UTC (16 years, 11 months ago) by wakaba
Branch: MAIN
CVS Tags: HEAD
Changes since 1.238: +31 -15 lines
++ whatpm/t/ChangeLog	6 Sep 2009 23:31:19 -0000
2009-09-07  Wakaba  <wakaba@suika.fam.cx>

	* tree-test-1.dat: Added new test data on obsolete permitted
	DOCTYPEs (HTML5 revision 3378).

++ whatpm/Whatpm/ChangeLog	6 Sep 2009 23:31:49 -0000
2009-09-07  Wakaba  <wakaba@suika.fam.cx>

	* HTML.pm.src (_tree_construction_initial): Implemented "obsolete
	permitted DOCTYPEs" (HTML5 revision 3378).

1 package Whatpm::HTML;
2 use strict;
3 our $VERSION=do{my @r=(q$Revision: 1.243 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
4 use Error qw(:try);
5
6 use Whatpm::HTML::Tokenizer;
7
8 ## NOTE: This module don't check all HTML5 parse errors; character
9 ## encoding related parse errors are expected to be handled by relevant
10 ## modules.
11 ## Parse errors for control characters that are not allowed in HTML5
12 ## documents, for surrogate code points, and for noncharacter code
13 ## points, as well as U+FFFD substitions for characters whose code points
14 ## is higher than U+10FFFF may be detected by combining the parser with
15 ## the checker implemented by Whatpm::Charset::UnicodeChecker (for its
16 ## usage example, see |t/HTML-tree.t| in the Whatpm package or the
17 ## WebHACC::Language::HTML module in the WebHACC package).
18
19 ## ISSUE:
20 ## var doc = implementation.createDocument (null, null, null);
21 ## doc.write ('');
22 ## alert (doc.compatMode);
23
24 require IO::Handle;
25
26 ## Namespace URLs
27
28 my $HTML_NS = q<http://www.w3.org/1999/xhtml>;
29 my $MML_NS = q<http://www.w3.org/1998/Math/MathML>;
30 my $SVG_NS = q<http://www.w3.org/2000/svg>;
31 my $XLINK_NS = q<http://www.w3.org/1999/xlink>;
32 my $XML_NS = q<http://www.w3.org/XML/1998/namespace>;
33 my $XMLNS_NS = q<http://www.w3.org/2000/xmlns/>;
34
35 ## Element categories
36
37 ## Bits 12-15
38 sub SPECIAL_EL () { 0b1_000000000000000 }
39 sub SCOPING_EL () { 0b1_00000000000000 }
40 sub FORMATTING_EL () { 0b1_0000000000000 }
41 sub PHRASING_EL () { 0b1_000000000000 }
42
43 ## Bits 10-11
44 #sub FOREIGN_EL () { 0b1_00000000000 } # see Whatpm::HTML::Tokenizer
45 sub FOREIGN_FLOW_CONTENT_EL () { 0b1_0000000000 }
46
47 ## Bits 6-9
48 sub TABLE_SCOPING_EL () { 0b1_000000000 }
49 sub TABLE_ROWS_SCOPING_EL () { 0b1_00000000 }
50 sub TABLE_ROW_SCOPING_EL () { 0b1_0000000 }
51 sub TABLE_ROWS_EL () { 0b1_000000 }
52
53 ## Bit 5
54 sub ADDRESS_DIV_P_EL () { 0b1_00000 }
55
56 ## NOTE: Used in </body> and EOF algorithms.
57 ## Bit 4
58 sub ALL_END_TAG_OPTIONAL_EL () { 0b1_0000 }
59
60 ## NOTE: Used in "generate implied end tags" algorithm.
61 ## NOTE: There is a code where a modified version of
62 ## END_TAG_OPTIONAL_EL is used in "generate implied end tags"
63 ## implementation (search for the algorithm name).
64 ## Bit 3
65 sub END_TAG_OPTIONAL_EL () { 0b1_000 }
66
67 ## Bits 0-2
68
69 sub MISC_SPECIAL_EL () { SPECIAL_EL | 0b000 }
70 sub FORM_EL () { SPECIAL_EL | 0b001 }
71 sub FRAMESET_EL () { SPECIAL_EL | 0b010 }
72 sub HEADING_EL () { SPECIAL_EL | 0b011 }
73 sub SELECT_EL () { SPECIAL_EL | 0b100 }
74 sub SCRIPT_EL () { SPECIAL_EL | 0b101 }
75
76 sub ADDRESS_DIV_EL () { SPECIAL_EL | ADDRESS_DIV_P_EL | 0b001 }
77 sub BODY_EL () { SPECIAL_EL | ALL_END_TAG_OPTIONAL_EL | 0b001 }
78
79 sub DTDD_EL () {
80 SPECIAL_EL |
81 END_TAG_OPTIONAL_EL |
82 ALL_END_TAG_OPTIONAL_EL |
83 0b010
84 }
85 sub LI_EL () {
86 SPECIAL_EL |
87 END_TAG_OPTIONAL_EL |
88 ALL_END_TAG_OPTIONAL_EL |
89 0b100
90 }
91 sub P_EL () {
92 SPECIAL_EL |
93 ADDRESS_DIV_P_EL |
94 END_TAG_OPTIONAL_EL |
95 ALL_END_TAG_OPTIONAL_EL |
96 0b001
97 }
98
99 sub TABLE_ROW_EL () {
100 SPECIAL_EL |
101 TABLE_ROWS_EL |
102 TABLE_ROW_SCOPING_EL |
103 ALL_END_TAG_OPTIONAL_EL |
104 0b001
105 }
106 sub TABLE_ROW_GROUP_EL () {
107 SPECIAL_EL |
108 TABLE_ROWS_EL |
109 TABLE_ROWS_SCOPING_EL |
110 ALL_END_TAG_OPTIONAL_EL |
111 0b001
112 }
113
114 sub MISC_SCOPING_EL () { SCOPING_EL | 0b000 }
115 sub BUTTON_EL () { SCOPING_EL | 0b001 }
116 sub CAPTION_EL () { SCOPING_EL | 0b010 }
117 sub HTML_EL () {
118 SCOPING_EL |
119 TABLE_SCOPING_EL |
120 TABLE_ROWS_SCOPING_EL |
121 TABLE_ROW_SCOPING_EL |
122 ALL_END_TAG_OPTIONAL_EL |
123 0b001
124 }
125 sub TABLE_EL () {
126 SCOPING_EL |
127 TABLE_ROWS_EL |
128 TABLE_SCOPING_EL |
129 0b001
130 }
131 sub TABLE_CELL_EL () {
132 SCOPING_EL |
133 TABLE_ROW_SCOPING_EL |
134 ALL_END_TAG_OPTIONAL_EL |
135 0b001
136 }
137
138 sub MISC_FORMATTING_EL () { FORMATTING_EL | 0b000 }
139 sub A_EL () { FORMATTING_EL | 0b001 }
140 sub NOBR_EL () { FORMATTING_EL | 0b010 }
141
142 sub RUBY_EL () { PHRASING_EL | 0b001 }
143
144 ## ISSUE: ALL_END_TAG_OPTIONAL_EL?
145 sub OPTGROUP_EL () { PHRASING_EL | END_TAG_OPTIONAL_EL | 0b001 }
146 sub OPTION_EL () { PHRASING_EL | END_TAG_OPTIONAL_EL | 0b010 }
147 sub RUBY_COMPONENT_EL () { PHRASING_EL | END_TAG_OPTIONAL_EL | 0b100 }
148
149 sub MML_AXML_EL () { PHRASING_EL | FOREIGN_EL | 0b001 }
150
151 my $el_category = {
152 a => A_EL,
153 address => ADDRESS_DIV_EL,
154 applet => MISC_SCOPING_EL,
155 area => MISC_SPECIAL_EL,
156 article => MISC_SPECIAL_EL,
157 aside => MISC_SPECIAL_EL,
158 b => FORMATTING_EL,
159 base => MISC_SPECIAL_EL,
160 basefont => MISC_SPECIAL_EL,
161 bgsound => MISC_SPECIAL_EL,
162 big => FORMATTING_EL,
163 blockquote => MISC_SPECIAL_EL,
164 body => BODY_EL,
165 br => MISC_SPECIAL_EL,
166 button => BUTTON_EL,
167 caption => CAPTION_EL,
168 center => MISC_SPECIAL_EL,
169 col => MISC_SPECIAL_EL,
170 colgroup => MISC_SPECIAL_EL,
171 command => MISC_SPECIAL_EL,
172 datagrid => MISC_SPECIAL_EL,
173 dd => DTDD_EL,
174 details => MISC_SPECIAL_EL,
175 dialog => MISC_SPECIAL_EL,
176 dir => MISC_SPECIAL_EL,
177 div => ADDRESS_DIV_EL,
178 dl => MISC_SPECIAL_EL,
179 dt => DTDD_EL,
180 em => FORMATTING_EL,
181 embed => MISC_SPECIAL_EL,
182 fieldset => MISC_SPECIAL_EL,
183 figure => MISC_SPECIAL_EL,
184 font => FORMATTING_EL,
185 footer => MISC_SPECIAL_EL,
186 form => FORM_EL,
187 frame => MISC_SPECIAL_EL,
188 frameset => FRAMESET_EL,
189 h1 => HEADING_EL,
190 h2 => HEADING_EL,
191 h3 => HEADING_EL,
192 h4 => HEADING_EL,
193 h5 => HEADING_EL,
194 h6 => HEADING_EL,
195 head => MISC_SPECIAL_EL,
196 header => MISC_SPECIAL_EL,
197 hgroup => MISC_SPECIAL_EL,
198 hr => MISC_SPECIAL_EL,
199 html => HTML_EL,
200 i => FORMATTING_EL,
201 iframe => MISC_SPECIAL_EL,
202 img => MISC_SPECIAL_EL,
203 #image => MISC_SPECIAL_EL, ## NOTE: Commented out in the spec.
204 input => MISC_SPECIAL_EL,
205 isindex => MISC_SPECIAL_EL,
206 ## XXX keygen? (Whether a void element is in Special or not does not
207 ## affect to the processing, however.)
208 li => LI_EL,
209 link => MISC_SPECIAL_EL,
210 listing => MISC_SPECIAL_EL,
211 marquee => MISC_SCOPING_EL,
212 menu => MISC_SPECIAL_EL,
213 meta => MISC_SPECIAL_EL,
214 nav => MISC_SPECIAL_EL,
215 nobr => NOBR_EL,
216 noembed => MISC_SPECIAL_EL,
217 noframes => MISC_SPECIAL_EL,
218 noscript => MISC_SPECIAL_EL,
219 object => MISC_SCOPING_EL,
220 ol => MISC_SPECIAL_EL,
221 optgroup => OPTGROUP_EL,
222 option => OPTION_EL,
223 p => P_EL,
224 param => MISC_SPECIAL_EL,
225 plaintext => MISC_SPECIAL_EL,
226 pre => MISC_SPECIAL_EL,
227 rp => RUBY_COMPONENT_EL,
228 rt => RUBY_COMPONENT_EL,
229 ruby => RUBY_EL,
230 s => FORMATTING_EL,
231 script => MISC_SPECIAL_EL,
232 select => SELECT_EL,
233 section => MISC_SPECIAL_EL,
234 small => FORMATTING_EL,
235 spacer => MISC_SPECIAL_EL,
236 strike => FORMATTING_EL,
237 strong => FORMATTING_EL,
238 style => MISC_SPECIAL_EL,
239 table => TABLE_EL,
240 tbody => TABLE_ROW_GROUP_EL,
241 td => TABLE_CELL_EL,
242 textarea => MISC_SPECIAL_EL,
243 tfoot => TABLE_ROW_GROUP_EL,
244 th => TABLE_CELL_EL,
245 thead => TABLE_ROW_GROUP_EL,
246 title => MISC_SPECIAL_EL,
247 tr => TABLE_ROW_EL,
248 tt => FORMATTING_EL,
249 u => FORMATTING_EL,
250 ul => MISC_SPECIAL_EL,
251 wbr => MISC_SPECIAL_EL,
252 xmp => MISC_SPECIAL_EL,
253 };
254
255 my $el_category_f = {
256 $MML_NS => {
257 'annotation-xml' => MML_AXML_EL,
258 mi => FOREIGN_EL | FOREIGN_FLOW_CONTENT_EL,
259 mo => FOREIGN_EL | FOREIGN_FLOW_CONTENT_EL,
260 mn => FOREIGN_EL | FOREIGN_FLOW_CONTENT_EL,
261 ms => FOREIGN_EL | FOREIGN_FLOW_CONTENT_EL,
262 mtext => FOREIGN_EL | FOREIGN_FLOW_CONTENT_EL,
263 },
264 $SVG_NS => {
265 foreignObject => SCOPING_EL | FOREIGN_EL | FOREIGN_FLOW_CONTENT_EL,
266 desc => FOREIGN_EL | FOREIGN_FLOW_CONTENT_EL,
267 title => FOREIGN_EL | FOREIGN_FLOW_CONTENT_EL,
268 },
269 ## NOTE: In addition, FOREIGN_EL is set to non-HTML elements.
270 };
271
272 my $svg_attr_name = {
273 attributename => 'attributeName',
274 attributetype => 'attributeType',
275 basefrequency => 'baseFrequency',
276 baseprofile => 'baseProfile',
277 calcmode => 'calcMode',
278 clippathunits => 'clipPathUnits',
279 contentscripttype => 'contentScriptType',
280 contentstyletype => 'contentStyleType',
281 diffuseconstant => 'diffuseConstant',
282 edgemode => 'edgeMode',
283 externalresourcesrequired => 'externalResourcesRequired',
284 filterres => 'filterRes',
285 filterunits => 'filterUnits',
286 glyphref => 'glyphRef',
287 gradienttransform => 'gradientTransform',
288 gradientunits => 'gradientUnits',
289 kernelmatrix => 'kernelMatrix',
290 kernelunitlength => 'kernelUnitLength',
291 keypoints => 'keyPoints',
292 keysplines => 'keySplines',
293 keytimes => 'keyTimes',
294 lengthadjust => 'lengthAdjust',
295 limitingconeangle => 'limitingConeAngle',
296 markerheight => 'markerHeight',
297 markerunits => 'markerUnits',
298 markerwidth => 'markerWidth',
299 maskcontentunits => 'maskContentUnits',
300 maskunits => 'maskUnits',
301 numoctaves => 'numOctaves',
302 pathlength => 'pathLength',
303 patterncontentunits => 'patternContentUnits',
304 patterntransform => 'patternTransform',
305 patternunits => 'patternUnits',
306 pointsatx => 'pointsAtX',
307 pointsaty => 'pointsAtY',
308 pointsatz => 'pointsAtZ',
309 preservealpha => 'preserveAlpha',
310 preserveaspectratio => 'preserveAspectRatio',
311 primitiveunits => 'primitiveUnits',
312 refx => 'refX',
313 refy => 'refY',
314 repeatcount => 'repeatCount',
315 repeatdur => 'repeatDur',
316 requiredextensions => 'requiredExtensions',
317 requiredfeatures => 'requiredFeatures',
318 specularconstant => 'specularConstant',
319 specularexponent => 'specularExponent',
320 spreadmethod => 'spreadMethod',
321 startoffset => 'startOffset',
322 stddeviation => 'stdDeviation',
323 stitchtiles => 'stitchTiles',
324 surfacescale => 'surfaceScale',
325 systemlanguage => 'systemLanguage',
326 tablevalues => 'tableValues',
327 targetx => 'targetX',
328 targety => 'targetY',
329 textlength => 'textLength',
330 viewbox => 'viewBox',
331 viewtarget => 'viewTarget',
332 xchannelselector => 'xChannelSelector',
333 ychannelselector => 'yChannelSelector',
334 zoomandpan => 'zoomAndPan',
335 };
336
337 my $foreign_attr_xname = {
338 'xlink:actuate' => [$XLINK_NS, ['xlink', 'actuate']],
339 'xlink:arcrole' => [$XLINK_NS, ['xlink', 'arcrole']],
340 'xlink:href' => [$XLINK_NS, ['xlink', 'href']],
341 'xlink:role' => [$XLINK_NS, ['xlink', 'role']],
342 'xlink:show' => [$XLINK_NS, ['xlink', 'show']],
343 'xlink:title' => [$XLINK_NS, ['xlink', 'title']],
344 'xlink:type' => [$XLINK_NS, ['xlink', 'type']],
345 'xml:base' => [$XML_NS, ['xml', 'base']],
346 'xml:lang' => [$XML_NS, ['xml', 'lang']],
347 'xml:space' => [$XML_NS, ['xml', 'space']],
348 'xmlns' => [$XMLNS_NS, [undef, 'xmlns']],
349 'xmlns:xlink' => [$XMLNS_NS, ['xmlns', 'xlink']],
350 };
351
352 ## ISSUE: xmlns:xlink="non-xlink-ns" is not an error.
353
354 ## TODO: Invoke the reset algorithm when a resettable element is
355 ## created (cf. HTML5 revision 2259).
356
357 sub parse_byte_string ($$$$;$) {
358 my $self = shift;
359 my $charset_name = shift;
360 open my $input, '<', ref $_[0] ? $_[0] : \($_[0]);
361 return $self->parse_byte_stream ($charset_name, $input, @_[1..$#_]);
362 } # parse_byte_string
363
364 sub parse_byte_stream ($$$$;$$) {
365 # my ($self, $charset_name, $byte_stream, $doc, $onerror, $get_wrapper) = @_;
366 my $self = ref $_[0] ? shift : shift->new;
367 my $charset_name = shift;
368 my $byte_stream = $_[0];
369
370 my $onerror = $_[2] || sub {
371 my (%opt) = @_;
372 warn "Parse error ($opt{type})\n";
373 };
374 $self->{parse_error} = $onerror; # updated later by parse_char_string
375
376 my $get_wrapper = $_[3] || sub ($) {
377 return $_[0]; # $_[0] = byte stream handle, returned = arg to char handle
378 };
379
380 ## HTML5 encoding sniffing algorithm
381 require Message::Charset::Info;
382 my $charset;
383 my $buffer;
384 my ($char_stream, $e_status);
385
386 SNIFFING: {
387 ## NOTE: By setting |allow_fallback| option true when the
388 ## |get_decode_handle| method is invoked, we ignore what the HTML5
389 ## spec requires, i.e. unsupported encoding should be ignored.
390 ## TODO: We should not do this unless the parser is invoked
391 ## in the conformance checking mode, in which this behavior
392 ## would be useful.
393
394 ## Step 1
395 if (defined $charset_name) {
396 $charset = Message::Charset::Info->get_by_html_name ($charset_name);
397 ## TODO: Is this ok? Transfer protocol's parameter should be
398 ## interpreted in its semantics?
399
400 ($char_stream, $e_status) = $charset->get_decode_handle
401 ($byte_stream, allow_error_reporting => 1,
402 allow_fallback => 1);
403 if ($char_stream) {
404 $self->{confident} = 1;
405 last SNIFFING;
406 } else {
407 $self->{parse_error}->(level => $self->{level}->{must}, type => 'charset:not supported',
408 layer => 'encode',
409 line => 1, column => 1,
410 value => $charset_name,
411 level => $self->{level}->{uncertain});
412 }
413 }
414
415 ## Step 2
416 my $byte_buffer = '';
417 for (1..1024) {
418 my $char = $byte_stream->getc;
419 last unless defined $char;
420 $byte_buffer .= $char;
421 } ## TODO: timeout
422
423 ## Step 3
424 if ($byte_buffer =~ /^\xFE\xFF/) {
425 $charset = Message::Charset::Info->get_by_html_name ('utf-16be');
426 ($char_stream, $e_status) = $charset->get_decode_handle
427 ($byte_stream, allow_error_reporting => 1,
428 allow_fallback => 1, byte_buffer => \$byte_buffer);
429 $self->{confident} = 1;
430 last SNIFFING;
431 } elsif ($byte_buffer =~ /^\xFF\xFE/) {
432 $charset = Message::Charset::Info->get_by_html_name ('utf-16le');
433 ($char_stream, $e_status) = $charset->get_decode_handle
434 ($byte_stream, allow_error_reporting => 1,
435 allow_fallback => 1, byte_buffer => \$byte_buffer);
436 $self->{confident} = 1;
437 last SNIFFING;
438 } elsif ($byte_buffer =~ /^\xEF\xBB\xBF/) {
439 $charset = Message::Charset::Info->get_by_html_name ('utf-8');
440 ($char_stream, $e_status) = $charset->get_decode_handle
441 ($byte_stream, allow_error_reporting => 1,
442 allow_fallback => 1, byte_buffer => \$byte_buffer);
443 $self->{confident} = 1;
444 last SNIFFING;
445 }
446
447 ## Step 4
448 ## TODO: <meta charset>
449
450 ## Step 5
451 ## TODO: from history
452
453 ## Step 6
454 require Whatpm::Charset::UniversalCharDet;
455 $charset_name = Whatpm::Charset::UniversalCharDet->detect_byte_string
456 ($byte_buffer);
457 if (defined $charset_name) {
458 $charset = Message::Charset::Info->get_by_html_name ($charset_name);
459
460 require Whatpm::Charset::DecodeHandle;
461 $buffer = Whatpm::Charset::DecodeHandle::ByteBuffer->new
462 ($byte_stream);
463 ($char_stream, $e_status) = $charset->get_decode_handle
464 ($buffer, allow_error_reporting => 1,
465 allow_fallback => 1, byte_buffer => \$byte_buffer);
466 if ($char_stream) {
467 $buffer->{buffer} = $byte_buffer;
468 $self->{parse_error}->(level => $self->{level}->{must}, type => 'sniffing:chardet',
469 text => $charset_name,
470 level => $self->{level}->{info},
471 layer => 'encode',
472 line => 1, column => 1);
473 $self->{confident} = 0;
474 last SNIFFING;
475 }
476 }
477
478 ## Step 7: default
479 ## TODO: Make this configurable.
480 $charset = Message::Charset::Info->get_by_html_name ('windows-1252');
481 ## NOTE: We choose |windows-1252| here, since |utf-8| should be
482 ## detectable in the step 6.
483 require Whatpm::Charset::DecodeHandle;
484 $buffer = Whatpm::Charset::DecodeHandle::ByteBuffer->new
485 ($byte_stream);
486 ($char_stream, $e_status)
487 = $charset->get_decode_handle ($buffer,
488 allow_error_reporting => 1,
489 allow_fallback => 1,
490 byte_buffer => \$byte_buffer);
491 $buffer->{buffer} = $byte_buffer;
492 $self->{parse_error}->(level => $self->{level}->{must}, type => 'sniffing:default',
493 text => 'windows-1252',
494 level => $self->{level}->{info},
495 line => 1, column => 1,
496 layer => 'encode');
497 $self->{confident} = 0;
498 } # SNIFFING
499
500 if ($e_status & Message::Charset::Info::FALLBACK_ENCODING_IMPL ()) {
501 $self->{input_encoding} = $charset->get_iana_name; ## TODO: Should we set actual charset decoder's encoding name?
502 $self->{parse_error}->(level => $self->{level}->{must}, type => 'chardecode:fallback',
503 #text => $self->{input_encoding},
504 level => $self->{level}->{uncertain},
505 line => 1, column => 1,
506 layer => 'encode');
507 } elsif (not ($e_status &
508 Message::Charset::Info::ERROR_REPORTING_ENCODING_IMPL ())) {
509 $self->{input_encoding} = $charset->get_iana_name;
510 $self->{parse_error}->(level => $self->{level}->{must}, type => 'chardecode:no error',
511 text => $self->{input_encoding},
512 level => $self->{level}->{uncertain},
513 line => 1, column => 1,
514 layer => 'encode');
515 } else {
516 $self->{input_encoding} = $charset->get_iana_name;
517 }
518
519 $self->{change_encoding} = sub {
520 my $self = shift;
521 $charset_name = shift;
522 my $token = shift;
523
524 $charset = Message::Charset::Info->get_by_html_name ($charset_name);
525 ($char_stream, $e_status) = $charset->get_decode_handle
526 ($byte_stream, allow_error_reporting => 1, allow_fallback => 1,
527 byte_buffer => \ $buffer->{buffer});
528
529 if ($char_stream) { # if supported
530 ## "Change the encoding" algorithm:
531
532 ## Step 1
533 if (defined $self->{input_encoding} and
534 $self->{input_encoding} eq $charset_name) {
535 $self->{parse_error}->(level => $self->{level}->{must}, type => 'charset label:matching',
536 text => $charset_name,
537 level => $self->{level}->{info});
538 $self->{confident} = 1;
539 return;
540 }
541
542 ## Step 2 (HTML5 revision 3205)
543 if (defined $self->{input_encoding} and
544 Message::Charset::Info->get_by_html_name ($self->{input_encoding})
545 ->{category} & Message::Charset::Info::CHARSET_CATEGORY_UTF16 ()) {
546 $self->{confident} = 1;
547 return;
548 }
549
550 ## Step 3
551 if ($charset->{category} &
552 Message::Charset::Info::CHARSET_CATEGORY_UTF16 ()) {
553 $charset = Message::Charset::Info->get_by_html_name ('utf-8');
554 ($char_stream, $e_status) = $charset->get_decode_handle
555 ($byte_stream,
556 byte_buffer => \ $buffer->{buffer});
557 }
558 $charset_name = $charset->get_iana_name;
559
560 $self->{parse_error}->(level => $self->{level}->{must}, type => 'charset label detected',
561 text => $self->{input_encoding},
562 value => $charset_name,
563 level => $self->{level}->{warn},
564 token => $token);
565
566 ## Step 4
567 # if (can) {
568 ## change the encoding on the fly.
569 #$self->{confident} = 1;
570 #return;
571 # }
572
573 ## Step 5
574 throw Whatpm::HTML::RestartParser ();
575 }
576 }; # $self->{change_encoding}
577
578 my $char_onerror = sub {
579 my (undef, $type, %opt) = @_;
580 $self->{parse_error}->(level => $self->{level}->{must}, layer => 'encode',
581 line => $self->{line}, column => $self->{column} + 1,
582 %opt, type => $type);
583 if ($opt{octets}) {
584 ${$opt{octets}} = "\x{FFFD}"; # relacement character
585 }
586 };
587
588 my $wrapped_char_stream = $get_wrapper->($char_stream);
589 $wrapped_char_stream->onerror ($char_onerror);
590
591 my @args = ($_[1], $_[2]); # $doc, $onerror - $get_wrapper = undef;
592 my $return;
593 try {
594 $return = $self->parse_char_stream ($wrapped_char_stream, @args);
595 } catch Whatpm::HTML::RestartParser with {
596 ## NOTE: Invoked after {change_encoding}.
597
598 if ($e_status & Message::Charset::Info::FALLBACK_ENCODING_IMPL ()) {
599 $self->{input_encoding} = $charset->get_iana_name; ## TODO: Should we set actual charset decoder's encoding name?
600 $self->{parse_error}->(level => $self->{level}->{must}, type => 'chardecode:fallback',
601 level => $self->{level}->{uncertain},
602 #text => $self->{input_encoding},
603 line => 1, column => 1,
604 layer => 'encode');
605 } elsif (not ($e_status &
606 Message::Charset::Info::ERROR_REPORTING_ENCODING_IMPL ())) {
607 $self->{input_encoding} = $charset->get_iana_name;
608 $self->{parse_error}->(level => $self->{level}->{must}, type => 'chardecode:no error',
609 text => $self->{input_encoding},
610 level => $self->{level}->{uncertain},
611 line => 1, column => 1,
612 layer => 'encode');
613 } else {
614 $self->{input_encoding} = $charset->get_iana_name;
615 }
616 $self->{confident} = 1;
617
618 $wrapped_char_stream = $get_wrapper->($char_stream);
619 $wrapped_char_stream->onerror ($char_onerror);
620
621 $return = $self->parse_char_stream ($wrapped_char_stream, @args);
622 };
623 return $return;
624 } # parse_byte_stream
625
626 ## NOTE: HTML5 spec says that the encoding layer MUST NOT strip BOM
627 ## and the HTML layer MUST ignore it. However, we does strip BOM in
628 ## the encoding layer and the HTML layer does not ignore any U+FEFF,
629 ## because the core part of our HTML parser expects a string of character,
630 ## not a string of bytes or code units or anything which might contain a BOM.
631 ## Therefore, any parser interface that accepts a string of bytes,
632 ## such as |parse_byte_string| in this module, must ensure that it does
633 ## strip the BOM and never strip any ZWNBSP.
634
635 sub parse_char_string ($$$;$$) {
636 #my ($self, $s, $doc, $onerror, $get_wrapper) = @_;
637 my $self = shift;
638 my $s = ref $_[0] ? $_[0] : \($_[0]);
639 require Whatpm::Charset::DecodeHandle;
640 my $input = Whatpm::Charset::DecodeHandle::CharString->new ($s);
641 return $self->parse_char_stream ($input, @_[1..$#_]);
642 } # parse_char_string
643 *parse_string = \&parse_char_string; ## NOTE: Alias for backward compatibility.
644
645 sub parse_char_stream ($$$;$$) {
646 my $self = ref $_[0] ? shift : shift->new;
647 my $input = $_[0];
648 $self->{document} = $_[1];
649 @{$self->{document}->child_nodes} = ();
650
651 ## NOTE: |set_inner_html| copies most of this method's code
652
653 ## Confidence: irrelevant.
654 $self->{confident} = 1 unless exists $self->{confident};
655
656 $self->{document}->input_encoding ($self->{input_encoding})
657 if defined $self->{input_encoding};
658 ## TODO: |{input_encoding}| is needless?
659
660 $self->{line_prev} = $self->{line} = 1;
661 $self->{column_prev} = -1;
662 $self->{column} = 0;
663 $self->{set_nc} = sub {
664 my $self = shift;
665
666 my $char = '';
667 if (defined $self->{next_nc}) {
668 $char = $self->{next_nc};
669 delete $self->{next_nc};
670 $self->{nc} = ord $char;
671 } else {
672 $self->{char_buffer} = '';
673 $self->{char_buffer_pos} = 0;
674
675 my $count = $input->manakai_read_until
676 ($self->{char_buffer}, qr/[^\x00\x0A\x0D]/, $self->{char_buffer_pos});
677 if ($count) {
678 $self->{line_prev} = $self->{line};
679 $self->{column_prev} = $self->{column};
680 $self->{column}++;
681 $self->{nc}
682 = ord substr ($self->{char_buffer}, $self->{char_buffer_pos}++, 1);
683 return;
684 }
685
686 if ($input->read ($char, 1)) {
687 $self->{nc} = ord $char;
688 } else {
689 $self->{nc} = -1;
690 return;
691 }
692 }
693
694 ($self->{line_prev}, $self->{column_prev})
695 = ($self->{line}, $self->{column});
696 $self->{column}++;
697
698 if ($self->{nc} == 0x000A) { # LF
699
700 $self->{line}++;
701 $self->{column} = 0;
702 } elsif ($self->{nc} == 0x000D) { # CR
703
704 ## TODO: support for abort/streaming
705 my $next = '';
706 if ($input->read ($next, 1) and $next ne "\x0A") {
707 $self->{next_nc} = $next;
708 }
709 $self->{nc} = 0x000A; # LF # MUST
710 $self->{line}++;
711 $self->{column} = 0;
712 } elsif ($self->{nc} == 0x0000) { # NULL
713
714 $self->{parse_error}->(level => $self->{level}->{must}, type => 'NULL');
715 $self->{nc} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
716 }
717 };
718
719 $self->{read_until} = sub {
720 #my ($scalar, $specials_range, $offset) = @_;
721 return 0 if defined $self->{next_nc};
722
723 my $pattern = qr/[^$_[1]\x00\x0A\x0D]/;
724 my $offset = $_[2] || 0;
725
726 if ($self->{char_buffer_pos} < length $self->{char_buffer}) {
727 pos ($self->{char_buffer}) = $self->{char_buffer_pos};
728 if ($self->{char_buffer} =~ /\G(?>$pattern)+/) {
729 substr ($_[0], $offset)
730 = substr ($self->{char_buffer}, $-[0], $+[0] - $-[0]);
731 my $count = $+[0] - $-[0];
732 if ($count) {
733 $self->{column} += $count;
734 $self->{char_buffer_pos} += $count;
735 $self->{line_prev} = $self->{line};
736 $self->{column_prev} = $self->{column} - 1;
737 $self->{nc} = -1;
738 }
739 return $count;
740 } else {
741 return 0;
742 }
743 } else {
744 my $count = $input->manakai_read_until ($_[0], $pattern, $_[2]);
745 if ($count) {
746 $self->{column} += $count;
747 $self->{line_prev} = $self->{line};
748 $self->{column_prev} = $self->{column} - 1;
749 $self->{nc} = -1;
750 }
751 return $count;
752 }
753 }; # $self->{read_until}
754
755 my $onerror = $_[2] || sub {
756 my (%opt) = @_;
757 my $line = $opt{token} ? $opt{token}->{line} : $opt{line};
758 my $column = $opt{token} ? $opt{token}->{column} : $opt{column};
759 warn "Parse error ($opt{type}) at line $line column $column\n";
760 };
761 $self->{parse_error} = sub {
762 $onerror->(line => $self->{line}, column => $self->{column}, @_);
763 };
764
765 my $char_onerror = sub {
766 my (undef, $type, %opt) = @_;
767 $self->{parse_error}->(level => $self->{level}->{must}, layer => 'encode',
768 line => $self->{line}, column => $self->{column} + 1,
769 %opt, type => $type);
770 }; # $char_onerror
771
772 if ($_[3]) {
773 $input = $_[3]->($input);
774 $input->onerror ($char_onerror);
775 } else {
776 $input->onerror ($char_onerror) unless defined $input->onerror;
777 }
778
779 $self->_initialize_tokenizer;
780 $self->_initialize_tree_constructor;
781 $self->_construct_tree;
782 $self->_terminate_tree_constructor;
783
784 delete $self->{parse_error}; # remove loop
785
786 return $self->{document};
787 } # parse_char_stream
788
789 sub new ($) {
790 my $class = shift;
791 my $self = bless {
792 level => {
793 must => 'm',
794 should => 's',
795 obc => 's', ## Obsolete but conforming, # XXX distinguish from "should"
796 warn => 'w',
797 info => 'i',
798 uncertain => 'u',
799 },
800 }, $class;
801 $self->{set_nc} = sub {
802 $self->{nc} = -1;
803 };
804 $self->{parse_error} = sub {
805 #
806 };
807 $self->{change_encoding} = sub {
808 # if ($_[0] is a supported encoding) {
809 # run "change the encoding" algorithm;
810 # throw Whatpm::HTML::RestartParser (charset => $new_encoding);
811 # }
812 };
813 $self->{application_cache_selection} = sub {
814 #
815 };
816 return $self;
817 } # new
818
819 ## Insertion modes
820
821 sub AFTER_HTML_IMS () { 0b100 }
822 sub HEAD_IMS () { 0b1000 }
823 sub BODY_IMS () { 0b10000 }
824 sub BODY_TABLE_IMS () { 0b100000 }
825 sub TABLE_IMS () { 0b1000000 }
826 sub ROW_IMS () { 0b10000000 }
827 sub BODY_AFTER_IMS () { 0b100000000 }
828 sub FRAME_IMS () { 0b1000000000 }
829 sub SELECT_IMS () { 0b10000000000 }
830 #sub IN_FOREIGN_CONTENT_IM () { 0b100000000000 } # see Whatpm::HTML::Tokenizer
831 ## NOTE: "in foreign content" insertion mode is special; it is combined
832 ## with the secondary insertion mode. In this parser, they are stored
833 ## together in the bit-or'ed form.
834 sub IN_CDATA_RCDATA_IM () { 0b1000000000000 }
835 ## NOTE: "in CDATA/RCDATA" insertion mode is also special; it is
836 ## combined with the original insertion mode. In thie parser,
837 ## they are stored together in the bit-or'ed form.
838
839 sub IM_MASK () { 0b11111111111 }
840
841 ## NOTE: "initial" and "before html" insertion modes have no constants.
842
843 ## NOTE: "after after body" insertion mode.
844 sub AFTER_HTML_BODY_IM () { AFTER_HTML_IMS | BODY_AFTER_IMS }
845
846 ## NOTE: "after after frameset" insertion mode.
847 sub AFTER_HTML_FRAMESET_IM () { AFTER_HTML_IMS | FRAME_IMS }
848
849 sub IN_HEAD_IM () { HEAD_IMS | 0b00 }
850 sub IN_HEAD_NOSCRIPT_IM () { HEAD_IMS | 0b01 }
851 sub AFTER_HEAD_IM () { HEAD_IMS | 0b10 }
852 sub BEFORE_HEAD_IM () { HEAD_IMS | 0b11 }
853 sub IN_BODY_IM () { BODY_IMS }
854 sub IN_CELL_IM () { BODY_IMS | BODY_TABLE_IMS | 0b01 }
855 sub IN_CAPTION_IM () { BODY_IMS | BODY_TABLE_IMS | 0b10 }
856 sub IN_ROW_IM () { TABLE_IMS | ROW_IMS | 0b01 }
857 sub IN_TABLE_BODY_IM () { TABLE_IMS | ROW_IMS | 0b10 }
858 sub IN_TABLE_IM () { TABLE_IMS }
859 sub AFTER_BODY_IM () { BODY_AFTER_IMS }
860 sub IN_FRAMESET_IM () { FRAME_IMS | 0b01 }
861 sub AFTER_FRAMESET_IM () { FRAME_IMS | 0b10 }
862 sub IN_SELECT_IM () { SELECT_IMS | 0b01 }
863 sub IN_SELECT_IN_TABLE_IM () { SELECT_IMS | 0b10 }
864 sub IN_COLUMN_GROUP_IM () { 0b10 }
865
866 sub _initialize_tree_constructor ($) {
867 my $self = shift;
868 ## NOTE: $self->{document} MUST be specified before this method is called
869 $self->{document}->strict_error_checking (0);
870 ## TODO: Turn mutation events off # MUST
871 ## TODO: Turn loose Document option (manakai extension) on
872 $self->{document}->manakai_is_html (1); # MUST
873 $self->{document}->set_user_data (manakai_source_line => 1);
874 $self->{document}->set_user_data (manakai_source_column => 1);
875
876 $self->{frameset_ok} = 1;
877 } # _initialize_tree_constructor
878
879 sub _terminate_tree_constructor ($) {
880 my $self = shift;
881 $self->{document}->strict_error_checking (1);
882 ## TODO: Turn mutation events on
883 } # _terminate_tree_constructor
884
885 ## ISSUE: Should append_child (for example) in script executed in tree construction stage fire mutation events?
886
887 { # tree construction stage
888 my $token;
889
890 sub _construct_tree ($) {
891 my ($self) = @_;
892
893 ## When an interactive UA render the $self->{document} available
894 ## to the user, or when it begin accepting user input, are
895 ## not defined.
896
897 $token = $self->_get_next_token;
898
899 undef $self->{form_element};
900 undef $self->{head_element};
901 undef $self->{head_element_inserted};
902 $self->{open_elements} = [];
903 undef $self->{inner_html_node};
904 undef $self->{ignore_newline};
905
906 ## NOTE: The "initial" insertion mode.
907 $self->_tree_construction_initial; # MUST
908
909 ## NOTE: The "before html" insertion mode.
910 $self->_tree_construction_root_element;
911 $self->{insertion_mode} = BEFORE_HEAD_IM;
912
913 ## NOTE: The "before head" insertion mode and so on.
914 $self->_tree_construction_main;
915 } # _construct_tree
916
917 sub _tree_construction_initial ($) {
918 my $self = shift;
919
920 ## NOTE: "initial" insertion mode
921
922 INITIAL: {
923 if ($token->{type} == DOCTYPE_TOKEN) {
924 ## NOTE: Conformance checkers MAY, instead of reporting "not
925 ## HTML5" error, switch to a conformance checking mode for
926 ## another language. (We don't support such mode switchings; it
927 ## is nonsense to do anything different from what browsers do.)
928 my $doctype_name = $token->{name};
929 $doctype_name = '' unless defined $doctype_name;
930 my $doctype = $self->{document}->create_document_type_definition
931 ($doctype_name);
932
933 $doctype_name =~ tr/A-Z/a-z/; # ASCII case-insensitive.
934 if ($doctype_name ne 'html') {
935
936 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not HTML5', token => $token);
937 } elsif (defined $token->{pubid}) {
938 ## Obsolete permitted DOCTYPEs (case-sensitive)
939 my $xsysid = {
940 '-//W3C//DTD HTML 4.0//EN' => 'http://www.w3.org/TR/REC-html40/strict.dtd',
941 '-//W3C//DTD HTML 4.01//EN' => 'http://www.w3.org/TR/html4/strict.dtd',
942 '-//W3C//DTD XHTML 1.0 Strict//EN' => 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd',
943 '-//W3C//DTD XHTML 1.1//EN' => 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd',
944 }->{$token->{pubid}};
945 if (defined $xsysid and
946 (not defined $token->{sysid} or $token->{sysid} eq $xsysid)) {
947
948 $self->{parse_error}->(level => $self->{level}->{must}, type => 'obs DOCTYPE', token => $token,
949 level => $self->{level}->{obc}); ## XXX error type
950 } else {
951
952 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not HTML5', token => $token);
953 }
954 } elsif (defined $token->{sysid}) {
955 if ($token->{sysid} eq 'about:legacy-compat') {
956 ## <!DOCTYPE HTML SYSTEM "about:legacy-compat">
957 $self->{parse_error}->(level => $self->{level}->{must}, type => 'XSLT-compat', token => $token,
958 level => $self->{level}->{should});
959 } else {
960 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not HTML5', token => $token);
961 }
962 } else { ## <!DOCTYPE HTML>
963
964 #
965 }
966
967 ## NOTE: Default value for both |public_id| and |system_id| attributes
968 ## are empty strings, so that we don't set any value in missing cases.
969 $doctype->public_id ($token->{pubid}) if defined $token->{pubid};
970 $doctype->system_id ($token->{sysid}) if defined $token->{sysid};
971
972 ## NOTE: Other DocumentType attributes are null or empty lists.
973 ## In Firefox3, |internalSubset| attribute is set to the empty
974 ## string, while |null| is an allowed value for the attribute
975 ## according to DOM3 Core.
976 $self->{document}->append_child ($doctype);
977
978 if ($token->{quirks} or $doctype_name ne 'html') {
979
980 $self->{document}->manakai_compat_mode ('quirks');
981 } elsif (defined $token->{pubid}) {
982 my $pubid = $token->{pubid};
983 $pubid =~ tr/a-z/A-Z/; ## ASCII case-insensitive.
984 my $prefix = [
985 "+//SILMARIL//DTD HTML PRO V0R11 19970101//",
986 "-//ADVASOFT LTD//DTD HTML 3.0 ASWEDIT + EXTENSIONS//",
987 "-//AS//DTD HTML 3.0 ASWEDIT + EXTENSIONS//",
988 "-//IETF//DTD HTML 2.0 LEVEL 1//",
989 "-//IETF//DTD HTML 2.0 LEVEL 2//",
990 "-//IETF//DTD HTML 2.0 STRICT LEVEL 1//",
991 "-//IETF//DTD HTML 2.0 STRICT LEVEL 2//",
992 "-//IETF//DTD HTML 2.0 STRICT//",
993 "-//IETF//DTD HTML 2.0//",
994 "-//IETF//DTD HTML 2.1E//",
995 "-//IETF//DTD HTML 3.0//",
996 "-//IETF//DTD HTML 3.2 FINAL//",
997 "-//IETF//DTD HTML 3.2//",
998 "-//IETF//DTD HTML 3//",
999 "-//IETF//DTD HTML LEVEL 0//",
1000 "-//IETF//DTD HTML LEVEL 1//",
1001 "-//IETF//DTD HTML LEVEL 2//",
1002 "-//IETF//DTD HTML LEVEL 3//",
1003 "-//IETF//DTD HTML STRICT LEVEL 0//",
1004 "-//IETF//DTD HTML STRICT LEVEL 1//",
1005 "-//IETF//DTD HTML STRICT LEVEL 2//",
1006 "-//IETF//DTD HTML STRICT LEVEL 3//",
1007 "-//IETF//DTD HTML STRICT//",
1008 "-//IETF//DTD HTML//",
1009 "-//METRIUS//DTD METRIUS PRESENTATIONAL//",
1010 "-//MICROSOFT//DTD INTERNET EXPLORER 2.0 HTML STRICT//",
1011 "-//MICROSOFT//DTD INTERNET EXPLORER 2.0 HTML//",
1012 "-//MICROSOFT//DTD INTERNET EXPLORER 2.0 TABLES//",
1013 "-//MICROSOFT//DTD INTERNET EXPLORER 3.0 HTML STRICT//",
1014 "-//MICROSOFT//DTD INTERNET EXPLORER 3.0 HTML//",
1015 "-//MICROSOFT//DTD INTERNET EXPLORER 3.0 TABLES//",
1016 "-//NETSCAPE COMM. CORP.//DTD HTML//",
1017 "-//NETSCAPE COMM. CORP.//DTD STRICT HTML//",
1018 "-//O'REILLY AND ASSOCIATES//DTD HTML 2.0//",
1019 "-//O'REILLY AND ASSOCIATES//DTD HTML EXTENDED 1.0//",
1020 "-//O'REILLY AND ASSOCIATES//DTD HTML EXTENDED RELAXED 1.0//",
1021 "-//SOFTQUAD SOFTWARE//DTD HOTMETAL PRO 6.0::19990601::EXTENSIONS TO HTML 4.0//",
1022 "-//SOFTQUAD//DTD HOTMETAL PRO 4.0::19971010::EXTENSIONS TO HTML 4.0//",
1023 "-//SPYGLASS//DTD HTML 2.0 EXTENDED//",
1024 "-//SQ//DTD HTML 2.0 HOTMETAL + EXTENSIONS//",
1025 "-//SUN MICROSYSTEMS CORP.//DTD HOTJAVA HTML//",
1026 "-//SUN MICROSYSTEMS CORP.//DTD HOTJAVA STRICT HTML//",
1027 "-//W3C//DTD HTML 3 1995-03-24//",
1028 "-//W3C//DTD HTML 3.2 DRAFT//",
1029 "-//W3C//DTD HTML 3.2 FINAL//",
1030 "-//W3C//DTD HTML 3.2//",
1031 "-//W3C//DTD HTML 3.2S DRAFT//",
1032 "-//W3C//DTD HTML 4.0 FRAMESET//",
1033 "-//W3C//DTD HTML 4.0 TRANSITIONAL//",
1034 "-//W3C//DTD HTML EXPERIMETNAL 19960712//",
1035 "-//W3C//DTD HTML EXPERIMENTAL 970421//",
1036 "-//W3C//DTD W3 HTML//",
1037 "-//W3O//DTD W3 HTML 3.0//",
1038 "-//WEBTECHS//DTD MOZILLA HTML 2.0//",
1039 "-//WEBTECHS//DTD MOZILLA HTML//",
1040 ]; # $prefix
1041 my $match;
1042 for (@$prefix) {
1043 if (substr ($prefix, 0, length $_) eq $_) {
1044 $match = 1;
1045 last;
1046 }
1047 }
1048 if ($match or
1049 $pubid eq "-//W3O//DTD W3 HTML STRICT 3.0//EN//" or
1050 $pubid eq "-/W3C/DTD HTML 4.0 TRANSITIONAL/EN" or
1051 $pubid eq "HTML") {
1052
1053 $self->{document}->manakai_compat_mode ('quirks');
1054 } elsif ($pubid =~ m[^-//W3C//DTD HTML 4.01 FRAMESET//] or
1055 $pubid =~ m[^-//W3C//DTD HTML 4.01 TRANSITIONAL//]) {
1056 if (defined $token->{sysid}) {
1057
1058 $self->{document}->manakai_compat_mode ('quirks');
1059 } else {
1060
1061 $self->{document}->manakai_compat_mode ('limited quirks');
1062 }
1063 } elsif ($pubid =~ m[^-//W3C//DTD XHTML 1.0 FRAMESET//] or
1064 $pubid =~ m[^-//W3C//DTD XHTML 1.0 TRANSITIONAL//]) {
1065
1066 $self->{document}->manakai_compat_mode ('limited quirks');
1067 } else {
1068
1069 }
1070 } else {
1071
1072 }
1073 if (defined $token->{sysid}) {
1074 my $sysid = $token->{sysid};
1075 $sysid =~ tr/A-Z/a-z/; ## ASCII case-insensitive.
1076 if ($sysid eq "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd") {
1077 ## NOTE: Ensure that |PUBLIC "(limited quirks)" "(quirks)"|
1078 ## is signaled as in quirks mode!
1079 $self->{document}->manakai_compat_mode ('quirks');
1080
1081 } else {
1082
1083 }
1084 } else {
1085
1086 }
1087
1088 ## Go to the "before html" insertion mode.
1089 $token = $self->_get_next_token;
1090 return;
1091 } elsif ({
1092 START_TAG_TOKEN, 1,
1093 END_TAG_TOKEN, 1,
1094 END_OF_FILE_TOKEN, 1,
1095 }->{$token->{type}}) {
1096
1097 $self->{parse_error}->(level => $self->{level}->{must}, type => 'no DOCTYPE', token => $token);
1098 $self->{document}->manakai_compat_mode ('quirks');
1099 ## Go to the "before html" insertion mode.
1100 ## reprocess
1101
1102 return;
1103 } elsif ($token->{type} == CHARACTER_TOKEN) {
1104 if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
1105 ## Ignore the token
1106
1107 unless (length $token->{data}) {
1108
1109 ## Stay in the insertion mode.
1110 $token = $self->_get_next_token;
1111 redo INITIAL;
1112 } else {
1113
1114 }
1115 } else {
1116
1117 }
1118
1119 $self->{parse_error}->(level => $self->{level}->{must}, type => 'no DOCTYPE', token => $token);
1120 $self->{document}->manakai_compat_mode ('quirks');
1121 ## Go to the "before html" insertion mode.
1122 ## reprocess
1123 return;
1124 } elsif ($token->{type} == COMMENT_TOKEN) {
1125
1126 my $comment = $self->{document}->create_comment ($token->{data});
1127 $self->{document}->append_child ($comment);
1128
1129 ## Stay in the insertion mode.
1130 $token = $self->_get_next_token;
1131 redo INITIAL;
1132 } else {
1133 die "$0: $token->{type}: Unknown token type";
1134 }
1135 } # INITIAL
1136
1137 die "$0: _tree_construction_initial: This should be never reached";
1138 } # _tree_construction_initial
1139
1140 sub _tree_construction_root_element ($) {
1141 my $self = shift;
1142
1143 ## NOTE: "before html" insertion mode.
1144
1145 B: {
1146 if ($token->{type} == DOCTYPE_TOKEN) {
1147
1148 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in html:#DOCTYPE', token => $token);
1149 ## Ignore the token
1150 ## Stay in the insertion mode.
1151 $token = $self->_get_next_token;
1152 redo B;
1153 } elsif ($token->{type} == COMMENT_TOKEN) {
1154
1155 my $comment = $self->{document}->create_comment ($token->{data});
1156 $self->{document}->append_child ($comment);
1157 ## Stay in the insertion mode.
1158 $token = $self->_get_next_token;
1159 redo B;
1160 } elsif ($token->{type} == CHARACTER_TOKEN) {
1161 if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
1162 ## Ignore the token.
1163
1164 unless (length $token->{data}) {
1165
1166 ## Stay in the insertion mode.
1167 $token = $self->_get_next_token;
1168 redo B;
1169 } else {
1170
1171 }
1172 } else {
1173
1174 }
1175
1176 $self->{application_cache_selection}->(undef);
1177
1178 #
1179 } elsif ($token->{type} == START_TAG_TOKEN) {
1180 if ($token->{tag_name} eq 'html') {
1181 my $root_element;
1182
1183 $root_element = $self->{document}->create_element_ns
1184 ($HTML_NS, [undef, $token->{tag_name}]);
1185
1186 for my $attr_name (keys %{ $token->{attributes}}) {
1187 my $attr_t = $token->{attributes}->{$attr_name};
1188 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
1189 $attr->value ($attr_t->{value});
1190 $attr->set_user_data (manakai_source_line => $attr_t->{line});
1191 $attr->set_user_data (manakai_source_column => $attr_t->{column});
1192 $root_element->set_attribute_node_ns ($attr);
1193 }
1194
1195 $root_element->set_user_data (manakai_source_line => $token->{line})
1196 if defined $token->{line};
1197 $root_element->set_user_data (manakai_source_column => $token->{column})
1198 if defined $token->{column};
1199
1200 $self->{document}->append_child ($root_element);
1201 push @{$self->{open_elements}},
1202 [$root_element, $el_category->{html}];
1203
1204 if ($token->{attributes}->{manifest}) {
1205
1206 $self->{application_cache_selection}
1207 ->($token->{attributes}->{manifest}->{value});
1208 ## ISSUE: Spec is unclear on relative references.
1209 ## According to Hixie (#whatwg 2008-03-19), it should be
1210 ## resolved against the base URI of the document in HTML
1211 ## or xml:base of the element in XHTML.
1212 } else {
1213
1214 $self->{application_cache_selection}->(undef);
1215 }
1216
1217
1218
1219 $token = $self->_get_next_token;
1220 return; ## Go to the "before head" insertion mode.
1221 } else {
1222
1223 #
1224 }
1225 } elsif ({
1226 END_TAG_TOKEN, 1,
1227 END_OF_FILE_TOKEN, 1,
1228 }->{$token->{type}}) {
1229
1230 #
1231 } else {
1232 die "$0: $token->{type}: Unknown token type";
1233 }
1234
1235 my $root_element;
1236
1237 $root_element = $self->{document}->create_element_ns
1238 ($HTML_NS, [undef, 'html']);
1239
1240 $root_element->set_user_data (manakai_source_line => $token->{line})
1241 if defined $token->{line};
1242 $root_element->set_user_data (manakai_source_column => $token->{column})
1243 if defined $token->{column};
1244
1245 $self->{document}->append_child ($root_element);
1246 push @{$self->{open_elements}}, [$root_element, $el_category->{html}];
1247
1248 $self->{application_cache_selection}->(undef);
1249
1250 ## NOTE: Reprocess the token.
1251
1252 return; ## Go to the "before head" insertion mode.
1253 } # B
1254
1255 die "$0: _tree_construction_root_element: This should never be reached";
1256 } # _tree_construction_root_element
1257
1258 sub _reset_insertion_mode ($) {
1259 my $self = shift;
1260
1261 ## Step 1
1262 my $last;
1263
1264 ## Step 2
1265 my $i = -1;
1266 my $node = $self->{open_elements}->[$i];
1267
1268 ## Step 3
1269 S3: {
1270 if ($self->{open_elements}->[0]->[0] eq $node->[0]) {
1271 $last = 1;
1272 if (defined $self->{inner_html_node}) {
1273
1274 $node = $self->{inner_html_node};
1275 } else {
1276 die "_reset_insertion_mode: t27";
1277 }
1278 }
1279
1280 ## Step 4..14
1281 my $new_mode;
1282 if ($node->[1] & FOREIGN_EL) {
1283
1284 ## NOTE: Strictly spaking, the line below only applies to MathML and
1285 ## SVG elements. Currently the HTML syntax supports only MathML and
1286 ## SVG elements as foreigners.
1287 $new_mode = IN_BODY_IM | IN_FOREIGN_CONTENT_IM;
1288 } elsif ($node->[1] == TABLE_CELL_EL) {
1289 if ($last) {
1290
1291 #
1292 } else {
1293
1294 $new_mode = IN_CELL_IM;
1295 }
1296 } else {
1297
1298 $new_mode = {
1299 select => IN_SELECT_IM,
1300 ## NOTE: |option| and |optgroup| do not set
1301 ## insertion mode to "in select" by themselves.
1302 tr => IN_ROW_IM,
1303 tbody => IN_TABLE_BODY_IM,
1304 thead => IN_TABLE_BODY_IM,
1305 tfoot => IN_TABLE_BODY_IM,
1306 caption => IN_CAPTION_IM,
1307 colgroup => IN_COLUMN_GROUP_IM,
1308 table => IN_TABLE_IM,
1309 head => IN_BODY_IM, # not in head!
1310 body => IN_BODY_IM,
1311 frameset => IN_FRAMESET_IM,
1312 }->{$node->[0]->manakai_local_name};
1313 }
1314 $self->{insertion_mode} = $new_mode and return if defined $new_mode;
1315
1316 ## Step 15
1317 if ($node->[1] == HTML_EL) {
1318 unless (defined $self->{head_element}) {
1319
1320 $self->{insertion_mode} = BEFORE_HEAD_IM;
1321 } else {
1322 ## ISSUE: Can this state be reached?
1323
1324 $self->{insertion_mode} = AFTER_HEAD_IM;
1325 }
1326 return;
1327 } else {
1328
1329 }
1330
1331 ## Step 16
1332 $self->{insertion_mode} = IN_BODY_IM and return if $last;
1333
1334 ## Step 17
1335 $i--;
1336 $node = $self->{open_elements}->[$i];
1337
1338 ## Step 18
1339 redo S3;
1340 } # S3
1341
1342 die "$0: _reset_insertion_mode: This line should never be reached";
1343 } # _reset_insertion_mode
1344
1345 sub _tree_construction_main ($) {
1346 my $self = shift;
1347
1348 my $active_formatting_elements = [];
1349
1350 my $reconstruct_active_formatting_elements = sub { # MUST
1351 my $insert = shift;
1352
1353 ## Step 1
1354 return unless @$active_formatting_elements;
1355
1356 ## Step 3
1357 my $i = -1;
1358 my $entry = $active_formatting_elements->[$i];
1359
1360 ## Step 2
1361 return if $entry->[0] eq '#marker';
1362 for (@{$self->{open_elements}}) {
1363 if ($entry->[0] eq $_->[0]) {
1364
1365 return;
1366 }
1367 }
1368
1369 S4: {
1370 ## Step 4
1371 last S4 if $active_formatting_elements->[0]->[0] eq $entry->[0];
1372
1373 ## Step 5
1374 $i--;
1375 $entry = $active_formatting_elements->[$i];
1376
1377 ## Step 6
1378 if ($entry->[0] eq '#marker') {
1379
1380 #
1381 } else {
1382 my $in_open_elements;
1383 OE: for (@{$self->{open_elements}}) {
1384 if ($entry->[0] eq $_->[0]) {
1385
1386 $in_open_elements = 1;
1387 last OE;
1388 }
1389 }
1390 if ($in_open_elements) {
1391
1392 #
1393 } else {
1394 ## NOTE: <!DOCTYPE HTML><p><b><i><u></p> <p>X
1395
1396 redo S4;
1397 }
1398 }
1399
1400 ## Step 7
1401 $i++;
1402 $entry = $active_formatting_elements->[$i];
1403 } # S4
1404
1405 S7: {
1406 ## Step 8
1407 my $clone = [$entry->[0]->clone_node (0), $entry->[1]];
1408
1409 ## Step 9
1410 $insert->($clone->[0]);
1411 push @{$self->{open_elements}}, $clone;
1412
1413 ## Step 10
1414 $active_formatting_elements->[$i] = $self->{open_elements}->[-1];
1415
1416 ## Step 11
1417 unless ($clone->[0] eq $active_formatting_elements->[-1]->[0]) {
1418
1419 ## Step 7'
1420 $i++;
1421 $entry = $active_formatting_elements->[$i];
1422
1423 redo S7;
1424 }
1425
1426
1427 } # S7
1428 }; # $reconstruct_active_formatting_elements
1429
1430 my $clear_up_to_marker = sub {
1431 for (reverse 0..$#$active_formatting_elements) {
1432 if ($active_formatting_elements->[$_]->[0] eq '#marker') {
1433
1434 splice @$active_formatting_elements, $_;
1435 return;
1436 }
1437 }
1438
1439
1440 }; # $clear_up_to_marker
1441
1442 my $insert;
1443
1444 my $parse_rcdata = sub ($) {
1445 my ($content_model_flag) = @_;
1446
1447 ## Step 1
1448 my $start_tag_name = $token->{tag_name};
1449
1450 {
1451 my $el;
1452
1453 $el = $self->{document}->create_element_ns
1454 ($HTML_NS, [undef, $token->{tag_name}]);
1455
1456 for my $attr_name (keys %{ $token->{attributes}}) {
1457 my $attr_t = $token->{attributes}->{$attr_name};
1458 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
1459 $attr->value ($attr_t->{value});
1460 $attr->set_user_data (manakai_source_line => $attr_t->{line});
1461 $attr->set_user_data (manakai_source_column => $attr_t->{column});
1462 $el->set_attribute_node_ns ($attr);
1463 }
1464
1465 $el->set_user_data (manakai_source_line => $token->{line})
1466 if defined $token->{line};
1467 $el->set_user_data (manakai_source_column => $token->{column})
1468 if defined $token->{column};
1469
1470 $insert->($el);
1471 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
1472 }
1473
1474
1475 ## Step 2
1476 $self->{content_model} = $content_model_flag; # CDATA or RCDATA
1477 delete $self->{escape}; # MUST
1478
1479 ## Step 3, 4
1480 $self->{insertion_mode} |= IN_CDATA_RCDATA_IM;
1481
1482
1483 $token = $self->_get_next_token;
1484 }; # $parse_rcdata
1485
1486 my $script_start_tag = sub () {
1487 ## Step 1
1488 my $script_el;
1489
1490 $script_el = $self->{document}->create_element_ns
1491 ($HTML_NS, [undef, 'script']);
1492
1493 for my $attr_name (keys %{ $token->{attributes}}) {
1494 my $attr_t = $token->{attributes}->{$attr_name};
1495 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
1496 $attr->value ($attr_t->{value});
1497 $attr->set_user_data (manakai_source_line => $attr_t->{line});
1498 $attr->set_user_data (manakai_source_column => $attr_t->{column});
1499 $script_el->set_attribute_node_ns ($attr);
1500 }
1501
1502 $script_el->set_user_data (manakai_source_line => $token->{line})
1503 if defined $token->{line};
1504 $script_el->set_user_data (manakai_source_column => $token->{column})
1505 if defined $token->{column};
1506
1507
1508 ## Step 2
1509 ## TODO: mark as "parser-inserted"
1510
1511 ## Step 3
1512 ## TODO: Mark as "already executed", if ...
1513
1514 ## Step 4 (HTML5 revision 2702)
1515 $insert->($script_el);
1516 push @{$self->{open_elements}}, [$script_el, $el_category->{script}];
1517
1518 ## Step 5
1519 $self->{content_model} = CDATA_CONTENT_MODEL;
1520 delete $self->{escape}; # MUST
1521
1522 ## Step 6-7
1523 $self->{insertion_mode} |= IN_CDATA_RCDATA_IM;
1524
1525
1526 $token = $self->_get_next_token;
1527 }; # $script_start_tag
1528
1529 ## NOTE: $open_tables->[-1]->[0] is the "current table" element node.
1530 ## NOTE: $open_tables->[-1]->[1] is the "tainted" flag (OBSOLETE; unused).
1531 ## NOTE: $open_tables->[-1]->[2] is set false when non-Text node inserted.
1532 my $open_tables = [[$self->{open_elements}->[0]->[0]]];
1533
1534 my $formatting_end_tag = sub {
1535 my $end_tag_token = shift;
1536 my $tag_name = $end_tag_token->{tag_name};
1537
1538 ## NOTE: The adoption agency algorithm (AAA).
1539
1540 FET: {
1541 ## Step 1
1542 my $formatting_element;
1543 my $formatting_element_i_in_active;
1544 AFE: for (reverse 0..$#$active_formatting_elements) {
1545 if ($active_formatting_elements->[$_]->[0] eq '#marker') {
1546
1547 last AFE;
1548 } elsif ($active_formatting_elements->[$_]->[0]->manakai_local_name
1549 eq $tag_name) {
1550
1551 $formatting_element = $active_formatting_elements->[$_];
1552 $formatting_element_i_in_active = $_;
1553 last AFE;
1554 }
1555 } # AFE
1556 unless (defined $formatting_element) {
1557
1558 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag', text => $tag_name, token => $end_tag_token);
1559 ## Ignore the token
1560 $token = $self->_get_next_token;
1561 return;
1562 }
1563 ## has an element in scope
1564 my $in_scope = 1;
1565 my $formatting_element_i_in_open;
1566 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
1567 my $node = $self->{open_elements}->[$_];
1568 if ($node->[0] eq $formatting_element->[0]) {
1569 if ($in_scope) {
1570
1571 $formatting_element_i_in_open = $_;
1572 last INSCOPE;
1573 } else { # in open elements but not in scope
1574
1575 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
1576 text => $token->{tag_name},
1577 token => $end_tag_token);
1578 ## Ignore the token
1579 $token = $self->_get_next_token;
1580 return;
1581 }
1582 } elsif ($node->[1] & SCOPING_EL) {
1583
1584 $in_scope = 0;
1585 }
1586 } # INSCOPE
1587 unless (defined $formatting_element_i_in_open) {
1588
1589 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
1590 text => $token->{tag_name},
1591 token => $end_tag_token);
1592 pop @$active_formatting_elements; # $formatting_element
1593 $token = $self->_get_next_token; ## TODO: ok?
1594 return;
1595 }
1596 if (not $self->{open_elements}->[-1]->[0] eq $formatting_element->[0]) {
1597
1598 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed',
1599 text => $self->{open_elements}->[-1]->[0]
1600 ->manakai_local_name,
1601 token => $end_tag_token);
1602 }
1603
1604 ## Step 2
1605 my $furthest_block;
1606 my $furthest_block_i_in_open;
1607 OE: for (reverse 0..$#{$self->{open_elements}}) {
1608 my $node = $self->{open_elements}->[$_];
1609 if (not ($node->[1] & FORMATTING_EL) and
1610 #not $phrasing_category->{$node->[1]} and
1611 ($node->[1] & SPECIAL_EL or
1612 $node->[1] & SCOPING_EL)) { ## Scoping is redundant, maybe
1613
1614 $furthest_block = $node;
1615 $furthest_block_i_in_open = $_;
1616 ## NOTE: The topmost (eldest) node.
1617 } elsif ($node->[0] eq $formatting_element->[0]) {
1618
1619 last OE;
1620 }
1621 } # OE
1622
1623 ## Step 3
1624 unless (defined $furthest_block) { # MUST
1625
1626 splice @{$self->{open_elements}}, $formatting_element_i_in_open;
1627 splice @$active_formatting_elements, $formatting_element_i_in_active, 1;
1628 $token = $self->_get_next_token;
1629 return;
1630 }
1631
1632 ## Step 4
1633 my $common_ancestor_node = $self->{open_elements}->[$formatting_element_i_in_open - 1];
1634
1635 ## Step 5
1636 my $furthest_block_parent = $furthest_block->[0]->parent_node;
1637 if (defined $furthest_block_parent) {
1638
1639 $furthest_block_parent->remove_child ($furthest_block->[0]);
1640 }
1641
1642 ## Step 6
1643 my $bookmark_prev_el
1644 = $active_formatting_elements->[$formatting_element_i_in_active - 1]
1645 ->[0];
1646
1647 ## Step 7
1648 my $node = $furthest_block;
1649 my $node_i_in_open = $furthest_block_i_in_open;
1650 my $last_node = $furthest_block;
1651 S7: {
1652 ## Step 1
1653 $node_i_in_open--;
1654 $node = $self->{open_elements}->[$node_i_in_open];
1655
1656 ## Step 2
1657 my $node_i_in_active;
1658 S7S2: {
1659 for (reverse 0..$#$active_formatting_elements) {
1660 if ($active_formatting_elements->[$_]->[0] eq $node->[0]) {
1661
1662 $node_i_in_active = $_;
1663 last S7S2;
1664 }
1665 }
1666 splice @{$self->{open_elements}}, $node_i_in_open, 1;
1667 redo S7;
1668 } # S7S2
1669
1670 ## Step 3
1671 last S7 if $node->[0] eq $formatting_element->[0];
1672
1673 ## Step 4
1674 if ($last_node->[0] eq $furthest_block->[0]) {
1675
1676 $bookmark_prev_el = $node->[0];
1677 }
1678
1679 ## Step 5
1680 if ($node->[0]->has_child_nodes ()) {
1681
1682 my $clone = [$node->[0]->clone_node (0), $node->[1]];
1683 $active_formatting_elements->[$node_i_in_active] = $clone;
1684 $self->{open_elements}->[$node_i_in_open] = $clone;
1685 $node = $clone;
1686 }
1687
1688 ## Step 6
1689 $node->[0]->append_child ($last_node->[0]);
1690
1691 ## Step 7
1692 $last_node = $node;
1693
1694 ## Step 8
1695 redo S7;
1696 } # S7
1697
1698 ## Step 8
1699 if ($common_ancestor_node->[1] & TABLE_ROWS_EL) {
1700 ## Foster parenting.
1701 my $foster_parent_element;
1702 my $next_sibling;
1703 OE: for (reverse 0..$#{$self->{open_elements}}) {
1704 if ($self->{open_elements}->[$_]->[1] == TABLE_EL) {
1705
1706 $foster_parent_element = $self->{open_elements}->[$_ - 1]->[0];
1707 $next_sibling = $self->{open_elements}->[$_]->[0];
1708 undef $next_sibling
1709 unless $next_sibling->parent_node eq $foster_parent_element;
1710 last OE;
1711 }
1712 } # OE
1713 $foster_parent_element ||= $self->{open_elements}->[0]->[0];
1714
1715 $foster_parent_element->insert_before ($last_node->[0], $next_sibling);
1716 $open_tables->[-1]->[1] = 1; # tainted
1717 } else {
1718
1719 $common_ancestor_node->[0]->append_child ($last_node->[0]);
1720 }
1721
1722 ## Step 9
1723 my $clone = [$formatting_element->[0]->clone_node (0),
1724 $formatting_element->[1]];
1725
1726 ## Step 10
1727 my @cn = @{$furthest_block->[0]->child_nodes};
1728 $clone->[0]->append_child ($_) for @cn;
1729
1730 ## Step 11
1731 $furthest_block->[0]->append_child ($clone->[0]);
1732
1733 ## Step 12
1734 my $i;
1735 AFE: for (reverse 0..$#$active_formatting_elements) {
1736 if ($active_formatting_elements->[$_]->[0] eq $formatting_element->[0]) {
1737
1738 splice @$active_formatting_elements, $_, 1;
1739 $i-- and last AFE if defined $i;
1740 } elsif ($active_formatting_elements->[$_]->[0] eq $bookmark_prev_el) {
1741
1742 $i = $_;
1743 }
1744 } # AFE
1745 splice @$active_formatting_elements, $i + 1, 0, $clone;
1746
1747 ## Step 13
1748 undef $i;
1749 OE: for (reverse 0..$#{$self->{open_elements}}) {
1750 if ($self->{open_elements}->[$_]->[0] eq $formatting_element->[0]) {
1751
1752 splice @{$self->{open_elements}}, $_, 1;
1753 $i-- and last OE if defined $i;
1754 } elsif ($self->{open_elements}->[$_]->[0] eq $furthest_block->[0]) {
1755
1756 $i = $_;
1757 }
1758 } # OE
1759 splice @{$self->{open_elements}}, $i + 1, 0, $clone;
1760
1761 ## Step 14
1762 redo FET;
1763 } # FET
1764 }; # $formatting_end_tag
1765
1766 $insert = my $insert_to_current = sub {
1767 $self->{open_elements}->[-1]->[0]->append_child ($_[0]);
1768 }; # $insert_to_current
1769
1770 ## Foster parenting. Note that there are three "foster parenting"
1771 ## code in the parser: for elements (this one), for texts, and for
1772 ## elements in the AAA code.
1773 my $insert_to_foster = sub {
1774 my $child = shift;
1775 if ($self->{open_elements}->[-1]->[1] & TABLE_ROWS_EL) {
1776 # MUST
1777 my $foster_parent_element;
1778 my $next_sibling;
1779 OE: for (reverse 0..$#{$self->{open_elements}}) {
1780 if ($self->{open_elements}->[$_]->[1] == TABLE_EL) {
1781
1782 $foster_parent_element = $self->{open_elements}->[$_ - 1]->[0];
1783 $next_sibling = $self->{open_elements}->[$_]->[0];
1784 undef $next_sibling
1785 unless $next_sibling->parent_node eq $foster_parent_element;
1786 last OE;
1787 }
1788 } # OE
1789 $foster_parent_element ||= $self->{open_elements}->[0]->[0];
1790
1791 $foster_parent_element->insert_before ($child, $next_sibling);
1792 $open_tables->[-1]->[1] = 1; # tainted
1793 } else {
1794
1795 $self->{open_elements}->[-1]->[0]->append_child ($child);
1796 }
1797 }; # $insert_to_foster
1798
1799 ## NOTE: Insert a character (MUST): When a character is inserted, if
1800 ## the last node that was inserted by the parser is a Text node and
1801 ## the character has to be inserted after that node, then the
1802 ## character is appended to the Text node. However, if any other
1803 ## node is inserted by the parser, then a new Text node is created
1804 ## and the character is appended as that Text node. If I'm not
1805 ## wrong, for a parser with scripting disabled, there are only two
1806 ## cases where this occurs. One is the case where an element node
1807 ## is inserted to the |head| element. This is covered by using the
1808 ## |$self->{head_element_inserted}| flag. Another is the case where
1809 ## an element or comment is inserted into the |table| subtree while
1810 ## foster parenting happens. This is covered by using the [2] flag
1811 ## of the |$open_tables| structure. All other cases are handled
1812 ## simply by calling |manakai_append_text| method.
1813
1814 ## TODO: |<body><script>document.write("a<br>");
1815 ## document.body.removeChild (document.body.lastChild);
1816 ## document.write ("b")</script>|
1817
1818 B: while (1) {
1819
1820 ## The "in table text" insertion mode.
1821 if ($self->{insertion_mode} & TABLE_IMS and
1822 not $self->{insertion_mode} & IN_FOREIGN_CONTENT_IM and
1823 not $self->{insertion_mode} & IN_CDATA_RCDATA_IM) {
1824 C: {
1825 my $s;
1826 if ($token->{type} == CHARACTER_TOKEN) {
1827
1828 $self->{pending_chars} ||= [];
1829 push @{$self->{pending_chars}}, $token;
1830 $token = $self->_get_next_token;
1831 next B;
1832 } else {
1833 if ($self->{pending_chars}) {
1834 $s = join '', map { $_->{data} } @{$self->{pending_chars}};
1835 delete $self->{pending_chars};
1836 if ($s =~ /[^\x09\x0A\x0C\x0D\x20]/) {
1837
1838 #
1839 } else {
1840
1841 #$self->{open_elements}->[-1]->[0]->manakai_append_text ($s);
1842 $self->{open_elements}->[-1]->[0]->append_child
1843 ($self->{document}->create_text_node ($s));
1844 last C;
1845 }
1846 } else {
1847
1848 last C;
1849 }
1850 }
1851
1852 ## Foster parenting.
1853 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in table:#text', token => $token);
1854
1855 ## NOTE: As if in body, but insert into the foster parent element.
1856 $reconstruct_active_formatting_elements->($insert_to_foster);
1857
1858 if ($self->{open_elements}->[-1]->[1] & TABLE_ROWS_EL) {
1859 # MUST
1860 my $foster_parent_element;
1861 my $next_sibling;
1862 OE: for (reverse 0..$#{$self->{open_elements}}) {
1863 if ($self->{open_elements}->[$_]->[1] == TABLE_EL) {
1864
1865 $foster_parent_element = $self->{open_elements}->[$_ - 1]->[0];
1866 $next_sibling = $self->{open_elements}->[$_]->[0];
1867 undef $next_sibling
1868 unless $next_sibling->parent_node eq $foster_parent_element;
1869 last OE;
1870 }
1871 } # OE
1872 $foster_parent_element ||= $self->{open_elements}->[0]->[0];
1873
1874
1875 $foster_parent_element->insert_before
1876 ($self->{document}->create_text_node ($s), $next_sibling);
1877
1878 $open_tables->[-1]->[1] = 1; # tainted
1879 $open_tables->[-1]->[2] = 1; # ~node inserted
1880 } else {
1881 ## NOTE: Fragment case or in a foster parent'ed element
1882 ## (e.g. |<table><span>a|). In fragment case, whether the
1883 ## character is appended to existing node or a new node is
1884 ## created is irrelevant, since the foster parent'ed nodes
1885 ## are discarded and fragment parsing does not invoke any
1886 ## script.
1887
1888 $self->{open_elements}->[-1]->[0]->manakai_append_text ($s);
1889 }
1890 } # C
1891 } # TABLE_IMS
1892
1893 if ($token->{type} == DOCTYPE_TOKEN) {
1894
1895 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in html:#DOCTYPE', token => $token);
1896 ## Ignore the token
1897 ## Stay in the phase
1898 $token = $self->_get_next_token;
1899 next B;
1900 } elsif ($token->{type} == START_TAG_TOKEN and
1901 $token->{tag_name} eq 'html') {
1902 if ($self->{insertion_mode} == AFTER_HTML_BODY_IM) {
1903
1904 $self->{parse_error}->(level => $self->{level}->{must}, type => 'after html', text => 'html', token => $token);
1905 $self->{insertion_mode} = AFTER_BODY_IM;
1906 } elsif ($self->{insertion_mode} == AFTER_HTML_FRAMESET_IM) {
1907
1908 $self->{parse_error}->(level => $self->{level}->{must}, type => 'after html', text => 'html', token => $token);
1909 $self->{insertion_mode} = AFTER_FRAMESET_IM;
1910 } else {
1911
1912 }
1913
1914
1915 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not first start tag', token => $token);
1916 my $top_el = $self->{open_elements}->[0]->[0];
1917 for my $attr_name (keys %{$token->{attributes}}) {
1918 unless ($top_el->has_attribute_ns (undef, $attr_name)) {
1919
1920 $top_el->set_attribute_ns
1921 (undef, [undef, $attr_name],
1922 $token->{attributes}->{$attr_name}->{value});
1923 }
1924 }
1925
1926 $token = $self->_get_next_token;
1927 next B;
1928 } elsif ($token->{type} == COMMENT_TOKEN) {
1929 my $comment = $self->{document}->create_comment ($token->{data});
1930 if ($self->{insertion_mode} & AFTER_HTML_IMS) {
1931
1932 $self->{document}->append_child ($comment);
1933 } elsif ($self->{insertion_mode} == AFTER_BODY_IM) {
1934
1935 $self->{open_elements}->[0]->[0]->append_child ($comment);
1936 } else {
1937
1938 $self->{open_elements}->[-1]->[0]->append_child ($comment);
1939 $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
1940 }
1941 $token = $self->_get_next_token;
1942 next B;
1943 } elsif ($self->{insertion_mode} & IN_CDATA_RCDATA_IM) {
1944 if ($token->{type} == CHARACTER_TOKEN) {
1945 $token->{data} =~ s/^\x0A// if $self->{ignore_newline};
1946 delete $self->{ignore_newline};
1947
1948 if (length $token->{data}) {
1949
1950 $self->{open_elements}->[-1]->[0]->manakai_append_text
1951 ($token->{data});
1952 } else {
1953
1954 }
1955 $token = $self->_get_next_token;
1956 next B;
1957 } elsif ($token->{type} == END_TAG_TOKEN) {
1958 delete $self->{ignore_newline};
1959
1960 if ($token->{tag_name} eq 'script') {
1961
1962
1963 ## Para 1-2
1964 my $script = pop @{$self->{open_elements}};
1965
1966 ## Para 3
1967 $self->{insertion_mode} &= ~ IN_CDATA_RCDATA_IM;
1968
1969 ## Para 4
1970 ## TODO: $old_insertion_point = $current_insertion_point;
1971 ## TODO: $current_insertion_point = just before $self->{nc};
1972
1973 ## Para 5
1974 ## TODO: Run the $script->[0].
1975
1976 ## Para 6
1977 ## TODO: $current_insertion_point = $old_insertion_point;
1978
1979 ## Para 7
1980 ## TODO: if ($pending_external_script) {
1981 ## TODO: ...
1982 ## TODO: }
1983
1984 $token = $self->_get_next_token;
1985 next B;
1986 } else {
1987
1988
1989 pop @{$self->{open_elements}};
1990
1991 $self->{insertion_mode} &= ~ IN_CDATA_RCDATA_IM;
1992 $token = $self->_get_next_token;
1993 next B;
1994 }
1995 } elsif ($token->{type} == END_OF_FILE_TOKEN) {
1996 delete $self->{ignore_newline};
1997
1998
1999 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed',
2000 text => $self->{open_elements}->[-1]->[0]
2001 ->manakai_local_name,
2002 token => $token);
2003
2004 #if ($self->{open_elements}->[-1]->[1] == SCRIPT_EL) {
2005 # ## TODO: Mark as "already executed"
2006 #}
2007
2008 pop @{$self->{open_elements}};
2009
2010 $self->{insertion_mode} &= ~ IN_CDATA_RCDATA_IM;
2011 ## Reprocess.
2012 next B;
2013 } else {
2014 die "$0: $token->{type}: In CDATA/RCDATA: Unknown token type";
2015 }
2016 } elsif ($self->{insertion_mode} & IN_FOREIGN_CONTENT_IM) {
2017 if ($token->{type} == CHARACTER_TOKEN) {
2018
2019
2020 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
2021
2022 if ($token->{data} =~ /[^\x09\x0A\x0C\x0D\x20]/) {
2023 delete $self->{frameset_ok};
2024 }
2025
2026 $token = $self->_get_next_token;
2027 next B;
2028 } elsif ($token->{type} == START_TAG_TOKEN) {
2029 if ((not {mglyph => 1, malignmark => 1}->{$token->{tag_name}} and
2030 $self->{open_elements}->[-1]->[1] & FOREIGN_FLOW_CONTENT_EL) or
2031 not ($self->{open_elements}->[-1]->[1] & FOREIGN_EL) or
2032 ($token->{tag_name} eq 'svg' and
2033 $self->{open_elements}->[-1]->[1] == MML_AXML_EL)) {
2034 ## NOTE: "using the rules for secondary insertion mode"then"continue"
2035
2036 #
2037 } elsif ({
2038 b => 1, big => 1, blockquote => 1, body => 1, br => 1,
2039 center => 1, code => 1, dd => 1, div => 1, dl => 1, dt => 1,
2040 em => 1, embed => 1, h1 => 1, h2 => 1, h3 => 1,
2041 h4 => 1, h5 => 1, h6 => 1, head => 1, hr => 1, i => 1,
2042 img => 1, li => 1, listing => 1, menu => 1, meta => 1,
2043 nobr => 1, ol => 1, p => 1, pre => 1, ruby => 1, s => 1,
2044 small => 1, span => 1, strong => 1, strike => 1, sub => 1,
2045 sup => 1, table => 1, tt => 1, u => 1, ul => 1, var => 1,
2046 }->{$token->{tag_name}} or
2047 ($token->{tag_name} eq 'font' and
2048 ($token->{attributes}->{color} or
2049 $token->{attributes}->{face} or
2050 $token->{attributes}->{size}))) {
2051
2052 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed',
2053 text => $self->{open_elements}->[-1]->[0]
2054 ->manakai_local_name,
2055 token => $token);
2056
2057 pop @{$self->{open_elements}}
2058 while $self->{open_elements}->[-1]->[1] & FOREIGN_EL;
2059
2060 $self->{insertion_mode} &= ~ IN_FOREIGN_CONTENT_IM;
2061 ## Reprocess.
2062 next B;
2063 } else {
2064 my $nsuri = $self->{open_elements}->[-1]->[0]->namespace_uri;
2065 my $tag_name = $token->{tag_name};
2066 if ($nsuri eq $SVG_NS) {
2067 $tag_name = {
2068 altglyph => 'altGlyph',
2069 altglyphdef => 'altGlyphDef',
2070 altglyphitem => 'altGlyphItem',
2071 animatecolor => 'animateColor',
2072 animatemotion => 'animateMotion',
2073 animatetransform => 'animateTransform',
2074 clippath => 'clipPath',
2075 feblend => 'feBlend',
2076 fecolormatrix => 'feColorMatrix',
2077 fecomponenttransfer => 'feComponentTransfer',
2078 fecomposite => 'feComposite',
2079 feconvolvematrix => 'feConvolveMatrix',
2080 fediffuselighting => 'feDiffuseLighting',
2081 fedisplacementmap => 'feDisplacementMap',
2082 fedistantlight => 'feDistantLight',
2083 feflood => 'feFlood',
2084 fefunca => 'feFuncA',
2085 fefuncb => 'feFuncB',
2086 fefuncg => 'feFuncG',
2087 fefuncr => 'feFuncR',
2088 fegaussianblur => 'feGaussianBlur',
2089 feimage => 'feImage',
2090 femerge => 'feMerge',
2091 femergenode => 'feMergeNode',
2092 femorphology => 'feMorphology',
2093 feoffset => 'feOffset',
2094 fepointlight => 'fePointLight',
2095 fespecularlighting => 'feSpecularLighting',
2096 fespotlight => 'feSpotLight',
2097 fetile => 'feTile',
2098 feturbulence => 'feTurbulence',
2099 foreignobject => 'foreignObject',
2100 glyphref => 'glyphRef',
2101 lineargradient => 'linearGradient',
2102 radialgradient => 'radialGradient',
2103 #solidcolor => 'solidColor', ## NOTE: Commented in spec (SVG1.2)
2104 textpath => 'textPath',
2105 }->{$tag_name} || $tag_name;
2106 }
2107
2108 ## "adjust SVG attributes" (SVG only) - done in insert-element-f
2109
2110 ## "adjust foreign attributes" - done in insert-element-f
2111
2112
2113 {
2114 my $el;
2115
2116 $el = $self->{document}->create_element_ns
2117 ($nsuri, [undef, $tag_name]);
2118
2119 for my $attr_name (keys %{ $token->{attributes}}) {
2120 my $attr_t = $token->{attributes}->{$attr_name};
2121 my $attr = $self->{document}->create_attribute_ns (
2122 @{
2123 $foreign_attr_xname->{$attr_name} ||
2124 [undef, [undef,
2125 ($nsuri) eq $SVG_NS ?
2126 ($svg_attr_name->{$attr_name} || $attr_name) :
2127 ($nsuri) eq $MML_NS ?
2128 ($attr_name eq 'definitionurl' ?
2129 'definitionURL' : $attr_name) :
2130 $attr_name]]
2131 }
2132 );
2133 $attr->value ($attr_t->{value});
2134 $attr->set_user_data (manakai_source_line => $attr_t->{line});
2135 $attr->set_user_data (manakai_source_column => $attr_t->{column});
2136 $el->set_attribute_node_ns ($attr);
2137 }
2138
2139 $el->set_user_data (manakai_source_line => $token->{line})
2140 if defined $token->{line};
2141 $el->set_user_data (manakai_source_column => $token->{column})
2142 if defined $token->{column};
2143
2144 $insert->($el);
2145 push @{$self->{open_elements}}, [$el, ($el_category_f->{$nsuri}->{ $tag_name} || 0) | FOREIGN_EL];
2146
2147 if ( $token->{attributes}->{xmlns} and $token->{attributes}->{xmlns}->{value} ne ($nsuri)) {
2148 $self->{parse_error}->(level => $self->{level}->{must}, type => 'bad namespace', token => $token);
2149 ## TODO: Error type documentation
2150 }
2151 if ( $token->{attributes}->{'xmlns:xlink'} and
2152 $token->{attributes}->{'xmlns:xlink'}->{value} ne q<http://www.w3.org/1999/xlink>) {
2153 $self->{parse_error}->(level => $self->{level}->{must}, type => 'bad namespace', token => $token);
2154 }
2155 }
2156
2157
2158 if ($self->{self_closing}) {
2159 pop @{$self->{open_elements}};
2160 delete $self->{self_closing};
2161 } else {
2162
2163 }
2164
2165 $token = $self->_get_next_token;
2166 next B;
2167 }
2168 } elsif ($token->{type} == END_TAG_TOKEN) {
2169 ## NOTE: "using the rules for secondary insertion mode" then "continue"
2170 if ($token->{tag_name} eq 'script') {
2171
2172 #
2173 ## XXXscript: Execute script here.
2174 } else {
2175
2176 #
2177 }
2178 } elsif ($token->{type} == END_OF_FILE_TOKEN) {
2179
2180 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed',
2181 text => $self->{open_elements}->[-1]->[0]
2182 ->manakai_local_name,
2183 token => $token);
2184
2185 pop @{$self->{open_elements}}
2186 while $self->{open_elements}->[-1]->[1] & FOREIGN_EL;
2187
2188 ## NOTE: |<span><svg>| ... two parse errors, |<svg>| ... a parse error.
2189
2190 $self->{insertion_mode} &= ~ IN_FOREIGN_CONTENT_IM;
2191 ## Reprocess.
2192 next B;
2193 } else {
2194 die "$0: $token->{type}: Unknown token type";
2195 }
2196 }
2197
2198 if ($self->{insertion_mode} & HEAD_IMS) {
2199 if ($token->{type} == CHARACTER_TOKEN) {
2200 if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
2201 unless ($self->{insertion_mode} == BEFORE_HEAD_IM) {
2202 if ($self->{head_element_inserted}) {
2203
2204 $self->{open_elements}->[-1]->[0]->append_child
2205 ($self->{document}->create_text_node ($1));
2206 delete $self->{head_element_inserted};
2207 ## NOTE: |</head> <link> |
2208 #
2209 } else {
2210
2211 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
2212 ## NOTE: |</head> &#x20;|
2213 #
2214 }
2215 } else {
2216
2217 ## Ignore the token.
2218 #
2219 }
2220 unless (length $token->{data}) {
2221
2222 $token = $self->_get_next_token;
2223 next B;
2224 }
2225 ## TODO: set $token->{column} appropriately
2226 }
2227
2228 if ($self->{insertion_mode} == BEFORE_HEAD_IM) {
2229
2230 ## As if <head>
2231
2232 $self->{head_element} = $self->{document}->create_element_ns
2233 ($HTML_NS, [undef, 'head']);
2234
2235 $self->{head_element}->set_user_data (manakai_source_line => $token->{line})
2236 if defined $token->{line};
2237 $self->{head_element}->set_user_data (manakai_source_column => $token->{column})
2238 if defined $token->{column};
2239
2240 $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
2241 push @{$self->{open_elements}},
2242 [$self->{head_element}, $el_category->{head}];
2243
2244 ## Reprocess in the "in head" insertion mode...
2245 pop @{$self->{open_elements}};
2246
2247 ## Reprocess in the "after head" insertion mode...
2248 } elsif ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
2249
2250 ## As if </noscript>
2251 pop @{$self->{open_elements}};
2252 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in noscript:#text', token => $token);
2253
2254 ## Reprocess in the "in head" insertion mode...
2255 ## As if </head>
2256 pop @{$self->{open_elements}};
2257
2258 ## Reprocess in the "after head" insertion mode...
2259 } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
2260
2261 pop @{$self->{open_elements}};
2262
2263 ## Reprocess in the "after head" insertion mode...
2264 } else {
2265
2266 }
2267
2268 ## "after head" insertion mode
2269 ## As if <body>
2270
2271 {
2272 my $el;
2273
2274 $el = $self->{document}->create_element_ns
2275 ($HTML_NS, [undef, 'body']);
2276
2277 $el->set_user_data (manakai_source_line => $token->{line})
2278 if defined $token->{line};
2279 $el->set_user_data (manakai_source_column => $token->{column})
2280 if defined $token->{column};
2281
2282 $self->{open_elements}->[-1]->[0]->append_child ($el);
2283 push @{$self->{open_elements}}, [$el, $el_category->{'body'} || 0];
2284 }
2285
2286 $self->{insertion_mode} = IN_BODY_IM;
2287 ## The "frameset-ok" flag is left unchanged in this case.
2288 ## Reporcess the token.
2289 next B;
2290 } elsif ($token->{type} == START_TAG_TOKEN) {
2291 if ($token->{tag_name} eq 'head') {
2292 if ($self->{insertion_mode} == BEFORE_HEAD_IM) {
2293
2294
2295 $self->{head_element} = $self->{document}->create_element_ns
2296 ($HTML_NS, [undef, $token->{tag_name}]);
2297
2298 for my $attr_name (keys %{ $token->{attributes}}) {
2299 my $attr_t = $token->{attributes}->{$attr_name};
2300 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
2301 $attr->value ($attr_t->{value});
2302 $attr->set_user_data (manakai_source_line => $attr_t->{line});
2303 $attr->set_user_data (manakai_source_column => $attr_t->{column});
2304 $self->{head_element}->set_attribute_node_ns ($attr);
2305 }
2306
2307 $self->{head_element}->set_user_data (manakai_source_line => $token->{line})
2308 if defined $token->{line};
2309 $self->{head_element}->set_user_data (manakai_source_column => $token->{column})
2310 if defined $token->{column};
2311
2312 $self->{open_elements}->[-1]->[0]->append_child
2313 ($self->{head_element});
2314 push @{$self->{open_elements}},
2315 [$self->{head_element}, $el_category->{head}];
2316 $self->{insertion_mode} = IN_HEAD_IM;
2317
2318 $token = $self->_get_next_token;
2319 next B;
2320 } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
2321
2322 $self->{parse_error}->(level => $self->{level}->{must}, type => 'after head', text => 'head',
2323 token => $token);
2324 ## Ignore the token
2325
2326 $token = $self->_get_next_token;
2327 next B;
2328 } else {
2329
2330 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in head:head',
2331 token => $token); # or in head noscript
2332 ## Ignore the token
2333
2334 $token = $self->_get_next_token;
2335 next B;
2336 }
2337 } elsif ($self->{insertion_mode} == BEFORE_HEAD_IM) {
2338
2339 ## As if <head>
2340
2341 $self->{head_element} = $self->{document}->create_element_ns
2342 ($HTML_NS, [undef, 'head']);
2343
2344 $self->{head_element}->set_user_data (manakai_source_line => $token->{line})
2345 if defined $token->{line};
2346 $self->{head_element}->set_user_data (manakai_source_column => $token->{column})
2347 if defined $token->{column};
2348
2349 $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
2350 push @{$self->{open_elements}},
2351 [$self->{head_element}, $el_category->{head}];
2352
2353 $self->{insertion_mode} = IN_HEAD_IM;
2354 ## Reprocess in the "in head" insertion mode...
2355 } else {
2356
2357 }
2358
2359 if ($token->{tag_name} eq 'base') {
2360 if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
2361
2362 ## As if </noscript>
2363 pop @{$self->{open_elements}};
2364 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in noscript', text => 'base',
2365 token => $token);
2366
2367 $self->{insertion_mode} = IN_HEAD_IM;
2368 ## Reprocess in the "in head" insertion mode...
2369 } else {
2370
2371 }
2372
2373 ## NOTE: There is a "as if in head" code clone.
2374 if ($self->{insertion_mode} == AFTER_HEAD_IM) {
2375
2376 $self->{parse_error}->(level => $self->{level}->{must}, type => 'after head',
2377 text => $token->{tag_name}, token => $token);
2378 push @{$self->{open_elements}},
2379 [$self->{head_element}, $el_category->{head}];
2380 $self->{head_element_inserted} = 1;
2381 } else {
2382
2383 }
2384
2385 {
2386 my $el;
2387
2388 $el = $self->{document}->create_element_ns
2389 ($HTML_NS, [undef, $token->{tag_name}]);
2390
2391 for my $attr_name (keys %{ $token->{attributes}}) {
2392 my $attr_t = $token->{attributes}->{$attr_name};
2393 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
2394 $attr->value ($attr_t->{value});
2395 $attr->set_user_data (manakai_source_line => $attr_t->{line});
2396 $attr->set_user_data (manakai_source_column => $attr_t->{column});
2397 $el->set_attribute_node_ns ($attr);
2398 }
2399
2400 $el->set_user_data (manakai_source_line => $token->{line})
2401 if defined $token->{line};
2402 $el->set_user_data (manakai_source_column => $token->{column})
2403 if defined $token->{column};
2404
2405 $self->{open_elements}->[-1]->[0]->append_child ($el);
2406 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
2407 }
2408
2409 pop @{$self->{open_elements}};
2410 pop @{$self->{open_elements}} # <head>
2411 if $self->{insertion_mode} == AFTER_HEAD_IM;
2412
2413 $token = $self->_get_next_token;
2414 next B;
2415 } elsif ($token->{tag_name} eq 'link') {
2416 ## NOTE: There is a "as if in head" code clone.
2417 if ($self->{insertion_mode} == AFTER_HEAD_IM) {
2418
2419 $self->{parse_error}->(level => $self->{level}->{must}, type => 'after head',
2420 text => $token->{tag_name}, token => $token);
2421 push @{$self->{open_elements}},
2422 [$self->{head_element}, $el_category->{head}];
2423 $self->{head_element_inserted} = 1;
2424 } else {
2425
2426 }
2427
2428 {
2429 my $el;
2430
2431 $el = $self->{document}->create_element_ns
2432 ($HTML_NS, [undef, $token->{tag_name}]);
2433
2434 for my $attr_name (keys %{ $token->{attributes}}) {
2435 my $attr_t = $token->{attributes}->{$attr_name};
2436 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
2437 $attr->value ($attr_t->{value});
2438 $attr->set_user_data (manakai_source_line => $attr_t->{line});
2439 $attr->set_user_data (manakai_source_column => $attr_t->{column});
2440 $el->set_attribute_node_ns ($attr);
2441 }
2442
2443 $el->set_user_data (manakai_source_line => $token->{line})
2444 if defined $token->{line};
2445 $el->set_user_data (manakai_source_column => $token->{column})
2446 if defined $token->{column};
2447
2448 $self->{open_elements}->[-1]->[0]->append_child ($el);
2449 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
2450 }
2451
2452 pop @{$self->{open_elements}};
2453 pop @{$self->{open_elements}} # <head>
2454 if $self->{insertion_mode} == AFTER_HEAD_IM;
2455 delete $self->{self_closing};
2456 $token = $self->_get_next_token;
2457 next B;
2458 } elsif ($token->{tag_name} eq 'command') {
2459 if ($self->{insertion_mode} == IN_HEAD_IM) {
2460 ## NOTE: If the insertion mode at the time of the emission
2461 ## of the token was "before head", $self->{insertion_mode}
2462 ## is already changed to |IN_HEAD_IM|.
2463
2464 ## NOTE: There is a "as if in head" code clone.
2465
2466 {
2467 my $el;
2468
2469 $el = $self->{document}->create_element_ns
2470 ($HTML_NS, [undef, $token->{tag_name}]);
2471
2472 for my $attr_name (keys %{ $token->{attributes}}) {
2473 my $attr_t = $token->{attributes}->{$attr_name};
2474 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
2475 $attr->value ($attr_t->{value});
2476 $attr->set_user_data (manakai_source_line => $attr_t->{line});
2477 $attr->set_user_data (manakai_source_column => $attr_t->{column});
2478 $el->set_attribute_node_ns ($attr);
2479 }
2480
2481 $el->set_user_data (manakai_source_line => $token->{line})
2482 if defined $token->{line};
2483 $el->set_user_data (manakai_source_column => $token->{column})
2484 if defined $token->{column};
2485
2486 $self->{open_elements}->[-1]->[0]->append_child ($el);
2487 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
2488 }
2489
2490 pop @{$self->{open_elements}};
2491 pop @{$self->{open_elements}} # <head>
2492 if $self->{insertion_mode} == AFTER_HEAD_IM;
2493 delete $self->{self_closing};
2494 $token = $self->_get_next_token;
2495 next B;
2496 } else {
2497 ## NOTE: "in head noscript" or "after head" insertion mode
2498 ## - in these cases, these tags are treated as same as
2499 ## normal in-body tags.
2500
2501 #
2502 }
2503 } elsif ($token->{tag_name} eq 'meta') {
2504 ## NOTE: There is a "as if in head" code clone.
2505 if ($self->{insertion_mode} == AFTER_HEAD_IM) {
2506
2507 $self->{parse_error}->(level => $self->{level}->{must}, type => 'after head',
2508 text => $token->{tag_name}, token => $token);
2509 push @{$self->{open_elements}},
2510 [$self->{head_element}, $el_category->{head}];
2511 $self->{head_element_inserted} = 1;
2512 } else {
2513
2514 }
2515
2516 {
2517 my $el;
2518
2519 $el = $self->{document}->create_element_ns
2520 ($HTML_NS, [undef, $token->{tag_name}]);
2521
2522 for my $attr_name (keys %{ $token->{attributes}}) {
2523 my $attr_t = $token->{attributes}->{$attr_name};
2524 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
2525 $attr->value ($attr_t->{value});
2526 $attr->set_user_data (manakai_source_line => $attr_t->{line});
2527 $attr->set_user_data (manakai_source_column => $attr_t->{column});
2528 $el->set_attribute_node_ns ($attr);
2529 }
2530
2531 $el->set_user_data (manakai_source_line => $token->{line})
2532 if defined $token->{line};
2533 $el->set_user_data (manakai_source_column => $token->{column})
2534 if defined $token->{column};
2535
2536 $self->{open_elements}->[-1]->[0]->append_child ($el);
2537 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
2538 }
2539
2540 my $meta_el = pop @{$self->{open_elements}};
2541
2542 unless ($self->{confident}) {
2543 if ($token->{attributes}->{charset}) {
2544
2545 ## NOTE: Whether the encoding is supported or not is handled
2546 ## in the {change_encoding} callback.
2547 $self->{change_encoding}
2548 ->($self, $token->{attributes}->{charset}->{value},
2549 $token);
2550
2551 $meta_el->[0]->get_attribute_node_ns (undef, 'charset')
2552 ->set_user_data (manakai_has_reference =>
2553 $token->{attributes}->{charset}
2554 ->{has_reference});
2555 } elsif ($token->{attributes}->{content}) {
2556 if ($token->{attributes}->{content}->{value}
2557 =~ /[Cc][Hh][Aa][Rr][Ss][Ee][Tt]
2558 [\x09\x0A\x0C\x0D\x20]*=
2559 [\x09\x0A\x0C\x0D\x20]*(?>"([^"]*)"|'([^']*)'|
2560 ([^"'\x09\x0A\x0C\x0D\x20]
2561 [^\x09\x0A\x0C\x0D\x20\x3B]*))/x) {
2562
2563 ## NOTE: Whether the encoding is supported or not is handled
2564 ## in the {change_encoding} callback.
2565 $self->{change_encoding}
2566 ->($self, defined $1 ? $1 : defined $2 ? $2 : $3,
2567 $token);
2568 $meta_el->[0]->get_attribute_node_ns (undef, 'content')
2569 ->set_user_data (manakai_has_reference =>
2570 $token->{attributes}->{content}
2571 ->{has_reference});
2572 } else {
2573
2574 }
2575 }
2576 } else {
2577 if ($token->{attributes}->{charset}) {
2578
2579 $meta_el->[0]->get_attribute_node_ns (undef, 'charset')
2580 ->set_user_data (manakai_has_reference =>
2581 $token->{attributes}->{charset}
2582 ->{has_reference});
2583 }
2584 if ($token->{attributes}->{content}) {
2585
2586 $meta_el->[0]->get_attribute_node_ns (undef, 'content')
2587 ->set_user_data (manakai_has_reference =>
2588 $token->{attributes}->{content}
2589 ->{has_reference});
2590 }
2591 }
2592
2593 pop @{$self->{open_elements}} # <head>
2594 if $self->{insertion_mode} == AFTER_HEAD_IM;
2595 delete $self->{self_closing};
2596 $token = $self->_get_next_token;
2597 next B;
2598 } elsif ($token->{tag_name} eq 'title') {
2599 if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
2600
2601 ## As if </noscript>
2602 pop @{$self->{open_elements}};
2603 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in noscript', text => 'title',
2604 token => $token);
2605
2606 $self->{insertion_mode} = IN_HEAD_IM;
2607 ## Reprocess in the "in head" insertion mode...
2608 } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
2609
2610 $self->{parse_error}->(level => $self->{level}->{must}, type => 'after head',
2611 text => $token->{tag_name}, token => $token);
2612 push @{$self->{open_elements}},
2613 [$self->{head_element}, $el_category->{head}];
2614 $self->{head_element_inserted} = 1;
2615 } else {
2616
2617 }
2618
2619 ## NOTE: There is a "as if in head" code clone.
2620 $parse_rcdata->(RCDATA_CONTENT_MODEL);
2621
2622 ## NOTE: At this point the stack of open elements contain
2623 ## the |head| element (index == -2) and the |script| element
2624 ## (index == -1). In the "after head" insertion mode the
2625 ## |head| element is inserted only for the purpose of
2626 ## providing the context for the |script| element, and
2627 ## therefore we can now and have to remove the element from
2628 ## the stack.
2629 splice @{$self->{open_elements}}, -2, 1, () # <head>
2630 if ($self->{insertion_mode} & IM_MASK) == AFTER_HEAD_IM;
2631 next B;
2632 } elsif ($token->{tag_name} eq 'style' or
2633 $token->{tag_name} eq 'noframes') {
2634 ## NOTE: Or (scripting is enabled and tag_name eq 'noscript' and
2635 ## insertion mode IN_HEAD_IM)
2636 ## NOTE: There is a "as if in head" code clone.
2637 if ($self->{insertion_mode} == AFTER_HEAD_IM) {
2638
2639 $self->{parse_error}->(level => $self->{level}->{must}, type => 'after head',
2640 text => $token->{tag_name}, token => $token);
2641 push @{$self->{open_elements}},
2642 [$self->{head_element}, $el_category->{head}];
2643 $self->{head_element_inserted} = 1;
2644 } else {
2645
2646 }
2647 $parse_rcdata->(CDATA_CONTENT_MODEL);
2648 ## ISSUE: A spec bug [Bug 6038]
2649 splice @{$self->{open_elements}}, -2, 1, () # <head>
2650 if ($self->{insertion_mode} & IM_MASK) == AFTER_HEAD_IM;
2651 next B;
2652 } elsif ($token->{tag_name} eq 'noscript') {
2653 if ($self->{insertion_mode} == IN_HEAD_IM) {
2654
2655 ## NOTE: and scripting is disalbed
2656
2657 {
2658 my $el;
2659
2660 $el = $self->{document}->create_element_ns
2661 ($HTML_NS, [undef, $token->{tag_name}]);
2662
2663 for my $attr_name (keys %{ $token->{attributes}}) {
2664 my $attr_t = $token->{attributes}->{$attr_name};
2665 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
2666 $attr->value ($attr_t->{value});
2667 $attr->set_user_data (manakai_source_line => $attr_t->{line});
2668 $attr->set_user_data (manakai_source_column => $attr_t->{column});
2669 $el->set_attribute_node_ns ($attr);
2670 }
2671
2672 $el->set_user_data (manakai_source_line => $token->{line})
2673 if defined $token->{line};
2674 $el->set_user_data (manakai_source_column => $token->{column})
2675 if defined $token->{column};
2676
2677 $self->{open_elements}->[-1]->[0]->append_child ($el);
2678 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
2679 }
2680
2681 $self->{insertion_mode} = IN_HEAD_NOSCRIPT_IM;
2682
2683 $token = $self->_get_next_token;
2684 next B;
2685 } elsif ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
2686
2687 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in noscript', text => 'noscript',
2688 token => $token);
2689 ## Ignore the token
2690
2691 $token = $self->_get_next_token;
2692 next B;
2693 } else {
2694
2695 #
2696 }
2697 } elsif ($token->{tag_name} eq 'script') {
2698 if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
2699
2700 ## As if </noscript>
2701 pop @{$self->{open_elements}};
2702 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in noscript', text => 'script',
2703 token => $token);
2704
2705 $self->{insertion_mode} = IN_HEAD_IM;
2706 ## Reprocess in the "in head" insertion mode...
2707 } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
2708
2709 $self->{parse_error}->(level => $self->{level}->{must}, type => 'after head',
2710 text => $token->{tag_name}, token => $token);
2711 push @{$self->{open_elements}},
2712 [$self->{head_element}, $el_category->{head}];
2713 $self->{head_element_inserted} = 1;
2714 } else {
2715
2716 }
2717
2718 ## NOTE: There is a "as if in head" code clone.
2719 $script_start_tag->();
2720 ## ISSUE: A spec bug [Bug 6038]
2721 splice @{$self->{open_elements}}, -2, 1 # <head>
2722 if ($self->{insertion_mode} & IM_MASK) == AFTER_HEAD_IM;
2723 next B;
2724 } elsif ($token->{tag_name} eq 'body' or
2725 $token->{tag_name} eq 'frameset') {
2726 if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
2727
2728 ## As if </noscript>
2729 pop @{$self->{open_elements}};
2730 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in noscript',
2731 text => $token->{tag_name}, token => $token);
2732
2733 ## Reprocess in the "in head" insertion mode...
2734 ## As if </head>
2735 pop @{$self->{open_elements}};
2736
2737 ## Reprocess in the "after head" insertion mode...
2738 } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
2739
2740 pop @{$self->{open_elements}};
2741
2742 ## Reprocess in the "after head" insertion mode...
2743 } else {
2744
2745 }
2746
2747 ## "after head" insertion mode
2748
2749 {
2750 my $el;
2751
2752 $el = $self->{document}->create_element_ns
2753 ($HTML_NS, [undef, $token->{tag_name}]);
2754
2755 for my $attr_name (keys %{ $token->{attributes}}) {
2756 my $attr_t = $token->{attributes}->{$attr_name};
2757 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
2758 $attr->value ($attr_t->{value});
2759 $attr->set_user_data (manakai_source_line => $attr_t->{line});
2760 $attr->set_user_data (manakai_source_column => $attr_t->{column});
2761 $el->set_attribute_node_ns ($attr);
2762 }
2763
2764 $el->set_user_data (manakai_source_line => $token->{line})
2765 if defined $token->{line};
2766 $el->set_user_data (manakai_source_column => $token->{column})
2767 if defined $token->{column};
2768
2769 $self->{open_elements}->[-1]->[0]->append_child ($el);
2770 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
2771 }
2772
2773 if ($token->{tag_name} eq 'body') {
2774
2775 delete $self->{frameset_ok};
2776 $self->{insertion_mode} = IN_BODY_IM;
2777 } elsif ($token->{tag_name} eq 'frameset') {
2778
2779 $self->{insertion_mode} = IN_FRAMESET_IM;
2780 } else {
2781 die "$0: tag name: $self->{tag_name}";
2782 }
2783
2784 $token = $self->_get_next_token;
2785 next B;
2786 } else {
2787
2788 #
2789 }
2790
2791 if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
2792
2793 ## As if </noscript>
2794 pop @{$self->{open_elements}};
2795 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in noscript:/',
2796 text => $token->{tag_name}, token => $token);
2797
2798 ## Reprocess in the "in head" insertion mode...
2799 ## As if </head>
2800 pop @{$self->{open_elements}};
2801
2802 ## Reprocess in the "after head" insertion mode...
2803 } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
2804
2805 ## As if </head>
2806 pop @{$self->{open_elements}};
2807
2808 ## Reprocess in the "after head" insertion mode...
2809 } else {
2810
2811 }
2812
2813 ## "after head" insertion mode
2814 ## As if <body>
2815
2816 {
2817 my $el;
2818
2819 $el = $self->{document}->create_element_ns
2820 ($HTML_NS, [undef, 'body']);
2821
2822 $el->set_user_data (manakai_source_line => $token->{line})
2823 if defined $token->{line};
2824 $el->set_user_data (manakai_source_column => $token->{column})
2825 if defined $token->{column};
2826
2827 $self->{open_elements}->[-1]->[0]->append_child ($el);
2828 push @{$self->{open_elements}}, [$el, $el_category->{'body'} || 0];
2829 }
2830
2831 $self->{insertion_mode} = IN_BODY_IM;
2832 ## The "frameset-ok" flag is not changed in this case.
2833 ## Reprocess the token.
2834
2835 next B;
2836 } elsif ($token->{type} == END_TAG_TOKEN) {
2837 ## "Before head", "in head", and "after head" insertion modes
2838 ## ignore most of end tags. Exceptions are "body", "html",
2839 ## and "br" end tags. "Before head" and "in head" insertion
2840 ## modes also recognize "head" end tag. "In head noscript"
2841 ## insertion modes ignore end tags except for "noscript" and
2842 ## "br".
2843
2844 if ($token->{tag_name} eq 'head') {
2845 if ($self->{insertion_mode} == BEFORE_HEAD_IM) {
2846
2847 ## As if <head>
2848
2849 $self->{head_element} = $self->{document}->create_element_ns
2850 ($HTML_NS, [undef, 'head']);
2851
2852 $self->{head_element}->set_user_data (manakai_source_line => $token->{line})
2853 if defined $token->{line};
2854 $self->{head_element}->set_user_data (manakai_source_column => $token->{column})
2855 if defined $token->{column};
2856
2857 $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
2858 push @{$self->{open_elements}},
2859 [$self->{head_element}, $el_category->{head}];
2860
2861 ## Reprocess in the "in head" insertion mode...
2862 pop @{$self->{open_elements}};
2863 $self->{insertion_mode} = AFTER_HEAD_IM;
2864 $token = $self->_get_next_token;
2865 next B;
2866 } elsif ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
2867
2868 #
2869 } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
2870
2871 pop @{$self->{open_elements}};
2872 $self->{insertion_mode} = AFTER_HEAD_IM;
2873 $token = $self->_get_next_token;
2874 next B;
2875 } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
2876
2877 #
2878 } else {
2879 die "$0: $self->{insertion_mode}: Unknown insertion mode";
2880 }
2881 } elsif ($token->{tag_name} eq 'noscript') {
2882 if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
2883
2884 pop @{$self->{open_elements}};
2885 $self->{insertion_mode} = IN_HEAD_IM;
2886 $token = $self->_get_next_token;
2887 next B;
2888 } else {
2889
2890 #
2891 }
2892 } elsif ({
2893 body => ($self->{insertion_mode} != IN_HEAD_NOSCRIPT_IM),
2894 html => ($self->{insertion_mode} != IN_HEAD_NOSCRIPT_IM),
2895 br => 1,
2896 }->{$token->{tag_name}}) {
2897 if ($self->{insertion_mode} == BEFORE_HEAD_IM) {
2898
2899 ## (before head) as if <head>, (in head) as if </head>
2900
2901 $self->{head_element} = $self->{document}->create_element_ns
2902 ($HTML_NS, [undef, 'head']);
2903
2904 $self->{head_element}->set_user_data (manakai_source_line => $token->{line})
2905 if defined $token->{line};
2906 $self->{head_element}->set_user_data (manakai_source_column => $token->{column})
2907 if defined $token->{column};
2908
2909 $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
2910 $self->{insertion_mode} = AFTER_HEAD_IM;
2911
2912 ## Reprocess in the "after head" insertion mode...
2913 } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
2914
2915 ## As if </head>
2916 pop @{$self->{open_elements}};
2917 $self->{insertion_mode} = AFTER_HEAD_IM;
2918
2919 ## Reprocess in the "after head" insertion mode...
2920 } elsif ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
2921
2922 ## NOTE: Two parse errors for <head><noscript></br>
2923 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
2924 text => $token->{tag_name}, token => $token);
2925 ## As if </noscript>
2926 pop @{$self->{open_elements}};
2927 $self->{insertion_mode} = IN_HEAD_IM;
2928
2929 ## Reprocess in the "in head" insertion mode...
2930 ## As if </head>
2931 pop @{$self->{open_elements}};
2932 $self->{insertion_mode} = AFTER_HEAD_IM;
2933
2934 ## Reprocess in the "after head" insertion mode...
2935 } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
2936
2937 #
2938 } else {
2939 die "$0: $self->{insertion_mode}: Unknown insertion mode";
2940 }
2941
2942 ## "after head" insertion mode
2943 ## As if <body>
2944
2945 {
2946 my $el;
2947
2948 $el = $self->{document}->create_element_ns
2949 ($HTML_NS, [undef, 'body']);
2950
2951 $el->set_user_data (manakai_source_line => $token->{line})
2952 if defined $token->{line};
2953 $el->set_user_data (manakai_source_column => $token->{column})
2954 if defined $token->{column};
2955
2956 $self->{open_elements}->[-1]->[0]->append_child ($el);
2957 push @{$self->{open_elements}}, [$el, $el_category->{'body'} || 0];
2958 }
2959
2960 $self->{insertion_mode} = IN_BODY_IM;
2961 ## The "frameset-ok" flag is left unchanged in this case.
2962 ## Reprocess the token.
2963 next B;
2964 }
2965
2966 ## End tags are ignored by default.
2967
2968 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
2969 text => $token->{tag_name}, token => $token);
2970 ## Ignore the token.
2971 $token = $self->_get_next_token;
2972 next B;
2973 } elsif ($token->{type} == END_OF_FILE_TOKEN) {
2974 if ($self->{insertion_mode} == BEFORE_HEAD_IM) {
2975
2976
2977 ## NOTE: As if <head>
2978
2979 $self->{head_element} = $self->{document}->create_element_ns
2980 ($HTML_NS, [undef, 'head']);
2981
2982 $self->{head_element}->set_user_data (manakai_source_line => $token->{line})
2983 if defined $token->{line};
2984 $self->{head_element}->set_user_data (manakai_source_column => $token->{column})
2985 if defined $token->{column};
2986
2987 $self->{open_elements}->[-1]->[0]->append_child
2988 ($self->{head_element});
2989 #push @{$self->{open_elements}},
2990 # [$self->{head_element}, $el_category->{head}];
2991 #$self->{insertion_mode} = IN_HEAD_IM;
2992 ## NOTE: Reprocess.
2993
2994 ## NOTE: As if </head>
2995 #pop @{$self->{open_elements}};
2996 #$self->{insertion_mode} = IN_AFTER_HEAD_IM;
2997 ## NOTE: Reprocess.
2998
2999 #
3000 } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
3001
3002
3003 ## NOTE: As if </head>
3004 pop @{$self->{open_elements}};
3005 #$self->{insertion_mode} = IN_AFTER_HEAD_IM;
3006 ## NOTE: Reprocess.
3007
3008 #
3009 } elsif ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
3010
3011
3012 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in noscript:#eof', token => $token);
3013
3014 ## As if </noscript>
3015 pop @{$self->{open_elements}};
3016 #$self->{insertion_mode} = IN_HEAD_IM;
3017 ## NOTE: Reprocess.
3018
3019 ## NOTE: As if </head>
3020 pop @{$self->{open_elements}};
3021 #$self->{insertion_mode} = IN_AFTER_HEAD_IM;
3022 ## NOTE: Reprocess.
3023
3024 #
3025 } else {
3026
3027 #
3028 }
3029
3030 ## NOTE: As if <body>
3031
3032 {
3033 my $el;
3034
3035 $el = $self->{document}->create_element_ns
3036 ($HTML_NS, [undef, 'body']);
3037
3038 $el->set_user_data (manakai_source_line => $token->{line})
3039 if defined $token->{line};
3040 $el->set_user_data (manakai_source_column => $token->{column})
3041 if defined $token->{column};
3042
3043 $self->{open_elements}->[-1]->[0]->append_child ($el);
3044 push @{$self->{open_elements}}, [$el, $el_category->{'body'} || 0];
3045 }
3046
3047 $self->{insertion_mode} = IN_BODY_IM;
3048 ## The "frameset-ok" flag is left unchanged in this case.
3049 ## Reprocess the token.
3050 next B;
3051 } else {
3052 die "$0: $token->{type}: Unknown token type";
3053 }
3054 } elsif ($self->{insertion_mode} & BODY_IMS) {
3055 if ($token->{type} == CHARACTER_TOKEN) {
3056
3057 $reconstruct_active_formatting_elements->($insert_to_current);
3058
3059 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
3060
3061 if ($token->{data} =~ /[^\x09\x0A\x0C\x0D\x20]/) {
3062 delete $self->{frameset_ok};
3063 }
3064
3065 $token = $self->_get_next_token;
3066 next B;
3067 } elsif ($token->{type} == START_TAG_TOKEN) {
3068 if ({
3069 caption => 1, col => 1, colgroup => 1, tbody => 1,
3070 td => 1, tfoot => 1, th => 1, thead => 1, tr => 1,
3071 }->{$token->{tag_name}}) {
3072 if (($self->{insertion_mode} & IM_MASK) == IN_CELL_IM) {
3073 ## have an element in table scope
3074 for (reverse 0..$#{$self->{open_elements}}) {
3075 my $node = $self->{open_elements}->[$_];
3076 if ($node->[1] == TABLE_CELL_EL) {
3077
3078
3079 ## Close the cell
3080
3081 $token->{self_closing} = $self->{self_closing};
3082 unshift @{$self->{token}}, $token;
3083 delete $self->{self_closing};
3084 # <x>
3085 $token = {type => END_TAG_TOKEN,
3086 tag_name => $node->[0]->manakai_local_name,
3087 line => $token->{line},
3088 column => $token->{column}};
3089 next B;
3090 } elsif ($node->[1] & TABLE_SCOPING_EL) {
3091
3092 ## ISSUE: This case can never be reached, maybe.
3093 last;
3094 }
3095 }
3096
3097
3098 $self->{parse_error}->(level => $self->{level}->{must}, type => 'start tag not allowed',
3099 text => $token->{tag_name}, token => $token);
3100 ## Ignore the token
3101
3102 $token = $self->_get_next_token;
3103 next B;
3104 } elsif (($self->{insertion_mode} & IM_MASK) == IN_CAPTION_IM) {
3105 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed', text => 'caption',
3106 token => $token);
3107
3108 ## NOTE: As if </caption>.
3109 ## have a table element in table scope
3110 my $i;
3111 INSCOPE: {
3112 for (reverse 0..$#{$self->{open_elements}}) {
3113 my $node = $self->{open_elements}->[$_];
3114 if ($node->[1] == CAPTION_EL) {
3115
3116 $i = $_;
3117 last INSCOPE;
3118 } elsif ($node->[1] & TABLE_SCOPING_EL) {
3119
3120 last;
3121 }
3122 }
3123
3124
3125 $self->{parse_error}->(level => $self->{level}->{must}, type => 'start tag not allowed',
3126 text => $token->{tag_name}, token => $token);
3127 ## Ignore the token
3128
3129 $token = $self->_get_next_token;
3130 next B;
3131 } # INSCOPE
3132
3133 ## generate implied end tags
3134 while ($self->{open_elements}->[-1]->[1]
3135 & END_TAG_OPTIONAL_EL) {
3136
3137 pop @{$self->{open_elements}};
3138 }
3139
3140 unless ($self->{open_elements}->[-1]->[1] == CAPTION_EL) {
3141
3142 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed',
3143 text => $self->{open_elements}->[-1]->[0]
3144 ->manakai_local_name,
3145 token => $token);
3146 } else {
3147
3148 }
3149
3150 splice @{$self->{open_elements}}, $i;
3151
3152 $clear_up_to_marker->();
3153
3154 $self->{insertion_mode} = IN_TABLE_IM;
3155
3156 ## reprocess
3157
3158 next B;
3159 } else {
3160
3161 #
3162 }
3163 } else {
3164
3165 #
3166 }
3167 } elsif ($token->{type} == END_TAG_TOKEN) {
3168 if ($token->{tag_name} eq 'td' or $token->{tag_name} eq 'th') {
3169 if (($self->{insertion_mode} & IM_MASK) == IN_CELL_IM) {
3170 ## have an element in table scope
3171 my $i;
3172 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3173 my $node = $self->{open_elements}->[$_];
3174 if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
3175
3176 $i = $_;
3177 last INSCOPE;
3178 } elsif ($node->[1] & TABLE_SCOPING_EL) {
3179
3180 last INSCOPE;
3181 }
3182 } # INSCOPE
3183 unless (defined $i) {
3184
3185 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
3186 text => $token->{tag_name},
3187 token => $token);
3188 ## Ignore the token
3189 $token = $self->_get_next_token;
3190 next B;
3191 }
3192
3193 ## generate implied end tags
3194 while ($self->{open_elements}->[-1]->[1]
3195 & END_TAG_OPTIONAL_EL) {
3196
3197 pop @{$self->{open_elements}};
3198 }
3199
3200 if ($self->{open_elements}->[-1]->[0]->manakai_local_name
3201 ne $token->{tag_name}) {
3202
3203 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed',
3204 text => $self->{open_elements}->[-1]->[0]
3205 ->manakai_local_name,
3206 token => $token);
3207 } else {
3208
3209 }
3210
3211 splice @{$self->{open_elements}}, $i;
3212
3213 $clear_up_to_marker->();
3214
3215 $self->{insertion_mode} = IN_ROW_IM;
3216
3217 $token = $self->_get_next_token;
3218 next B;
3219 } elsif (($self->{insertion_mode} & IM_MASK) == IN_CAPTION_IM) {
3220
3221 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
3222 text => $token->{tag_name}, token => $token);
3223 ## Ignore the token
3224 $token = $self->_get_next_token;
3225 next B;
3226 } else {
3227
3228 #
3229 }
3230 } elsif ($token->{tag_name} eq 'caption') {
3231 if (($self->{insertion_mode} & IM_MASK) == IN_CAPTION_IM) {
3232 ## have a table element in table scope
3233 my $i;
3234 INSCOPE: {
3235 for (reverse 0..$#{$self->{open_elements}}) {
3236 my $node = $self->{open_elements}->[$_];
3237 if ($node->[1] == CAPTION_EL) {
3238
3239 $i = $_;
3240 last INSCOPE;
3241 } elsif ($node->[1] & TABLE_SCOPING_EL) {
3242
3243 last;
3244 }
3245 }
3246
3247
3248 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
3249 text => $token->{tag_name}, token => $token);
3250 ## Ignore the token
3251 $token = $self->_get_next_token;
3252 next B;
3253 } # INSCOPE
3254
3255 ## generate implied end tags
3256 while ($self->{open_elements}->[-1]->[1]
3257 & END_TAG_OPTIONAL_EL) {
3258
3259 pop @{$self->{open_elements}};
3260 }
3261
3262 unless ($self->{open_elements}->[-1]->[1] == CAPTION_EL) {
3263
3264 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed',
3265 text => $self->{open_elements}->[-1]->[0]
3266 ->manakai_local_name,
3267 token => $token);
3268 } else {
3269
3270 }
3271
3272 splice @{$self->{open_elements}}, $i;
3273
3274 $clear_up_to_marker->();
3275
3276 $self->{insertion_mode} = IN_TABLE_IM;
3277
3278 $token = $self->_get_next_token;
3279 next B;
3280 } elsif (($self->{insertion_mode} & IM_MASK) == IN_CELL_IM) {
3281
3282 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
3283 text => $token->{tag_name}, token => $token);
3284 ## Ignore the token
3285 $token = $self->_get_next_token;
3286 next B;
3287 } else {
3288
3289 #
3290 }
3291 } elsif ({
3292 table => 1, tbody => 1, tfoot => 1,
3293 thead => 1, tr => 1,
3294 }->{$token->{tag_name}} and
3295 ($self->{insertion_mode} & IM_MASK) == IN_CELL_IM) {
3296 ## have an element in table scope
3297 my $i;
3298 my $tn;
3299 INSCOPE: {
3300 for (reverse 0..$#{$self->{open_elements}}) {
3301 my $node = $self->{open_elements}->[$_];
3302 if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
3303
3304 $i = $_;
3305
3306 ## Close the cell
3307
3308 $token->{self_closing} = $self->{self_closing};
3309 unshift @{$self->{token}}, $token;
3310 delete $self->{self_closing};
3311 # </x>
3312 $token = {type => END_TAG_TOKEN, tag_name => $tn,
3313 line => $token->{line},
3314 column => $token->{column}};
3315 next B;
3316 } elsif ($node->[1] == TABLE_CELL_EL) {
3317
3318 $tn = $node->[0]->manakai_local_name;
3319 ## NOTE: There is exactly one |td| or |th| element
3320 ## in scope in the stack of open elements by definition.
3321 } elsif ($node->[1] & TABLE_SCOPING_EL) {
3322 ## ISSUE: Can this be reached?
3323
3324 last;
3325 }
3326 }
3327
3328
3329 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
3330 text => $token->{tag_name}, token => $token);
3331 ## Ignore the token
3332 $token = $self->_get_next_token;
3333 next B;
3334 } # INSCOPE
3335 } elsif ($token->{tag_name} eq 'table' and
3336 ($self->{insertion_mode} & IM_MASK) == IN_CAPTION_IM) {
3337 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed', text => 'caption',
3338 token => $token);
3339
3340 ## As if </caption>
3341 ## have a table element in table scope
3342 my $i;
3343 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3344 my $node = $self->{open_elements}->[$_];
3345 if ($node->[1] == CAPTION_EL) {
3346
3347 $i = $_;
3348 last INSCOPE;
3349 } elsif ($node->[1] & TABLE_SCOPING_EL) {
3350
3351 last INSCOPE;
3352 }
3353 } # INSCOPE
3354 unless (defined $i) {
3355
3356 ## TODO: Wrong error type?
3357 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
3358 text => 'caption', token => $token);
3359 ## Ignore the token
3360 $token = $self->_get_next_token;
3361 next B;
3362 }
3363
3364 ## generate implied end tags
3365 while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
3366
3367 pop @{$self->{open_elements}};
3368 }
3369
3370 unless ($self->{open_elements}->[-1]->[1] == CAPTION_EL) {
3371
3372 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed',
3373 text => $self->{open_elements}->[-1]->[0]
3374 ->manakai_local_name,
3375 token => $token);
3376 } else {
3377
3378 }
3379
3380 splice @{$self->{open_elements}}, $i;
3381
3382 $clear_up_to_marker->();
3383
3384 $self->{insertion_mode} = IN_TABLE_IM;
3385
3386 ## reprocess
3387 next B;
3388 } elsif ({
3389 body => 1, col => 1, colgroup => 1, html => 1,
3390 }->{$token->{tag_name}}) {
3391 if ($self->{insertion_mode} & BODY_TABLE_IMS) {
3392
3393 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
3394 text => $token->{tag_name}, token => $token);
3395 ## Ignore the token
3396 $token = $self->_get_next_token;
3397 next B;
3398 } else {
3399
3400 #
3401 }
3402 } elsif ({
3403 tbody => 1, tfoot => 1,
3404 thead => 1, tr => 1,
3405 }->{$token->{tag_name}} and
3406 ($self->{insertion_mode} & IM_MASK) == IN_CAPTION_IM) {
3407
3408 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
3409 text => $token->{tag_name}, token => $token);
3410 ## Ignore the token
3411 $token = $self->_get_next_token;
3412 next B;
3413 } else {
3414
3415 #
3416 }
3417 } elsif ($token->{type} == END_OF_FILE_TOKEN) {
3418 for my $entry (@{$self->{open_elements}}) {
3419 unless ($entry->[1] & ALL_END_TAG_OPTIONAL_EL) {
3420
3421 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in body:#eof', token => $token);
3422 last;
3423 }
3424 }
3425
3426 ## Stop parsing.
3427 last B;
3428 } else {
3429 die "$0: $token->{type}: Unknown token type";
3430 }
3431
3432 $insert = $insert_to_current;
3433 #
3434 } elsif ($self->{insertion_mode} & TABLE_IMS) {
3435 if ($token->{type} == START_TAG_TOKEN) {
3436 if ({
3437 tr => (($self->{insertion_mode} & IM_MASK) != IN_ROW_IM),
3438 th => 1, td => 1,
3439 }->{$token->{tag_name}}) {
3440 if (($self->{insertion_mode} & IM_MASK) == IN_TABLE_IM) {
3441 ## Clear back to table context
3442 while (not ($self->{open_elements}->[-1]->[1]
3443 & TABLE_SCOPING_EL)) {
3444
3445 pop @{$self->{open_elements}};
3446 }
3447
3448
3449 {
3450 my $el;
3451
3452 $el = $self->{document}->create_element_ns
3453 ($HTML_NS, [undef, 'tbody']);
3454
3455 $el->set_user_data (manakai_source_line => $token->{line})
3456 if defined $token->{line};
3457 $el->set_user_data (manakai_source_column => $token->{column})
3458 if defined $token->{column};
3459
3460 $self->{open_elements}->[-1]->[0]->append_child ($el);
3461 push @{$self->{open_elements}}, [$el, $el_category->{'tbody'} || 0];
3462 }
3463
3464 $self->{insertion_mode} = IN_TABLE_BODY_IM;
3465 ## reprocess in the "in table body" insertion mode...
3466 }
3467
3468 if (($self->{insertion_mode} & IM_MASK) == IN_TABLE_BODY_IM) {
3469 unless ($token->{tag_name} eq 'tr') {
3470
3471 $self->{parse_error}->(level => $self->{level}->{must}, type => 'missing start tag:tr', token => $token);
3472 }
3473
3474 ## Clear back to table body context
3475 while (not ($self->{open_elements}->[-1]->[1]
3476 & TABLE_ROWS_SCOPING_EL)) {
3477
3478 ## ISSUE: Can this case be reached?
3479 pop @{$self->{open_elements}};
3480 }
3481
3482 $self->{insertion_mode} = IN_ROW_IM;
3483 if ($token->{tag_name} eq 'tr') {
3484
3485
3486 {
3487 my $el;
3488
3489 $el = $self->{document}->create_element_ns
3490 ($HTML_NS, [undef, $token->{tag_name}]);
3491
3492 for my $attr_name (keys %{ $token->{attributes}}) {
3493 my $attr_t = $token->{attributes}->{$attr_name};
3494 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
3495 $attr->value ($attr_t->{value});
3496 $attr->set_user_data (manakai_source_line => $attr_t->{line});
3497 $attr->set_user_data (manakai_source_column => $attr_t->{column});
3498 $el->set_attribute_node_ns ($attr);
3499 }
3500
3501 $el->set_user_data (manakai_source_line => $token->{line})
3502 if defined $token->{line};
3503 $el->set_user_data (manakai_source_column => $token->{column})
3504 if defined $token->{column};
3505
3506 $self->{open_elements}->[-1]->[0]->append_child ($el);
3507 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
3508 }
3509
3510 $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
3511
3512 $token = $self->_get_next_token;
3513 next B;
3514 } else {
3515
3516
3517 {
3518 my $el;
3519
3520 $el = $self->{document}->create_element_ns
3521 ($HTML_NS, [undef, 'tr']);
3522
3523 $el->set_user_data (manakai_source_line => $token->{line})
3524 if defined $token->{line};
3525 $el->set_user_data (manakai_source_column => $token->{column})
3526 if defined $token->{column};
3527
3528 $self->{open_elements}->[-1]->[0]->append_child ($el);
3529 push @{$self->{open_elements}}, [$el, $el_category->{'tr'} || 0];
3530 }
3531
3532 ## reprocess in the "in row" insertion mode
3533 }
3534 } else {
3535
3536 }
3537
3538 ## Clear back to table row context
3539 while (not ($self->{open_elements}->[-1]->[1]
3540 & TABLE_ROW_SCOPING_EL)) {
3541
3542 pop @{$self->{open_elements}};
3543 }
3544
3545
3546 {
3547 my $el;
3548
3549 $el = $self->{document}->create_element_ns
3550 ($HTML_NS, [undef, $token->{tag_name}]);
3551
3552 for my $attr_name (keys %{ $token->{attributes}}) {
3553 my $attr_t = $token->{attributes}->{$attr_name};
3554 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
3555 $attr->value ($attr_t->{value});
3556 $attr->set_user_data (manakai_source_line => $attr_t->{line});
3557 $attr->set_user_data (manakai_source_column => $attr_t->{column});
3558 $el->set_attribute_node_ns ($attr);
3559 }
3560
3561 $el->set_user_data (manakai_source_line => $token->{line})
3562 if defined $token->{line};
3563 $el->set_user_data (manakai_source_column => $token->{column})
3564 if defined $token->{column};
3565
3566 $self->{open_elements}->[-1]->[0]->append_child ($el);
3567 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
3568 }
3569
3570 $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
3571 $self->{insertion_mode} = IN_CELL_IM;
3572
3573 push @$active_formatting_elements, ['#marker', ''];
3574
3575
3576 $token = $self->_get_next_token;
3577 next B;
3578 } elsif ({
3579 caption => 1, col => 1, colgroup => 1,
3580 tbody => 1, tfoot => 1, thead => 1,
3581 tr => 1, # $self->{insertion_mode} == IN_ROW_IM
3582 }->{$token->{tag_name}}) {
3583 if (($self->{insertion_mode} & IM_MASK) == IN_ROW_IM) {
3584 ## As if </tr>
3585 ## have an element in table scope
3586 my $i;
3587 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3588 my $node = $self->{open_elements}->[$_];
3589 if ($node->[1] == TABLE_ROW_EL) {
3590
3591 $i = $_;
3592 last INSCOPE;
3593 } elsif ($node->[1] & TABLE_SCOPING_EL) {
3594
3595 last INSCOPE;
3596 }
3597 } # INSCOPE
3598 unless (defined $i) {
3599
3600 ## TODO: This type is wrong.
3601 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmacthed end tag',
3602 text => $token->{tag_name}, token => $token);
3603 ## Ignore the token
3604
3605 $token = $self->_get_next_token;
3606 next B;
3607 }
3608
3609 ## Clear back to table row context
3610 while (not ($self->{open_elements}->[-1]->[1]
3611 & TABLE_ROW_SCOPING_EL)) {
3612
3613 ## ISSUE: Can this case be reached?
3614 pop @{$self->{open_elements}};
3615 }
3616
3617 pop @{$self->{open_elements}}; # tr
3618 $self->{insertion_mode} = IN_TABLE_BODY_IM;
3619 if ($token->{tag_name} eq 'tr') {
3620
3621 ## reprocess
3622
3623 next B;
3624 } else {
3625
3626 ## reprocess in the "in table body" insertion mode...
3627 }
3628 }
3629
3630 if (($self->{insertion_mode} & IM_MASK) == IN_TABLE_BODY_IM) {
3631 ## have an element in table scope
3632 my $i;
3633 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3634 my $node = $self->{open_elements}->[$_];
3635 if ($node->[1] == TABLE_ROW_GROUP_EL) {
3636
3637 $i = $_;
3638 last INSCOPE;
3639 } elsif ($node->[1] & TABLE_SCOPING_EL) {
3640
3641 last INSCOPE;
3642 }
3643 } # INSCOPE
3644 unless (defined $i) {
3645
3646 ## TODO: This erorr type is wrong.
3647 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
3648 text => $token->{tag_name}, token => $token);
3649 ## Ignore the token
3650
3651 $token = $self->_get_next_token;
3652 next B;
3653 }
3654
3655 ## Clear back to table body context
3656 while (not ($self->{open_elements}->[-1]->[1]
3657 & TABLE_ROWS_SCOPING_EL)) {
3658
3659 ## ISSUE: Can this state be reached?
3660 pop @{$self->{open_elements}};
3661 }
3662
3663 ## As if <{current node}>
3664 ## have an element in table scope
3665 ## true by definition
3666
3667 ## Clear back to table body context
3668 ## nop by definition
3669
3670 pop @{$self->{open_elements}};
3671 $self->{insertion_mode} = IN_TABLE_IM;
3672 ## reprocess in "in table" insertion mode...
3673 } else {
3674
3675 }
3676
3677 if ($token->{tag_name} eq 'col') {
3678 ## Clear back to table context
3679 while (not ($self->{open_elements}->[-1]->[1]
3680 & TABLE_SCOPING_EL)) {
3681
3682 ## ISSUE: Can this state be reached?
3683 pop @{$self->{open_elements}};
3684 }
3685
3686
3687 {
3688 my $el;
3689
3690 $el = $self->{document}->create_element_ns
3691 ($HTML_NS, [undef, 'colgroup']);
3692
3693 $el->set_user_data (manakai_source_line => $token->{line})
3694 if defined $token->{line};
3695 $el->set_user_data (manakai_source_column => $token->{column})
3696 if defined $token->{column};
3697
3698 $self->{open_elements}->[-1]->[0]->append_child ($el);
3699 push @{$self->{open_elements}}, [$el, $el_category->{'colgroup'} || 0];
3700 }
3701
3702 $self->{insertion_mode} = IN_COLUMN_GROUP_IM;
3703 ## reprocess
3704 $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
3705
3706 next B;
3707 } elsif ({
3708 caption => 1,
3709 colgroup => 1,
3710 tbody => 1, tfoot => 1, thead => 1,
3711 }->{$token->{tag_name}}) {
3712 ## Clear back to table context
3713 while (not ($self->{open_elements}->[-1]->[1]
3714 & TABLE_SCOPING_EL)) {
3715
3716 ## ISSUE: Can this state be reached?
3717 pop @{$self->{open_elements}};
3718 }
3719
3720 push @$active_formatting_elements, ['#marker', '']
3721 if $token->{tag_name} eq 'caption';
3722
3723
3724 {
3725 my $el;
3726
3727 $el = $self->{document}->create_element_ns
3728 ($HTML_NS, [undef, $token->{tag_name}]);
3729
3730 for my $attr_name (keys %{ $token->{attributes}}) {
3731 my $attr_t = $token->{attributes}->{$attr_name};
3732 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
3733 $attr->value ($attr_t->{value});
3734 $attr->set_user_data (manakai_source_line => $attr_t->{line});
3735 $attr->set_user_data (manakai_source_column => $attr_t->{column});
3736 $el->set_attribute_node_ns ($attr);
3737 }
3738
3739 $el->set_user_data (manakai_source_line => $token->{line})
3740 if defined $token->{line};
3741 $el->set_user_data (manakai_source_column => $token->{column})
3742 if defined $token->{column};
3743
3744 $self->{open_elements}->[-1]->[0]->append_child ($el);
3745 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
3746 }
3747
3748 $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
3749 $self->{insertion_mode} = {
3750 caption => IN_CAPTION_IM,
3751 colgroup => IN_COLUMN_GROUP_IM,
3752 tbody => IN_TABLE_BODY_IM,
3753 tfoot => IN_TABLE_BODY_IM,
3754 thead => IN_TABLE_BODY_IM,
3755 }->{$token->{tag_name}};
3756 $token = $self->_get_next_token;
3757
3758 next B;
3759 } else {
3760 die "$0: in table: <>: $token->{tag_name}";
3761 }
3762 } elsif ($token->{tag_name} eq 'table') {
3763 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed',
3764 text => $self->{open_elements}->[-1]->[0]
3765 ->manakai_local_name,
3766 token => $token);
3767
3768 ## As if </table>
3769 ## have a table element in table scope
3770 my $i;
3771 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3772 my $node = $self->{open_elements}->[$_];
3773 if ($node->[1] == TABLE_EL) {
3774
3775 $i = $_;
3776 last INSCOPE;
3777 } elsif ($node->[1] & TABLE_SCOPING_EL) {
3778
3779 last INSCOPE;
3780 }
3781 } # INSCOPE
3782 unless (defined $i) {
3783
3784 ## TODO: The following is wrong, maybe.
3785 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag', text => 'table',
3786 token => $token);
3787 ## Ignore tokens </table><table>
3788
3789 $token = $self->_get_next_token;
3790 next B;
3791 }
3792
3793 ## TODO: Followings are removed from the latest spec.
3794 ## generate implied end tags
3795 while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
3796
3797 pop @{$self->{open_elements}};
3798 }
3799
3800 unless ($self->{open_elements}->[-1]->[1] == TABLE_EL) {
3801
3802 ## NOTE: |<table><tr><table>|
3803 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed',
3804 text => $self->{open_elements}->[-1]->[0]
3805 ->manakai_local_name,
3806 token => $token);
3807 } else {
3808
3809 }
3810
3811 splice @{$self->{open_elements}}, $i;
3812 pop @{$open_tables};
3813
3814 $self->_reset_insertion_mode;
3815
3816 ## reprocess
3817
3818 next B;
3819 } elsif ($token->{tag_name} eq 'style') {
3820
3821 ## NOTE: This is a "as if in head" code clone.
3822 $parse_rcdata->(CDATA_CONTENT_MODEL);
3823 $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
3824 next B;
3825 } elsif ($token->{tag_name} eq 'script') {
3826
3827 ## NOTE: This is a "as if in head" code clone.
3828 $script_start_tag->();
3829 $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
3830 next B;
3831 } elsif ($token->{tag_name} eq 'input') {
3832 if ($token->{attributes}->{type}) {
3833 my $type = $token->{attributes}->{type}->{value};
3834 $type =~ tr/A-Z/a-z/; ## ASCII case-insensitive.
3835 if ($type eq 'hidden') {
3836
3837 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in table',
3838 text => $token->{tag_name}, token => $token);
3839
3840
3841 {
3842 my $el;
3843
3844 $el = $self->{document}->create_element_ns
3845 ($HTML_NS, [undef, $token->{tag_name}]);
3846
3847 for my $attr_name (keys %{ $token->{attributes}}) {
3848 my $attr_t = $token->{attributes}->{$attr_name};
3849 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
3850 $attr->value ($attr_t->{value});
3851 $attr->set_user_data (manakai_source_line => $attr_t->{line});
3852 $attr->set_user_data (manakai_source_column => $attr_t->{column});
3853 $el->set_attribute_node_ns ($attr);
3854 }
3855
3856 $el->set_user_data (manakai_source_line => $token->{line})
3857 if defined $token->{line};
3858 $el->set_user_data (manakai_source_column => $token->{column})
3859 if defined $token->{column};
3860
3861 $self->{open_elements}->[-1]->[0]->append_child ($el);
3862 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
3863 }
3864
3865 $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
3866
3867 ## TODO: form element pointer
3868
3869 pop @{$self->{open_elements}};
3870
3871 $token = $self->_get_next_token;
3872 delete $self->{self_closing};
3873 next B;
3874 } else {
3875
3876 #
3877 }
3878 } else {
3879
3880 #
3881 }
3882 } else {
3883
3884 #
3885 }
3886
3887 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in table', text => $token->{tag_name},
3888 token => $token);
3889
3890 $insert = $insert_to_foster;
3891 #
3892 } elsif ($token->{type} == END_TAG_TOKEN) {
3893 if ($token->{tag_name} eq 'tr' and
3894 ($self->{insertion_mode} & IM_MASK) == IN_ROW_IM) {
3895 ## have an element in table scope
3896 my $i;
3897 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3898 my $node = $self->{open_elements}->[$_];
3899 if ($node->[1] == TABLE_ROW_EL) {
3900
3901 $i = $_;
3902 last INSCOPE;
3903 } elsif ($node->[1] & TABLE_SCOPING_EL) {
3904
3905 last INSCOPE;
3906 }
3907 } # INSCOPE
3908 unless (defined $i) {
3909
3910 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
3911 text => $token->{tag_name}, token => $token);
3912 ## Ignore the token
3913
3914 $token = $self->_get_next_token;
3915 next B;
3916 } else {
3917
3918 }
3919
3920 ## Clear back to table row context
3921 while (not ($self->{open_elements}->[-1]->[1]
3922 & TABLE_ROW_SCOPING_EL)) {
3923
3924 ## ISSUE: Can this state be reached?
3925 pop @{$self->{open_elements}};
3926 }
3927
3928 pop @{$self->{open_elements}}; # tr
3929 $self->{insertion_mode} = IN_TABLE_BODY_IM;
3930 $token = $self->_get_next_token;
3931
3932 next B;
3933 } elsif ($token->{tag_name} eq 'table') {
3934 if (($self->{insertion_mode} & IM_MASK) == IN_ROW_IM) {
3935 ## As if </tr>
3936 ## have an element in table scope
3937 my $i;
3938 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3939 my $node = $self->{open_elements}->[$_];
3940 if ($node->[1] == TABLE_ROW_EL) {
3941
3942 $i = $_;
3943 last INSCOPE;
3944 } elsif ($node->[1] & TABLE_SCOPING_EL) {
3945
3946 last INSCOPE;
3947 }
3948 } # INSCOPE
3949 unless (defined $i) {
3950
3951 ## TODO: The following is wrong.
3952 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
3953 text => $token->{type}, token => $token);
3954 ## Ignore the token
3955
3956 $token = $self->_get_next_token;
3957 next B;
3958 }
3959
3960 ## Clear back to table row context
3961 while (not ($self->{open_elements}->[-1]->[1]
3962 & TABLE_ROW_SCOPING_EL)) {
3963
3964 ## ISSUE: Can this state be reached?
3965 pop @{$self->{open_elements}};
3966 }
3967
3968 pop @{$self->{open_elements}}; # tr
3969 $self->{insertion_mode} = IN_TABLE_BODY_IM;
3970 ## reprocess in the "in table body" insertion mode...
3971 }
3972
3973 if (($self->{insertion_mode} & IM_MASK) == IN_TABLE_BODY_IM) {
3974 ## have an element in table scope
3975 my $i;
3976 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
3977 my $node = $self->{open_elements}->[$_];
3978 if ($node->[1] == TABLE_ROW_GROUP_EL) {
3979
3980 $i = $_;
3981 last INSCOPE;
3982 } elsif ($node->[1] & TABLE_SCOPING_EL) {
3983
3984 last INSCOPE;
3985 }
3986 } # INSCOPE
3987 unless (defined $i) {
3988
3989 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
3990 text => $token->{tag_name}, token => $token);
3991 ## Ignore the token
3992
3993 $token = $self->_get_next_token;
3994 next B;
3995 }
3996
3997 ## Clear back to table body context
3998 while (not ($self->{open_elements}->[-1]->[1]
3999 & TABLE_ROWS_SCOPING_EL)) {
4000
4001 pop @{$self->{open_elements}};
4002 }
4003
4004 ## As if <{current node}>
4005 ## have an element in table scope
4006 ## true by definition
4007
4008 ## Clear back to table body context
4009 ## nop by definition
4010
4011 pop @{$self->{open_elements}};
4012 $self->{insertion_mode} = IN_TABLE_IM;
4013 ## reprocess in the "in table" insertion mode...
4014 }
4015
4016 ## NOTE: </table> in the "in table" insertion mode.
4017 ## When you edit the code fragment below, please ensure that
4018 ## the code for <table> in the "in table" insertion mode
4019 ## is synced with it.
4020
4021 ## have a table element in table scope
4022 my $i;
4023 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4024 my $node = $self->{open_elements}->[$_];
4025 if ($node->[1] == TABLE_EL) {
4026
4027 $i = $_;
4028 last INSCOPE;
4029 } elsif ($node->[1] & TABLE_SCOPING_EL) {
4030
4031 last INSCOPE;
4032 }
4033 } # INSCOPE
4034 unless (defined $i) {
4035
4036 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
4037 text => $token->{tag_name}, token => $token);
4038 ## Ignore the token
4039
4040 $token = $self->_get_next_token;
4041 next B;
4042 }
4043
4044 splice @{$self->{open_elements}}, $i;
4045 pop @{$open_tables};
4046
4047 $self->_reset_insertion_mode;
4048
4049 $token = $self->_get_next_token;
4050 next B;
4051 } elsif ({
4052 tbody => 1, tfoot => 1, thead => 1,
4053 }->{$token->{tag_name}} and
4054 $self->{insertion_mode} & ROW_IMS) {
4055 if (($self->{insertion_mode} & IM_MASK) == IN_ROW_IM) {
4056 ## have an element in table scope
4057 my $i;
4058 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4059 my $node = $self->{open_elements}->[$_];
4060 if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
4061
4062 $i = $_;
4063 last INSCOPE;
4064 } elsif ($node->[1] & TABLE_SCOPING_EL) {
4065
4066 last INSCOPE;
4067 }
4068 } # INSCOPE
4069 unless (defined $i) {
4070
4071 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
4072 text => $token->{tag_name}, token => $token);
4073 ## Ignore the token
4074
4075 $token = $self->_get_next_token;
4076 next B;
4077 }
4078
4079 ## As if </tr>
4080 ## have an element in table scope
4081 my $i;
4082 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4083 my $node = $self->{open_elements}->[$_];
4084 if ($node->[1] == TABLE_ROW_EL) {
4085
4086 $i = $_;
4087 last INSCOPE;
4088 } elsif ($node->[1] & TABLE_SCOPING_EL) {
4089
4090 last INSCOPE;
4091 }
4092 } # INSCOPE
4093 unless (defined $i) {
4094
4095 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
4096 text => 'tr', token => $token);
4097 ## Ignore the token
4098
4099 $token = $self->_get_next_token;
4100 next B;
4101 }
4102
4103 ## Clear back to table row context
4104 while (not ($self->{open_elements}->[-1]->[1]
4105 & TABLE_ROW_SCOPING_EL)) {
4106
4107 ## ISSUE: Can this case be reached?
4108 pop @{$self->{open_elements}};
4109 }
4110
4111 pop @{$self->{open_elements}}; # tr
4112 $self->{insertion_mode} = IN_TABLE_BODY_IM;
4113 ## reprocess in the "in table body" insertion mode...
4114 }
4115
4116 ## have an element in table scope
4117 my $i;
4118 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4119 my $node = $self->{open_elements}->[$_];
4120 if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
4121
4122 $i = $_;
4123 last INSCOPE;
4124 } elsif ($node->[1] & TABLE_SCOPING_EL) {
4125
4126 last INSCOPE;
4127 }
4128 } # INSCOPE
4129 unless (defined $i) {
4130
4131 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
4132 text => $token->{tag_name}, token => $token);
4133 ## Ignore the token
4134
4135 $token = $self->_get_next_token;
4136 next B;
4137 }
4138
4139 ## Clear back to table body context
4140 while (not ($self->{open_elements}->[-1]->[1]
4141 & TABLE_ROWS_SCOPING_EL)) {
4142
4143 ## ISSUE: Can this case be reached?
4144 pop @{$self->{open_elements}};
4145 }
4146
4147 pop @{$self->{open_elements}};
4148 $self->{insertion_mode} = IN_TABLE_IM;
4149
4150 $token = $self->_get_next_token;
4151 next B;
4152 } elsif ({
4153 body => 1, caption => 1, col => 1, colgroup => 1,
4154 html => 1, td => 1, th => 1,
4155 tr => 1, # $self->{insertion_mode} == IN_ROW_IM
4156 tbody => 1, tfoot => 1, thead => 1, # $self->{insertion_mode} == IN_TABLE_IM
4157 }->{$token->{tag_name}}) {
4158
4159 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
4160 text => $token->{tag_name}, token => $token);
4161 ## Ignore the token
4162
4163 $token = $self->_get_next_token;
4164 next B;
4165 } else {
4166
4167 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in table:/',
4168 text => $token->{tag_name}, token => $token);
4169
4170 $insert = $insert_to_foster;
4171 #
4172 }
4173 } elsif ($token->{type} == END_OF_FILE_TOKEN) {
4174 unless ($self->{open_elements}->[-1]->[1] == HTML_EL and
4175 @{$self->{open_elements}} == 1) { # redundant, maybe
4176 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in body:#eof', token => $token);
4177
4178 #
4179 } else {
4180
4181 #
4182 }
4183
4184 ## Stop parsing
4185 last B;
4186 } else {
4187 die "$0: $token->{type}: Unknown token type";
4188 }
4189 } elsif (($self->{insertion_mode} & IM_MASK) == IN_COLUMN_GROUP_IM) {
4190 if ($token->{type} == CHARACTER_TOKEN) {
4191 if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
4192 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
4193 unless (length $token->{data}) {
4194
4195 $token = $self->_get_next_token;
4196 next B;
4197 }
4198 }
4199
4200
4201 #
4202 } elsif ($token->{type} == START_TAG_TOKEN) {
4203 if ($token->{tag_name} eq 'col') {
4204
4205
4206 {
4207 my $el;
4208
4209 $el = $self->{document}->create_element_ns
4210 ($HTML_NS, [undef, $token->{tag_name}]);
4211
4212 for my $attr_name (keys %{ $token->{attributes}}) {
4213 my $attr_t = $token->{attributes}->{$attr_name};
4214 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
4215 $attr->value ($attr_t->{value});
4216 $attr->set_user_data (manakai_source_line => $attr_t->{line});
4217 $attr->set_user_data (manakai_source_column => $attr_t->{column});
4218 $el->set_attribute_node_ns ($attr);
4219 }
4220
4221 $el->set_user_data (manakai_source_line => $token->{line})
4222 if defined $token->{line};
4223 $el->set_user_data (manakai_source_column => $token->{column})
4224 if defined $token->{column};
4225
4226 $self->{open_elements}->[-1]->[0]->append_child ($el);
4227 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
4228 }
4229
4230 pop @{$self->{open_elements}};
4231 delete $self->{self_closing};
4232 $token = $self->_get_next_token;
4233 next B;
4234 } else {
4235
4236 #
4237 }
4238 } elsif ($token->{type} == END_TAG_TOKEN) {
4239 if ($token->{tag_name} eq 'colgroup') {
4240 if ($self->{open_elements}->[-1]->[1] == HTML_EL) {
4241
4242 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
4243 text => 'colgroup', token => $token);
4244 ## Ignore the token
4245 $token = $self->_get_next_token;
4246 next B;
4247 } else {
4248
4249 pop @{$self->{open_elements}}; # colgroup
4250 $self->{insertion_mode} = IN_TABLE_IM;
4251 $token = $self->_get_next_token;
4252 next B;
4253 }
4254 } elsif ($token->{tag_name} eq 'col') {
4255
4256 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
4257 text => 'col', token => $token);
4258 ## Ignore the token
4259 $token = $self->_get_next_token;
4260 next B;
4261 } else {
4262
4263 #
4264 }
4265 } elsif ($token->{type} == END_OF_FILE_TOKEN) {
4266 if ($self->{open_elements}->[-1]->[1] == HTML_EL and
4267 @{$self->{open_elements}} == 1) { # redundant, maybe
4268
4269 ## Stop parsing.
4270 last B;
4271 } else {
4272 ## NOTE: As if </colgroup>.
4273
4274 pop @{$self->{open_elements}}; # colgroup
4275 $self->{insertion_mode} = IN_TABLE_IM;
4276 ## Reprocess.
4277 next B;
4278 }
4279 } else {
4280 die "$0: $token->{type}: Unknown token type";
4281 }
4282
4283 ## As if </colgroup>
4284 if ($self->{open_elements}->[-1]->[1] == HTML_EL) {
4285
4286 ## TODO: Wrong error type?
4287 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
4288 text => 'colgroup', token => $token);
4289 ## Ignore the token
4290
4291 $token = $self->_get_next_token;
4292 next B;
4293 } else {
4294
4295 pop @{$self->{open_elements}}; # colgroup
4296 $self->{insertion_mode} = IN_TABLE_IM;
4297
4298 ## reprocess
4299 next B;
4300 }
4301 } elsif ($self->{insertion_mode} & SELECT_IMS) {
4302 if ($token->{type} == CHARACTER_TOKEN) {
4303
4304 $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
4305 $token = $self->_get_next_token;
4306 next B;
4307 } elsif ($token->{type} == START_TAG_TOKEN) {
4308 if ($token->{tag_name} eq 'option') {
4309 if ($self->{open_elements}->[-1]->[1] == OPTION_EL) {
4310
4311 ## As if </option>
4312 pop @{$self->{open_elements}};
4313 } else {
4314
4315 }
4316
4317
4318 {
4319 my $el;
4320
4321 $el = $self->{document}->create_element_ns
4322 ($HTML_NS, [undef, $token->{tag_name}]);
4323
4324 for my $attr_name (keys %{ $token->{attributes}}) {
4325 my $attr_t = $token->{attributes}->{$attr_name};
4326 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
4327 $attr->value ($attr_t->{value});
4328 $attr->set_user_data (manakai_source_line => $attr_t->{line});
4329 $attr->set_user_data (manakai_source_column => $attr_t->{column});
4330 $el->set_attribute_node_ns ($attr);
4331 }
4332
4333 $el->set_user_data (manakai_source_line => $token->{line})
4334 if defined $token->{line};
4335 $el->set_user_data (manakai_source_column => $token->{column})
4336 if defined $token->{column};
4337
4338 $self->{open_elements}->[-1]->[0]->append_child ($el);
4339 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
4340 }
4341
4342
4343 $token = $self->_get_next_token;
4344 next B;
4345 } elsif ($token->{tag_name} eq 'optgroup') {
4346 if ($self->{open_elements}->[-1]->[1] == OPTION_EL) {
4347
4348 ## As if </option>
4349 pop @{$self->{open_elements}};
4350 } else {
4351
4352 }
4353
4354 if ($self->{open_elements}->[-1]->[1] == OPTGROUP_EL) {
4355
4356 ## As if </optgroup>
4357 pop @{$self->{open_elements}};
4358 } else {
4359
4360 }
4361
4362
4363 {
4364 my $el;
4365
4366 $el = $self->{document}->create_element_ns
4367 ($HTML_NS, [undef, $token->{tag_name}]);
4368
4369 for my $attr_name (keys %{ $token->{attributes}}) {
4370 my $attr_t = $token->{attributes}->{$attr_name};
4371 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
4372 $attr->value ($attr_t->{value});
4373 $attr->set_user_data (manakai_source_line => $attr_t->{line});
4374 $attr->set_user_data (manakai_source_column => $attr_t->{column});
4375 $el->set_attribute_node_ns ($attr);
4376 }
4377
4378 $el->set_user_data (manakai_source_line => $token->{line})
4379 if defined $token->{line};
4380 $el->set_user_data (manakai_source_column => $token->{column})
4381 if defined $token->{column};
4382
4383 $self->{open_elements}->[-1]->[0]->append_child ($el);
4384 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
4385 }
4386
4387
4388 $token = $self->_get_next_token;
4389 next B;
4390 } elsif ({
4391 select => 1, input => 1, textarea => 1, keygen => 1,
4392 }->{$token->{tag_name}} or
4393 (($self->{insertion_mode} & IM_MASK)
4394 == IN_SELECT_IN_TABLE_IM and
4395 {
4396 caption => 1, table => 1,
4397 tbody => 1, tfoot => 1, thead => 1,
4398 tr => 1, td => 1, th => 1,
4399 }->{$token->{tag_name}})) {
4400
4401 ## 1. Parse error.
4402 if ($token->{tag_name} eq 'select') {
4403 $self->{parse_error}->(level => $self->{level}->{must}, type => 'select in select', ## XXX: documentation
4404 token => $token);
4405 } else {
4406 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed', text => 'select',
4407 token => $token);
4408 }
4409
4410 ## 2./<select>-1. Unless "have an element in table scope" (select):
4411 my $i;
4412 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4413 my $node = $self->{open_elements}->[$_];
4414 if ($node->[1] == SELECT_EL) {
4415
4416 $i = $_;
4417 last INSCOPE;
4418 } elsif ($node->[1] & TABLE_SCOPING_EL) {
4419
4420 last INSCOPE;
4421 }
4422 } # INSCOPE
4423 unless (defined $i) {
4424
4425 if ($token->{tag_name} eq 'select') {
4426 ## NOTE: This error would be raised when
4427 ## |select.innerHTML = '<select>'| is executed; in this
4428 ## case two errors, "select in select" and "unmatched
4429 ## end tags" are reported to the user, the latter might
4430 ## be confusing but this is what the spec requires.
4431 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
4432 text => 'select',
4433 token => $token);
4434 }
4435 ## Ignore the token.
4436
4437 $token = $self->_get_next_token;
4438 next B;
4439 }
4440
4441 ## 3. Otherwise, as if there were <select>:
4442
4443
4444 splice @{$self->{open_elements}}, $i;
4445
4446 $self->_reset_insertion_mode;
4447
4448 if ($token->{tag_name} eq 'select') {
4449
4450 $token = $self->_get_next_token;
4451 next B;
4452 } else {
4453
4454
4455 ## Reprocess the token.
4456 next B;
4457 }
4458 } elsif ($token->{tag_name} eq 'script') {
4459
4460 ## NOTE: This is an "as if in head" code clone
4461 $script_start_tag->();
4462 next B;
4463 } else {
4464
4465 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in select',
4466 text => $token->{tag_name}, token => $token);
4467 ## Ignore the token
4468
4469 $token = $self->_get_next_token;
4470 next B;
4471 }
4472 } elsif ($token->{type} == END_TAG_TOKEN) {
4473 if ($token->{tag_name} eq 'optgroup') {
4474 if ($self->{open_elements}->[-1]->[1] == OPTION_EL and
4475 $self->{open_elements}->[-2]->[1] == OPTGROUP_EL) {
4476
4477 ## As if </option>
4478 splice @{$self->{open_elements}}, -2;
4479 } elsif ($self->{open_elements}->[-1]->[1] == OPTGROUP_EL) {
4480
4481 pop @{$self->{open_elements}};
4482 } else {
4483
4484 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
4485 text => $token->{tag_name}, token => $token);
4486 ## Ignore the token
4487 }
4488
4489 $token = $self->_get_next_token;
4490 next B;
4491 } elsif ($token->{tag_name} eq 'option') {
4492 if ($self->{open_elements}->[-1]->[1] == OPTION_EL) {
4493
4494 pop @{$self->{open_elements}};
4495 } else {
4496
4497 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
4498 text => $token->{tag_name}, token => $token);
4499 ## Ignore the token
4500 }
4501
4502 $token = $self->_get_next_token;
4503 next B;
4504 } elsif ($token->{tag_name} eq 'select') {
4505 ## have an element in table scope
4506 my $i;
4507 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4508 my $node = $self->{open_elements}->[$_];
4509 if ($node->[1] == SELECT_EL) {
4510
4511 $i = $_;
4512 last INSCOPE;
4513 } elsif ($node->[1] & TABLE_SCOPING_EL) {
4514
4515 last INSCOPE;
4516 }
4517 } # INSCOPE
4518 unless (defined $i) {
4519
4520 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
4521 text => $token->{tag_name}, token => $token);
4522 ## Ignore the token
4523
4524 $token = $self->_get_next_token;
4525 next B;
4526 }
4527
4528
4529 splice @{$self->{open_elements}}, $i;
4530
4531 $self->_reset_insertion_mode;
4532
4533
4534 $token = $self->_get_next_token;
4535 next B;
4536 } elsif (($self->{insertion_mode} & IM_MASK)
4537 == IN_SELECT_IN_TABLE_IM and
4538 {
4539 caption => 1, table => 1, tbody => 1,
4540 tfoot => 1, thead => 1, tr => 1, td => 1, th => 1,
4541 }->{$token->{tag_name}}) {
4542 ## TODO: The following is wrong?
4543 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
4544 text => $token->{tag_name}, token => $token);
4545
4546 ## have an element in table scope
4547 my $i;
4548 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4549 my $node = $self->{open_elements}->[$_];
4550 if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
4551
4552 $i = $_;
4553 last INSCOPE;
4554 } elsif ($node->[1] & TABLE_SCOPING_EL) {
4555
4556 last INSCOPE;
4557 }
4558 } # INSCOPE
4559 unless (defined $i) {
4560
4561 ## Ignore the token
4562
4563 $token = $self->_get_next_token;
4564 next B;
4565 }
4566
4567 ## As if </select>
4568 ## have an element in table scope
4569 undef $i;
4570 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
4571 my $node = $self->{open_elements}->[$_];
4572 if ($node->[1] == SELECT_EL) {
4573
4574 $i = $_;
4575 last INSCOPE;
4576 } elsif ($node->[1] & TABLE_SCOPING_EL) {
4577 ## ISSUE: Can this state be reached?
4578
4579 last INSCOPE;
4580 }
4581 } # INSCOPE
4582 unless (defined $i) {
4583
4584 ## TODO: The following error type is correct?
4585 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
4586 text => 'select', token => $token);
4587 ## Ignore the </select> token
4588
4589 $token = $self->_get_next_token; ## TODO: ok?
4590 next B;
4591 }
4592
4593
4594 splice @{$self->{open_elements}}, $i;
4595
4596 $self->_reset_insertion_mode;
4597
4598
4599 ## reprocess
4600 next B;
4601 } else {
4602
4603 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in select:/',
4604 text => $token->{tag_name}, token => $token);
4605 ## Ignore the token
4606
4607 $token = $self->_get_next_token;
4608 next B;
4609 }
4610 } elsif ($token->{type} == END_OF_FILE_TOKEN) {
4611 unless ($self->{open_elements}->[-1]->[1] == HTML_EL and
4612 @{$self->{open_elements}} == 1) { # redundant, maybe
4613
4614 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in body:#eof', token => $token);
4615 } else {
4616
4617 }
4618
4619 ## Stop parsing.
4620 last B;
4621 } else {
4622 die "$0: $token->{type}: Unknown token type";
4623 }
4624 } elsif ($self->{insertion_mode} & BODY_AFTER_IMS) {
4625 if ($token->{type} == CHARACTER_TOKEN) {
4626 if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
4627 my $data = $1;
4628 ## As if in body
4629 $reconstruct_active_formatting_elements->($insert_to_current);
4630
4631 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
4632
4633 unless (length $token->{data}) {
4634
4635 $token = $self->_get_next_token;
4636 next B;
4637 }
4638 }
4639
4640 if ($self->{insertion_mode} == AFTER_HTML_BODY_IM) {
4641
4642 $self->{parse_error}->(level => $self->{level}->{must}, type => 'after html:#text', token => $token);
4643 #
4644 } else {
4645
4646 ## "after body" insertion mode
4647 $self->{parse_error}->(level => $self->{level}->{must}, type => 'after body:#text', token => $token);
4648 #
4649 }
4650
4651 $self->{insertion_mode} = IN_BODY_IM;
4652 ## reprocess
4653 next B;
4654 } elsif ($token->{type} == START_TAG_TOKEN) {
4655 if ($self->{insertion_mode} == AFTER_HTML_BODY_IM) {
4656
4657 $self->{parse_error}->(level => $self->{level}->{must}, type => 'after html',
4658 text => $token->{tag_name}, token => $token);
4659 #
4660 } else {
4661
4662 ## "after body" insertion mode
4663 $self->{parse_error}->(level => $self->{level}->{must}, type => 'after body',
4664 text => $token->{tag_name}, token => $token);
4665 #
4666 }
4667
4668 $self->{insertion_mode} = IN_BODY_IM;
4669
4670 ## reprocess
4671 next B;
4672 } elsif ($token->{type} == END_TAG_TOKEN) {
4673 if ($self->{insertion_mode} == AFTER_HTML_BODY_IM) {
4674
4675 $self->{parse_error}->(level => $self->{level}->{must}, type => 'after html:/',
4676 text => $token->{tag_name}, token => $token);
4677
4678 $self->{insertion_mode} = IN_BODY_IM;
4679 ## Reprocess.
4680 next B;
4681 } else {
4682
4683 }
4684
4685 ## "after body" insertion mode
4686 if ($token->{tag_name} eq 'html') {
4687 if (defined $self->{inner_html_node}) {
4688
4689 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
4690 text => 'html', token => $token);
4691 ## Ignore the token
4692 $token = $self->_get_next_token;
4693 next B;
4694 } else {
4695
4696 $self->{insertion_mode} = AFTER_HTML_BODY_IM;
4697 $token = $self->_get_next_token;
4698 next B;
4699 }
4700 } else {
4701
4702 $self->{parse_error}->(level => $self->{level}->{must}, type => 'after body:/',
4703 text => $token->{tag_name}, token => $token);
4704
4705 $self->{insertion_mode} = IN_BODY_IM;
4706 ## reprocess
4707 next B;
4708 }
4709 } elsif ($token->{type} == END_OF_FILE_TOKEN) {
4710
4711 ## Stop parsing
4712 last B;
4713 } else {
4714 die "$0: $token->{type}: Unknown token type";
4715 }
4716 } elsif ($self->{insertion_mode} & FRAME_IMS) {
4717 if ($token->{type} == CHARACTER_TOKEN) {
4718 if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
4719 $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
4720
4721 unless (length $token->{data}) {
4722
4723 $token = $self->_get_next_token;
4724 next B;
4725 }
4726 }
4727
4728 if ($token->{data} =~ s/^[^\x09\x0A\x0C\x20]+//) {
4729 if ($self->{insertion_mode} == IN_FRAMESET_IM) {
4730
4731 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in frameset:#text', token => $token);
4732 } elsif ($self->{insertion_mode} == AFTER_FRAMESET_IM) {
4733
4734 $self->{parse_error}->(level => $self->{level}->{must}, type => 'after frameset:#text', token => $token);
4735 } else { # "after after frameset"
4736
4737 $self->{parse_error}->(level => $self->{level}->{must}, type => 'after html:#text', token => $token);
4738 }
4739
4740 ## Ignore the token.
4741 if (length $token->{data}) {
4742
4743 ## reprocess the rest of characters
4744 } else {
4745
4746 $token = $self->_get_next_token;
4747 }
4748 next B;
4749 }
4750
4751 die qq[$0: Character "$token->{data}"];
4752 } elsif ($token->{type} == START_TAG_TOKEN) {
4753 if ($token->{tag_name} eq 'frameset' and
4754 $self->{insertion_mode} == IN_FRAMESET_IM) {
4755
4756
4757 {
4758 my $el;
4759
4760 $el = $self->{document}->create_element_ns
4761 ($HTML_NS, [undef, $token->{tag_name}]);
4762
4763 for my $attr_name (keys %{ $token->{attributes}}) {
4764 my $attr_t = $token->{attributes}->{$attr_name};
4765 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
4766 $attr->value ($attr_t->{value});
4767 $attr->set_user_data (manakai_source_line => $attr_t->{line});
4768 $attr->set_user_data (manakai_source_column => $attr_t->{column});
4769 $el->set_attribute_node_ns ($attr);
4770 }
4771
4772 $el->set_user_data (manakai_source_line => $token->{line})
4773 if defined $token->{line};
4774 $el->set_user_data (manakai_source_column => $token->{column})
4775 if defined $token->{column};
4776
4777 $self->{open_elements}->[-1]->[0]->append_child ($el);
4778 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
4779 }
4780
4781
4782 $token = $self->_get_next_token;
4783 next B;
4784 } elsif ($token->{tag_name} eq 'frame' and
4785 $self->{insertion_mode} == IN_FRAMESET_IM) {
4786
4787
4788 {
4789 my $el;
4790
4791 $el = $self->{document}->create_element_ns
4792 ($HTML_NS, [undef, $token->{tag_name}]);
4793
4794 for my $attr_name (keys %{ $token->{attributes}}) {
4795 my $attr_t = $token->{attributes}->{$attr_name};
4796 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
4797 $attr->value ($attr_t->{value});
4798 $attr->set_user_data (manakai_source_line => $attr_t->{line});
4799 $attr->set_user_data (manakai_source_column => $attr_t->{column});
4800 $el->set_attribute_node_ns ($attr);
4801 }
4802
4803 $el->set_user_data (manakai_source_line => $token->{line})
4804 if defined $token->{line};
4805 $el->set_user_data (manakai_source_column => $token->{column})
4806 if defined $token->{column};
4807
4808 $self->{open_elements}->[-1]->[0]->append_child ($el);
4809 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
4810 }
4811
4812 pop @{$self->{open_elements}};
4813 delete $self->{self_closing};
4814 $token = $self->_get_next_token;
4815 next B;
4816 } elsif ($token->{tag_name} eq 'noframes') {
4817
4818 ## NOTE: As if in head.
4819 $parse_rcdata->(CDATA_CONTENT_MODEL);
4820 next B;
4821
4822 ## NOTE: |<!DOCTYPE HTML><frameset></frameset></html><noframes></noframes>|
4823 ## has no parse error.
4824 } else {
4825 if ($self->{insertion_mode} == IN_FRAMESET_IM) {
4826
4827 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in frameset',
4828 text => $token->{tag_name}, token => $token);
4829 } elsif ($self->{insertion_mode} == AFTER_FRAMESET_IM) {
4830
4831 $self->{parse_error}->(level => $self->{level}->{must}, type => 'after frameset',
4832 text => $token->{tag_name}, token => $token);
4833 } else { # "after after frameset"
4834
4835 $self->{parse_error}->(level => $self->{level}->{must}, type => 'after after frameset',
4836 text => $token->{tag_name}, token => $token);
4837 }
4838 ## Ignore the token
4839
4840 $token = $self->_get_next_token;
4841 next B;
4842 }
4843 } elsif ($token->{type} == END_TAG_TOKEN) {
4844 if ($token->{tag_name} eq 'frameset' and
4845 $self->{insertion_mode} == IN_FRAMESET_IM) {
4846 if ($self->{open_elements}->[-1]->[1] == HTML_EL and
4847 @{$self->{open_elements}} == 1) {
4848
4849 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
4850 text => $token->{tag_name}, token => $token);
4851 ## Ignore the token
4852 $token = $self->_get_next_token;
4853 } else {
4854
4855 pop @{$self->{open_elements}};
4856 $token = $self->_get_next_token;
4857 }
4858
4859 if (not defined $self->{inner_html_node} and
4860 not ($self->{open_elements}->[-1]->[1] == FRAMESET_EL)) {
4861
4862 $self->{insertion_mode} = AFTER_FRAMESET_IM;
4863 } else {
4864
4865 }
4866 next B;
4867 } elsif ($token->{tag_name} eq 'html' and
4868 $self->{insertion_mode} == AFTER_FRAMESET_IM) {
4869
4870 $self->{insertion_mode} = AFTER_HTML_FRAMESET_IM;
4871 $token = $self->_get_next_token;
4872 next B;
4873 } else {
4874 if ($self->{insertion_mode} == IN_FRAMESET_IM) {
4875
4876 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in frameset:/',
4877 text => $token->{tag_name}, token => $token);
4878 } elsif ($self->{insertion_mode} == AFTER_FRAMESET_IM) {
4879
4880 $self->{parse_error}->(level => $self->{level}->{must}, type => 'after frameset:/',
4881 text => $token->{tag_name}, token => $token);
4882 } else { # "after after html"
4883
4884 $self->{parse_error}->(level => $self->{level}->{must}, type => 'after after frameset:/',
4885 text => $token->{tag_name}, token => $token);
4886 }
4887 ## Ignore the token
4888 $token = $self->_get_next_token;
4889 next B;
4890 }
4891 } elsif ($token->{type} == END_OF_FILE_TOKEN) {
4892 unless ($self->{open_elements}->[-1]->[1] == HTML_EL and
4893 @{$self->{open_elements}} == 1) { # redundant, maybe
4894
4895 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in body:#eof', token => $token);
4896 } else {
4897
4898 }
4899
4900 ## Stop parsing
4901 last B;
4902 } else {
4903 die "$0: $token->{type}: Unknown token type";
4904 }
4905 } else {
4906 die "$0: $self->{insertion_mode}: Unknown insertion mode";
4907 }
4908
4909 ## "in body" insertion mode
4910 if ($token->{type} == START_TAG_TOKEN) {
4911 if ($token->{tag_name} eq 'script') {
4912
4913 ## NOTE: This is an "as if in head" code clone
4914 $script_start_tag->();
4915 next B;
4916 } elsif ($token->{tag_name} eq 'style') {
4917
4918 ## NOTE: This is an "as if in head" code clone
4919 $parse_rcdata->(CDATA_CONTENT_MODEL);
4920 next B;
4921 } elsif ({
4922 base => 1, command => 1, link => 1,
4923 }->{$token->{tag_name}}) {
4924
4925 ## NOTE: This is an "as if in head" code clone, only "-t" differs
4926
4927 {
4928 my $el;
4929
4930 $el = $self->{document}->create_element_ns
4931 ($HTML_NS, [undef, $token->{tag_name}]);
4932
4933 for my $attr_name (keys %{ $token->{attributes}}) {
4934 my $attr_t = $token->{attributes}->{$attr_name};
4935 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
4936 $attr->value ($attr_t->{value});
4937 $attr->set_user_data (manakai_source_line => $attr_t->{line});
4938 $attr->set_user_data (manakai_source_column => $attr_t->{column});
4939 $el->set_attribute_node_ns ($attr);
4940 }
4941
4942 $el->set_user_data (manakai_source_line => $token->{line})
4943 if defined $token->{line};
4944 $el->set_user_data (manakai_source_column => $token->{column})
4945 if defined $token->{column};
4946
4947 $insert->($el);
4948 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
4949 }
4950
4951 pop @{$self->{open_elements}};
4952 delete $self->{self_closing};
4953 $token = $self->_get_next_token;
4954 next B;
4955 } elsif ($token->{tag_name} eq 'meta') {
4956 ## NOTE: This is an "as if in head" code clone, only "-t" differs
4957
4958 {
4959 my $el;
4960
4961 $el = $self->{document}->create_element_ns
4962 ($HTML_NS, [undef, $token->{tag_name}]);
4963
4964 for my $attr_name (keys %{ $token->{attributes}}) {
4965 my $attr_t = $token->{attributes}->{$attr_name};
4966 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
4967 $attr->value ($attr_t->{value});
4968 $attr->set_user_data (manakai_source_line => $attr_t->{line});
4969 $attr->set_user_data (manakai_source_column => $attr_t->{column});
4970 $el->set_attribute_node_ns ($attr);
4971 }
4972
4973 $el->set_user_data (manakai_source_line => $token->{line})
4974 if defined $token->{line};
4975 $el->set_user_data (manakai_source_column => $token->{column})
4976 if defined $token->{column};
4977
4978 $insert->($el);
4979 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
4980 }
4981
4982 my $meta_el = pop @{$self->{open_elements}};
4983
4984 unless ($self->{confident}) {
4985 if ($token->{attributes}->{charset}) {
4986
4987 ## NOTE: Whether the encoding is supported or not is handled
4988 ## in the {change_encoding} callback.
4989 $self->{change_encoding}
4990 ->($self, $token->{attributes}->{charset}->{value}, $token);
4991
4992 $meta_el->[0]->get_attribute_node_ns (undef, 'charset')
4993 ->set_user_data (manakai_has_reference =>
4994 $token->{attributes}->{charset}
4995 ->{has_reference});
4996 } elsif ($token->{attributes}->{content}) {
4997 if ($token->{attributes}->{content}->{value}
4998 =~ /[Cc][Hh][Aa][Rr][Ss][Ee][Tt]
4999 [\x09\x0A\x0C\x0D\x20]*=
5000 [\x09\x0A\x0C\x0D\x20]*(?>"([^"]*)"|'([^']*)'|
5001 ([^"'\x09\x0A\x0C\x0D\x20][^\x09\x0A\x0C\x0D\x20\x3B]*))
5002 /x) {
5003
5004 ## NOTE: Whether the encoding is supported or not is handled
5005 ## in the {change_encoding} callback.
5006 $self->{change_encoding}
5007 ->($self, defined $1 ? $1 : defined $2 ? $2 : $3, $token);
5008 $meta_el->[0]->get_attribute_node_ns (undef, 'content')
5009 ->set_user_data (manakai_has_reference =>
5010 $token->{attributes}->{content}
5011 ->{has_reference});
5012 }
5013 }
5014 } else {
5015 if ($token->{attributes}->{charset}) {
5016
5017 $meta_el->[0]->get_attribute_node_ns (undef, 'charset')
5018 ->set_user_data (manakai_has_reference =>
5019 $token->{attributes}->{charset}
5020 ->{has_reference});
5021 }
5022 if ($token->{attributes}->{content}) {
5023
5024 $meta_el->[0]->get_attribute_node_ns (undef, 'content')
5025 ->set_user_data (manakai_has_reference =>
5026 $token->{attributes}->{content}
5027 ->{has_reference});
5028 }
5029 }
5030
5031 delete $self->{self_closing};
5032 $token = $self->_get_next_token;
5033 next B;
5034 } elsif ($token->{tag_name} eq 'title') {
5035
5036 ## NOTE: This is an "as if in head" code clone
5037 $parse_rcdata->(RCDATA_CONTENT_MODEL);
5038 next B;
5039 } elsif ($token->{tag_name} eq 'body') {
5040 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in body', text => 'body', token => $token);
5041
5042 if (@{$self->{open_elements}} == 1 or
5043 not ($self->{open_elements}->[1]->[1] == BODY_EL)) {
5044
5045 ## Ignore the token
5046 } else {
5047 my $body_el = $self->{open_elements}->[1]->[0];
5048 for my $attr_name (keys %{$token->{attributes}}) {
5049 unless ($body_el->has_attribute_ns (undef, $attr_name)) {
5050
5051 $body_el->set_attribute_ns
5052 (undef, [undef, $attr_name],
5053 $token->{attributes}->{$attr_name}->{value});
5054 }
5055 }
5056 }
5057
5058 $token = $self->_get_next_token;
5059 next B;
5060 } elsif ($token->{tag_name} eq 'frameset') {
5061 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in body', text => $token->{tag_name},
5062 token => $token);
5063
5064 if (@{$self->{open_elements}} == 1 or
5065 not ($self->{open_elements}->[1]->[1] == BODY_EL)) {
5066
5067 ## Ignore the token.
5068 } elsif (not $self->{frameset_ok}) {
5069
5070 ## Ignore the token.
5071 } else {
5072
5073
5074 ## 1. Remove the second element.
5075 my $body = $self->{open_elements}->[1]->[0];
5076 my $body_parent = $body->parent_node;
5077 $body_parent->remove_child ($body) if $body_parent;
5078
5079 ## 2. Pop nodes.
5080 splice @{$self->{open_elements}}, 1;
5081
5082 ## 3. Insert.
5083
5084 {
5085 my $el;
5086
5087 $el = $self->{document}->create_element_ns
5088 ($HTML_NS, [undef, $token->{tag_name}]);
5089
5090 for my $attr_name (keys %{ $token->{attributes}}) {
5091 my $attr_t = $token->{attributes}->{$attr_name};
5092 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
5093 $attr->value ($attr_t->{value});
5094 $attr->set_user_data (manakai_source_line => $attr_t->{line});
5095 $attr->set_user_data (manakai_source_column => $attr_t->{column});
5096 $el->set_attribute_node_ns ($attr);
5097 }
5098
5099 $el->set_user_data (manakai_source_line => $token->{line})
5100 if defined $token->{line};
5101 $el->set_user_data (manakai_source_column => $token->{column})
5102 if defined $token->{column};
5103
5104 $insert->($el);
5105 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
5106 }
5107
5108
5109 ## 4. Switch.
5110 $self->{insertion_mode} = IN_FRAMESET_IM;
5111 }
5112
5113
5114 $token = $self->_get_next_token;
5115 next B;
5116 } elsif ({
5117 ## NOTE: Start tags for non-phrasing flow content elements
5118
5119 ## NOTE: The normal one
5120 address => 1, article => 1, aside => 1, blockquote => 1,
5121 center => 1, datagrid => 1, details => 1, dialog => 1,
5122 dir => 1, div => 1, dl => 1, fieldset => 1, figure => 1,
5123 footer => 1, h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1,
5124 h6 => 1, header => 1, hgroup => 1,
5125 menu => 1, nav => 1, ol => 1, p => 1,
5126 section => 1, ul => 1,
5127 ## NOTE: As normal, but drops leading newline
5128 pre => 1, listing => 1,
5129 ## NOTE: As normal, but interacts with the form element pointer
5130 form => 1,
5131
5132 table => 1,
5133 hr => 1,
5134 }->{$token->{tag_name}}) {
5135
5136 ## 1. When there is an opening |form| element:
5137 if ($token->{tag_name} eq 'form' and defined $self->{form_element}) {
5138
5139 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in form:form', token => $token);
5140 ## Ignore the token
5141
5142 $token = $self->_get_next_token;
5143 next B;
5144 }
5145
5146 ## 2. Close the |p| element, if any.
5147 if ($token->{tag_name} ne 'table' or # The Hixie Quirk
5148 $self->{document}->manakai_compat_mode ne 'quirks') {
5149 ## has a p element in scope
5150 INSCOPE: for (reverse @{$self->{open_elements}}) {
5151 if ($_->[1] == P_EL) {
5152
5153
5154 $token->{self_closing} = $self->{self_closing};
5155 unshift @{$self->{token}}, $token;
5156 delete $self->{self_closing};
5157 # <form>
5158 $token = {type => END_TAG_TOKEN, tag_name => 'p',
5159 line => $token->{line}, column => $token->{column}};
5160 next B;
5161 } elsif ($_->[1] & SCOPING_EL) {
5162
5163 last INSCOPE;
5164 }
5165 } # INSCOPE
5166 }
5167
5168 ## 3. Close the opening <hn> element, if any.
5169 if ({h1 => 1, h2 => 1, h3 => 1,
5170 h4 => 1, h5 => 1, h6 => 1}->{$token->{tag_name}}) {
5171 if ($self->{open_elements}->[-1]->[1] == HEADING_EL) {
5172 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed',
5173 text => $self->{open_elements}->[-1]->[0]->manakai_local_name,
5174 token => $token);
5175 pop @{$self->{open_elements}};
5176 }
5177 }
5178
5179 ## 4. Insertion.
5180
5181 {
5182 my $el;
5183
5184 $el = $self->{document}->create_element_ns
5185 ($HTML_NS, [undef, $token->{tag_name}]);
5186
5187 for my $attr_name (keys %{ $token->{attributes}}) {
5188 my $attr_t = $token->{attributes}->{$attr_name};
5189 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
5190 $attr->value ($attr_t->{value});
5191 $attr->set_user_data (manakai_source_line => $attr_t->{line});
5192 $attr->set_user_data (manakai_source_column => $attr_t->{column});
5193 $el->set_attribute_node_ns ($attr);
5194 }
5195
5196 $el->set_user_data (manakai_source_line => $token->{line})
5197 if defined $token->{line};
5198 $el->set_user_data (manakai_source_column => $token->{column})
5199 if defined $token->{column};
5200
5201 $insert->($el);
5202 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
5203 }
5204
5205 if ($token->{tag_name} eq 'pre' or $token->{tag_name} eq 'listing') {
5206
5207 $token = $self->_get_next_token;
5208 if ($token->{type} == CHARACTER_TOKEN) {
5209 $token->{data} =~ s/^\x0A//;
5210 unless (length $token->{data}) {
5211
5212 $token = $self->_get_next_token;
5213 } else {
5214
5215 }
5216 } else {
5217
5218 }
5219
5220 delete $self->{frameset_ok};
5221 } elsif ($token->{tag_name} eq 'form') {
5222
5223 $self->{form_element} = $self->{open_elements}->[-1]->[0];
5224
5225
5226 $token = $self->_get_next_token;
5227 } elsif ($token->{tag_name} eq 'table') {
5228
5229 push @{$open_tables}, [$self->{open_elements}->[-1]->[0]];
5230
5231 delete $self->{frameset_ok};
5232
5233 $self->{insertion_mode} = IN_TABLE_IM;
5234
5235
5236 $token = $self->_get_next_token;
5237 } elsif ($token->{tag_name} eq 'hr') {
5238
5239 pop @{$self->{open_elements}};
5240
5241 delete $self->{self_closing};
5242
5243 delete $self->{frameset_ok};
5244
5245 $token = $self->_get_next_token;
5246 } else {
5247
5248 $token = $self->_get_next_token;
5249 }
5250 next B;
5251 } elsif ($token->{tag_name} eq 'li') {
5252 ## NOTE: As normal, but imply </li> when there's another <li> ...
5253
5254 ## NOTE: Special, Scope (<li><foo><li> == <li><foo><li/></foo></li>)::
5255 ## Interpreted as <li><foo/></li><li/> (non-conforming):
5256 ## blockquote (O9.27), center (O), dd (Fx3, O, S3.1.2, IE7),
5257 ## dt (Fx, O, S, IE), dl (O), fieldset (O, S, IE), form (Fx, O, S),
5258 ## hn (O), pre (O), applet (O, S), button (O, S), marquee (Fx, O, S),
5259 ## object (Fx)
5260 ## Generate non-tree (non-conforming):
5261 ## basefont (IE7 (where basefont is non-void)), center (IE),
5262 ## form (IE), hn (IE)
5263 ## address, div, p (<li><foo><li> == <li><foo/></li><li/>)::
5264 ## Interpreted as <li><foo><li/></foo></li> (non-conforming):
5265 ## div (Fx, S)
5266
5267 ## 1. Frameset-ng
5268 delete $self->{frameset_ok};
5269
5270 my $non_optional;
5271 my $i = -1;
5272
5273 ## 2.
5274 for my $node (reverse @{$self->{open_elements}}) {
5275 if ($node->[1] == LI_EL) {
5276 ## 3. (a) As if </li>
5277 {
5278 ## If no </li> - not applied
5279 #
5280
5281 ## Otherwise
5282
5283 ## 1. generate implied end tags, except for </li>
5284 #
5285
5286 ## 2. If current node != "li", parse error
5287 if ($non_optional) {
5288 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed',
5289 text => $non_optional->[0]->manakai_local_name,
5290 token => $token);
5291
5292 } else {
5293
5294 }
5295
5296 ## 3. Pop
5297 splice @{$self->{open_elements}}, $i;
5298 }
5299
5300 last; ## 3. (b) goto 5.
5301 } elsif (
5302 ## NOTE: not "formatting" and not "phrasing"
5303 ($node->[1] & SPECIAL_EL or
5304 $node->[1] & SCOPING_EL) and
5305 ## NOTE: "li", "dt", and "dd" are in |SPECIAL_EL|.
5306 (not $node->[1] & ADDRESS_DIV_P_EL)
5307 ) {
5308 ## 4.
5309
5310 last; ## goto 6.
5311 } elsif ($node->[1] & END_TAG_OPTIONAL_EL) {
5312
5313 #
5314 } else {
5315
5316 $non_optional ||= $node;
5317 #
5318 }
5319 ## 5.
5320 ## goto 3.
5321 $i--;
5322 }
5323
5324 ## 6. (a) has a |p| element in scope
5325 INSCOPE: for (reverse @{$self->{open_elements}}) {
5326 if ($_->[1] == P_EL) {
5327
5328
5329 ## NOTE: |<p><li>|, for example.
5330
5331
5332 $token->{self_closing} = $self->{self_closing};
5333 unshift @{$self->{token}}, $token;
5334 delete $self->{self_closing};
5335 # <x>
5336 $token = {type => END_TAG_TOKEN, tag_name => 'p',
5337 line => $token->{line}, column => $token->{column}};
5338 next B;
5339 } elsif ($_->[1] & SCOPING_EL) {
5340
5341 last INSCOPE;
5342 }
5343 } # INSCOPE
5344
5345 ## 6. (b) insert
5346
5347 {
5348 my $el;
5349
5350 $el = $self->{document}->create_element_ns
5351 ($HTML_NS, [undef, $token->{tag_name}]);
5352
5353 for my $attr_name (keys %{ $token->{attributes}}) {
5354 my $attr_t = $token->{attributes}->{$attr_name};
5355 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
5356 $attr->value ($attr_t->{value});
5357 $attr->set_user_data (manakai_source_line => $attr_t->{line});
5358 $attr->set_user_data (manakai_source_column => $attr_t->{column});
5359 $el->set_attribute_node_ns ($attr);
5360 }
5361
5362 $el->set_user_data (manakai_source_line => $token->{line})
5363 if defined $token->{line};
5364 $el->set_user_data (manakai_source_column => $token->{column})
5365 if defined $token->{column};
5366
5367 $insert->($el);
5368 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
5369 }
5370
5371
5372 $token = $self->_get_next_token;
5373 next B;
5374 } elsif ($token->{tag_name} eq 'dt' or
5375 $token->{tag_name} eq 'dd') {
5376 ## NOTE: As normal, but imply </dt> or </dd> when ...
5377
5378 ## 1. Frameset-ng
5379 delete $self->{frameset_ok};
5380
5381 my $non_optional;
5382 my $i = -1;
5383
5384 ## 2.
5385 for my $node (reverse @{$self->{open_elements}}) {
5386 if ($node->[1] == DTDD_EL) {
5387 ## 3. (a) As if </li>
5388 {
5389 ## If no </li> - not applied
5390 #
5391
5392 ## Otherwise
5393
5394 ## 1. generate implied end tags, except for </dt> or </dd>
5395 #
5396
5397 ## 2. If current node != "dt"|"dd", parse error
5398 if ($non_optional) {
5399 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed',
5400 text => $non_optional->[0]->manakai_local_name,
5401 token => $token);
5402
5403 } else {
5404
5405 }
5406
5407 ## 3. Pop
5408 splice @{$self->{open_elements}}, $i;
5409 }
5410
5411 last; ## 3. (b) goto 5.
5412 } elsif (
5413 ## NOTE: not "formatting" and not "phrasing"
5414 ($node->[1] & SPECIAL_EL or
5415 $node->[1] & SCOPING_EL) and
5416 ## NOTE: "li", "dt", and "dd" are in |SPECIAL_EL|.
5417
5418 (not $node->[1] & ADDRESS_DIV_P_EL)
5419 ) {
5420 ## 4.
5421
5422 last; ## goto 5.
5423 } elsif ($node->[1] & END_TAG_OPTIONAL_EL) {
5424
5425 #
5426 } else {
5427
5428 $non_optional ||= $node;
5429 #
5430 }
5431 ## 5.
5432 ## goto 3.
5433 $i--;
5434 }
5435
5436 ## 6. (a) has a |p| element in scope
5437 INSCOPE: for (reverse @{$self->{open_elements}}) {
5438 if ($_->[1] == P_EL) {
5439
5440
5441 $token->{self_closing} = $self->{self_closing};
5442 unshift @{$self->{token}}, $token;
5443 delete $self->{self_closing};
5444 # <x>
5445 $token = {type => END_TAG_TOKEN, tag_name => 'p',
5446 line => $token->{line}, column => $token->{column}};
5447 next B;
5448 } elsif ($_->[1] & SCOPING_EL) {
5449
5450 last INSCOPE;
5451 }
5452 } # INSCOPE
5453
5454 ## 6. (b) insert
5455
5456 {
5457 my $el;
5458
5459 $el = $self->{document}->create_element_ns
5460 ($HTML_NS, [undef, $token->{tag_name}]);
5461
5462 for my $attr_name (keys %{ $token->{attributes}}) {
5463 my $attr_t = $token->{attributes}->{$attr_name};
5464 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
5465 $attr->value ($attr_t->{value});
5466 $attr->set_user_data (manakai_source_line => $attr_t->{line});
5467 $attr->set_user_data (manakai_source_column => $attr_t->{column});
5468 $el->set_attribute_node_ns ($attr);
5469 }
5470
5471 $el->set_user_data (manakai_source_line => $token->{line})
5472 if defined $token->{line};
5473 $el->set_user_data (manakai_source_column => $token->{column})
5474 if defined $token->{column};
5475
5476 $insert->($el);
5477 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
5478 }
5479
5480
5481 $token = $self->_get_next_token;
5482 next B;
5483 } elsif ($token->{tag_name} eq 'plaintext') {
5484 ## NOTE: As normal, but effectively ends parsing
5485
5486 ## has a p element in scope
5487 INSCOPE: for (reverse @{$self->{open_elements}}) {
5488 if ($_->[1] == P_EL) {
5489
5490
5491 $token->{self_closing} = $self->{self_closing};
5492 unshift @{$self->{token}}, $token;
5493 delete $self->{self_closing};
5494 # <plaintext>
5495 $token = {type => END_TAG_TOKEN, tag_name => 'p',
5496 line => $token->{line}, column => $token->{column}};
5497 next B;
5498 } elsif ($_->[1] & SCOPING_EL) {
5499
5500 last INSCOPE;
5501 }
5502 } # INSCOPE
5503
5504
5505 {
5506 my $el;
5507
5508 $el = $self->{document}->create_element_ns
5509 ($HTML_NS, [undef, $token->{tag_name}]);
5510
5511 for my $attr_name (keys %{ $token->{attributes}}) {
5512 my $attr_t = $token->{attributes}->{$attr_name};
5513 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
5514 $attr->value ($attr_t->{value});
5515 $attr->set_user_data (manakai_source_line => $attr_t->{line});
5516 $attr->set_user_data (manakai_source_column => $attr_t->{column});
5517 $el->set_attribute_node_ns ($attr);
5518 }
5519
5520 $el->set_user_data (manakai_source_line => $token->{line})
5521 if defined $token->{line};
5522 $el->set_user_data (manakai_source_column => $token->{column})
5523 if defined $token->{column};
5524
5525 $insert->($el);
5526 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
5527 }
5528
5529
5530 $self->{content_model} = PLAINTEXT_CONTENT_MODEL;
5531
5532
5533 $token = $self->_get_next_token;
5534 next B;
5535 } elsif ($token->{tag_name} eq 'a') {
5536 AFE: for my $i (reverse 0..$#$active_formatting_elements) {
5537 my $node = $active_formatting_elements->[$i];
5538 if ($node->[1] == A_EL) {
5539
5540 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in a:a', token => $token);
5541
5542
5543 $token->{self_closing} = $self->{self_closing};
5544 unshift @{$self->{token}}, $token;
5545 delete $self->{self_closing};
5546 # <a>
5547 $token = {type => END_TAG_TOKEN, tag_name => 'a',
5548 line => $token->{line}, column => $token->{column}};
5549 $formatting_end_tag->($token);
5550
5551 AFE2: for (reverse 0..$#$active_formatting_elements) {
5552 if ($active_formatting_elements->[$_]->[0] eq $node->[0]) {
5553
5554 splice @$active_formatting_elements, $_, 1;
5555 last AFE2;
5556 }
5557 } # AFE2
5558 OE: for (reverse 0..$#{$self->{open_elements}}) {
5559 if ($self->{open_elements}->[$_]->[0] eq $node->[0]) {
5560
5561 splice @{$self->{open_elements}}, $_, 1;
5562 last OE;
5563 }
5564 } # OE
5565 last AFE;
5566 } elsif ($node->[0] eq '#marker') {
5567
5568 last AFE;
5569 }
5570 } # AFE
5571
5572 $reconstruct_active_formatting_elements->($insert_to_current);
5573
5574
5575 {
5576 my $el;
5577
5578 $el = $self->{document}->create_element_ns
5579 ($HTML_NS, [undef, $token->{tag_name}]);
5580
5581 for my $attr_name (keys %{ $token->{attributes}}) {
5582 my $attr_t = $token->{attributes}->{$attr_name};
5583 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
5584 $attr->value ($attr_t->{value});
5585 $attr->set_user_data (manakai_source_line => $attr_t->{line});
5586 $attr->set_user_data (manakai_source_column => $attr_t->{column});
5587 $el->set_attribute_node_ns ($attr);
5588 }
5589
5590 $el->set_user_data (manakai_source_line => $token->{line})
5591 if defined $token->{line};
5592 $el->set_user_data (manakai_source_column => $token->{column})
5593 if defined $token->{column};
5594
5595 $insert->($el);
5596 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
5597 }
5598
5599 push @$active_formatting_elements, $self->{open_elements}->[-1];
5600
5601
5602 $token = $self->_get_next_token;
5603 next B;
5604 } elsif ($token->{tag_name} eq 'nobr') {
5605 $reconstruct_active_formatting_elements->($insert_to_current);
5606
5607 ## has a |nobr| element in scope
5608 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5609 my $node = $self->{open_elements}->[$_];
5610 if ($node->[1] == NOBR_EL) {
5611
5612 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in nobr:nobr', token => $token);
5613
5614 $token->{self_closing} = $self->{self_closing};
5615 unshift @{$self->{token}}, $token;
5616 delete $self->{self_closing};
5617 # <nobr>
5618 $token = {type => END_TAG_TOKEN, tag_name => 'nobr',
5619 line => $token->{line}, column => $token->{column}};
5620 next B;
5621 } elsif ($node->[1] & SCOPING_EL) {
5622
5623 last INSCOPE;
5624 }
5625 } # INSCOPE
5626
5627
5628 {
5629 my $el;
5630
5631 $el = $self->{document}->create_element_ns
5632 ($HTML_NS, [undef, $token->{tag_name}]);
5633
5634 for my $attr_name (keys %{ $token->{attributes}}) {
5635 my $attr_t = $token->{attributes}->{$attr_name};
5636 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
5637 $attr->value ($attr_t->{value});
5638 $attr->set_user_data (manakai_source_line => $attr_t->{line});
5639 $attr->set_user_data (manakai_source_column => $attr_t->{column});
5640 $el->set_attribute_node_ns ($attr);
5641 }
5642
5643 $el->set_user_data (manakai_source_line => $token->{line})
5644 if defined $token->{line};
5645 $el->set_user_data (manakai_source_column => $token->{column})
5646 if defined $token->{column};
5647
5648 $insert->($el);
5649 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
5650 }
5651
5652 push @$active_formatting_elements, $self->{open_elements}->[-1];
5653
5654
5655 $token = $self->_get_next_token;
5656 next B;
5657 } elsif ($token->{tag_name} eq 'button') {
5658 ## has a button element in scope
5659 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5660 my $node = $self->{open_elements}->[$_];
5661 if ($node->[1] == BUTTON_EL) {
5662
5663 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in button:button', token => $token);
5664
5665 $token->{self_closing} = $self->{self_closing};
5666 unshift @{$self->{token}}, $token;
5667 delete $self->{self_closing};
5668 # <button>
5669 $token = {type => END_TAG_TOKEN, tag_name => 'button',
5670 line => $token->{line}, column => $token->{column}};
5671 next B;
5672 } elsif ($node->[1] & SCOPING_EL) {
5673
5674 last INSCOPE;
5675 }
5676 } # INSCOPE
5677
5678 $reconstruct_active_formatting_elements->($insert_to_current);
5679
5680
5681 {
5682 my $el;
5683
5684 $el = $self->{document}->create_element_ns
5685 ($HTML_NS, [undef, $token->{tag_name}]);
5686
5687 for my $attr_name (keys %{ $token->{attributes}}) {
5688 my $attr_t = $token->{attributes}->{$attr_name};
5689 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
5690 $attr->value ($attr_t->{value});
5691 $attr->set_user_data (manakai_source_line => $attr_t->{line});
5692 $attr->set_user_data (manakai_source_column => $attr_t->{column});
5693 $el->set_attribute_node_ns ($attr);
5694 }
5695
5696 $el->set_user_data (manakai_source_line => $token->{line})
5697 if defined $token->{line};
5698 $el->set_user_data (manakai_source_column => $token->{column})
5699 if defined $token->{column};
5700
5701 $insert->($el);
5702 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
5703 }
5704
5705
5706 ## TODO: associate with $self->{form_element} if defined
5707
5708 push @$active_formatting_elements, ['#marker', ''];
5709
5710 delete $self->{frameset_ok};
5711
5712
5713 $token = $self->_get_next_token;
5714 next B;
5715 } elsif ({
5716 xmp => 1,
5717 iframe => 1,
5718 noembed => 1,
5719 noframes => 1, ## NOTE: This is an "as if in head" code clone.
5720 noscript => 0, ## TODO: 1 if scripting is enabled
5721 }->{$token->{tag_name}}) {
5722 if ($token->{tag_name} eq 'xmp') {
5723
5724 $reconstruct_active_formatting_elements->($insert_to_current);
5725
5726 delete $self->{frameset_ok};
5727 } elsif ($token->{tag_name} eq 'iframe') {
5728
5729 delete $self->{frameset_ok};
5730 } else {
5731
5732 }
5733 ## NOTE: There is an "as if in body" code clone.
5734 $parse_rcdata->(CDATA_CONTENT_MODEL);
5735 next B;
5736 } elsif ($token->{tag_name} eq 'isindex') {
5737 $self->{parse_error}->(level => $self->{level}->{must}, type => 'isindex', token => $token);
5738
5739 if (defined $self->{form_element}) {
5740
5741 ## Ignore the token
5742 ## NOTE: Not acknowledged.
5743 $token = $self->_get_next_token;
5744 next B;
5745 } else {
5746 delete $self->{self_closing};
5747
5748 my $at = $token->{attributes};
5749 my $form_attrs;
5750 $form_attrs->{action} = $at->{action} if $at->{action};
5751 my $prompt_attr = $at->{prompt};
5752 $at->{name} = {name => 'name', value => 'isindex'};
5753 delete $at->{action};
5754 delete $at->{prompt};
5755 my @tokens = (
5756 {type => START_TAG_TOKEN, tag_name => 'form',
5757 attributes => $form_attrs,
5758 line => $token->{line}, column => $token->{column}},
5759 {type => START_TAG_TOKEN, tag_name => 'hr',
5760 line => $token->{line}, column => $token->{column}},
5761 {type => START_TAG_TOKEN, tag_name => 'label',
5762 line => $token->{line}, column => $token->{column}},
5763 );
5764 if ($prompt_attr) {
5765
5766 push @tokens, {type => CHARACTER_TOKEN, data => $prompt_attr->{value},
5767 #line => $token->{line}, column => $token->{column},
5768 };
5769 } else {
5770
5771 push @tokens, {type => CHARACTER_TOKEN,
5772 data => 'This is a searchable index. Insert your search keywords here: ',
5773 #line => $token->{line}, column => $token->{column},
5774 }; # SHOULD
5775 ## TODO: make this configurable
5776 }
5777 push @tokens,
5778 {type => START_TAG_TOKEN, tag_name => 'input', attributes => $at,
5779 line => $token->{line}, column => $token->{column}},
5780 #{type => CHARACTER_TOKEN, data => ''}, # SHOULD
5781 {type => END_TAG_TOKEN, tag_name => 'label',
5782 line => $token->{line}, column => $token->{column}},
5783 {type => START_TAG_TOKEN, tag_name => 'hr',
5784 line => $token->{line}, column => $token->{column}},
5785 {type => END_TAG_TOKEN, tag_name => 'form',
5786 line => $token->{line}, column => $token->{column}};
5787 unshift @{$self->{token}}, (@tokens);
5788 $token = $self->_get_next_token;
5789 next B;
5790 }
5791 } elsif ($token->{tag_name} eq 'textarea') {
5792 ## 1. Insert
5793
5794 {
5795 my $el;
5796
5797 $el = $self->{document}->create_element_ns
5798 ($HTML_NS, [undef, $token->{tag_name}]);
5799
5800 for my $attr_name (keys %{ $token->{attributes}}) {
5801 my $attr_t = $token->{attributes}->{$attr_name};
5802 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
5803 $attr->value ($attr_t->{value});
5804 $attr->set_user_data (manakai_source_line => $attr_t->{line});
5805 $attr->set_user_data (manakai_source_column => $attr_t->{column});
5806 $el->set_attribute_node_ns ($attr);
5807 }
5808
5809 $el->set_user_data (manakai_source_line => $token->{line})
5810 if defined $token->{line};
5811 $el->set_user_data (manakai_source_column => $token->{column})
5812 if defined $token->{column};
5813
5814 $insert->($el);
5815 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
5816 }
5817
5818
5819 ## Step 2 # XXX
5820 ## TODO: $self->{form_element} if defined
5821
5822 ## 2. Drop U+000A LINE FEED
5823 $self->{ignore_newline} = 1;
5824
5825 ## 3. RCDATA
5826 $self->{content_model} = RCDATA_CONTENT_MODEL;
5827 delete $self->{escape}; # MUST
5828
5829 ## 4., 6. Insertion mode
5830 $self->{insertion_mode} |= IN_CDATA_RCDATA_IM;
5831
5832 ## 5. Frameset-ng.
5833 delete $self->{frameset_ok};
5834
5835
5836 $token = $self->_get_next_token;
5837 next B;
5838 } elsif ($token->{tag_name} eq 'optgroup' or
5839 $token->{tag_name} eq 'option') {
5840 ## has an |option| element in scope
5841 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5842 my $node = $self->{open_elements}->[$_];
5843 if ($node->[1] == OPTION_EL) {
5844
5845 ## NOTE: As if </option>
5846
5847 $token->{self_closing} = $self->{self_closing};
5848 unshift @{$self->{token}}, $token;
5849 delete $self->{self_closing};
5850 # <option> or <optgroup>
5851 $token = {type => END_TAG_TOKEN, tag_name => 'option',
5852 line => $token->{line}, column => $token->{column}};
5853 next B;
5854 } elsif ($node->[1] & SCOPING_EL) {
5855
5856 last INSCOPE;
5857 }
5858 } # INSCOPE
5859
5860 $reconstruct_active_formatting_elements->($insert_to_current);
5861
5862
5863 {
5864 my $el;
5865
5866 $el = $self->{document}->create_element_ns
5867 ($HTML_NS, [undef, $token->{tag_name}]);
5868
5869 for my $attr_name (keys %{ $token->{attributes}}) {
5870 my $attr_t = $token->{attributes}->{$attr_name};
5871 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
5872 $attr->value ($attr_t->{value});
5873 $attr->set_user_data (manakai_source_line => $attr_t->{line});
5874 $attr->set_user_data (manakai_source_column => $attr_t->{column});
5875 $el->set_attribute_node_ns ($attr);
5876 }
5877
5878 $el->set_user_data (manakai_source_line => $token->{line})
5879 if defined $token->{line};
5880 $el->set_user_data (manakai_source_column => $token->{column})
5881 if defined $token->{column};
5882
5883 $insert->($el);
5884 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
5885 }
5886
5887
5888
5889 $token = $self->_get_next_token;
5890 redo B;
5891 } elsif ($token->{tag_name} eq 'rt' or
5892 $token->{tag_name} eq 'rp') {
5893 ## has a |ruby| element in scope
5894 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5895 my $node = $self->{open_elements}->[$_];
5896 if ($node->[1] == RUBY_EL) {
5897
5898 ## generate implied end tags
5899 while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
5900
5901 pop @{$self->{open_elements}};
5902 }
5903 unless ($self->{open_elements}->[-1]->[1] == RUBY_EL) {
5904
5905 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed',
5906 text => $self->{open_elements}->[-1]->[0]
5907 ->manakai_local_name,
5908 token => $token);
5909 pop @{$self->{open_elements}}
5910 while not $self->{open_elements}->[-1]->[1] == RUBY_EL;
5911 }
5912 last INSCOPE;
5913 } elsif ($node->[1] & SCOPING_EL) {
5914
5915 last INSCOPE;
5916 }
5917 } # INSCOPE
5918
5919 ## TODO: <non-ruby><rt> is not allowed.
5920
5921
5922 {
5923 my $el;
5924
5925 $el = $self->{document}->create_element_ns
5926 ($HTML_NS, [undef, $token->{tag_name}]);
5927
5928 for my $attr_name (keys %{ $token->{attributes}}) {
5929 my $attr_t = $token->{attributes}->{$attr_name};
5930 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
5931 $attr->value ($attr_t->{value});
5932 $attr->set_user_data (manakai_source_line => $attr_t->{line});
5933 $attr->set_user_data (manakai_source_column => $attr_t->{column});
5934 $el->set_attribute_node_ns ($attr);
5935 }
5936
5937 $el->set_user_data (manakai_source_line => $token->{line})
5938 if defined $token->{line};
5939 $el->set_user_data (manakai_source_column => $token->{column})
5940 if defined $token->{column};
5941
5942 $insert->($el);
5943 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
5944 }
5945
5946
5947
5948 $token = $self->_get_next_token;
5949 redo B;
5950 } elsif ($token->{tag_name} eq 'math' or
5951 $token->{tag_name} eq 'svg') {
5952 $reconstruct_active_formatting_elements->($insert_to_current);
5953
5954 ## "Adjust MathML attributes" ('math' only) - done in insert-element-f
5955
5956 ## "adjust SVG attributes" ('svg' only) - done in insert-element-f
5957
5958 ## "adjust foreign attributes" - done in insert-element-f
5959
5960
5961 {
5962 my $el;
5963
5964 $el = $self->{document}->create_element_ns
5965 ($token->{tag_name} eq 'math' ? $MML_NS : $SVG_NS, [undef, $token->{tag_name}]);
5966
5967 for my $attr_name (keys %{ $token->{attributes}}) {
5968 my $attr_t = $token->{attributes}->{$attr_name};
5969 my $attr = $self->{document}->create_attribute_ns (
5970 @{
5971 $foreign_attr_xname->{$attr_name} ||
5972 [undef, [undef,
5973 ($token->{tag_name} eq 'math' ? $MML_NS : $SVG_NS) eq $SVG_NS ?
5974 ($svg_attr_name->{$attr_name} || $attr_name) :
5975 ($token->{tag_name} eq 'math' ? $MML_NS : $SVG_NS) eq $MML_NS ?
5976 ($attr_name eq 'definitionurl' ?
5977 'definitionURL' : $attr_name) :
5978 $attr_name]]
5979 }
5980 );
5981 $attr->value ($attr_t->{value});
5982 $attr->set_user_data (manakai_source_line => $attr_t->{line});
5983 $attr->set_user_data (manakai_source_column => $attr_t->{column});
5984 $el->set_attribute_node_ns ($attr);
5985 }
5986
5987 $el->set_user_data (manakai_source_line => $token->{line})
5988 if defined $token->{line};
5989 $el->set_user_data (manakai_source_column => $token->{column})
5990 if defined $token->{column};
5991
5992 $insert->($el);
5993 push @{$self->{open_elements}}, [$el, ($el_category_f->{$token->{tag_name} eq 'math' ? $MML_NS : $SVG_NS}->{ $token->{tag_name}} || 0) | FOREIGN_EL];
5994
5995 if ( $token->{attributes}->{xmlns} and $token->{attributes}->{xmlns}->{value} ne ($token->{tag_name} eq 'math' ? $MML_NS : $SVG_NS)) {
5996 $self->{parse_error}->(level => $self->{level}->{must}, type => 'bad namespace', token => $token);
5997 ## TODO: Error type documentation
5998 }
5999 if ( $token->{attributes}->{'xmlns:xlink'} and
6000 $token->{attributes}->{'xmlns:xlink'}->{value} ne q<http://www.w3.org/1999/xlink>) {
6001 $self->{parse_error}->(level => $self->{level}->{must}, type => 'bad namespace', token => $token);
6002 }
6003 }
6004
6005
6006 if ($self->{self_closing}) {
6007 pop @{$self->{open_elements}};
6008 delete $self->{self_closing};
6009 } else {
6010
6011 $self->{insertion_mode} |= IN_FOREIGN_CONTENT_IM;
6012 ## NOTE: |<body><math><mi><svg>| -> "in foreign content" insertion
6013 ## mode, "in body" (not "in foreign content") secondary insertion
6014 ## mode, maybe.
6015 }
6016
6017 $token = $self->_get_next_token;
6018 next B;
6019 } elsif ({
6020 caption => 1, col => 1, colgroup => 1, frame => 1,
6021 head => 1,
6022 tbody => 1, td => 1, tfoot => 1, th => 1,
6023 thead => 1, tr => 1,
6024 }->{$token->{tag_name}}) {
6025
6026 $self->{parse_error}->(level => $self->{level}->{must}, type => 'in body',
6027 text => $token->{tag_name}, token => $token);
6028 ## Ignore the token
6029 ## NOTE: |<col/>| or |<frame/>| here is an error.
6030 $token = $self->_get_next_token;
6031 next B;
6032 } elsif ($token->{tag_name} eq 'param' or
6033 $token->{tag_name} eq 'source') {
6034
6035 {
6036 my $el;
6037
6038 $el = $self->{document}->create_element_ns
6039 ($HTML_NS, [undef, $token->{tag_name}]);
6040
6041 for my $attr_name (keys %{ $token->{attributes}}) {
6042 my $attr_t = $token->{attributes}->{$attr_name};
6043 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
6044 $attr->value ($attr_t->{value});
6045 $attr->set_user_data (manakai_source_line => $attr_t->{line});
6046 $attr->set_user_data (manakai_source_column => $attr_t->{column});
6047 $el->set_attribute_node_ns ($attr);
6048 }
6049
6050 $el->set_user_data (manakai_source_line => $token->{line})
6051 if defined $token->{line};
6052 $el->set_user_data (manakai_source_column => $token->{column})
6053 if defined $token->{column};
6054
6055 $insert->($el);
6056 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
6057 }
6058
6059 pop @{$self->{open_elements}};
6060
6061 delete $self->{self_closing};
6062 $token = $self->_get_next_token;
6063 redo B;
6064 } else {
6065 if ($token->{tag_name} eq 'image') {
6066
6067 $self->{parse_error}->(level => $self->{level}->{must}, type => 'image', token => $token);
6068 $token->{tag_name} = 'img';
6069 } else {
6070
6071 }
6072
6073 ## NOTE: There is an "as if <br>" code clone.
6074 $reconstruct_active_formatting_elements->($insert_to_current);
6075
6076
6077 {
6078 my $el;
6079
6080 $el = $self->{document}->create_element_ns
6081 ($HTML_NS, [undef, $token->{tag_name}]);
6082
6083 for my $attr_name (keys %{ $token->{attributes}}) {
6084 my $attr_t = $token->{attributes}->{$attr_name};
6085 my $attr = $self->{document}->create_attribute_ns (undef, [undef, $attr_name]);
6086 $attr->value ($attr_t->{value});
6087 $attr->set_user_data (manakai_source_line => $attr_t->{line});
6088 $attr->set_user_data (manakai_source_column => $attr_t->{column});
6089 $el->set_attribute_node_ns ($attr);
6090 }
6091
6092 $el->set_user_data (manakai_source_line => $token->{line})
6093 if defined $token->{line};
6094 $el->set_user_data (manakai_source_column => $token->{column})
6095 if defined $token->{column};
6096
6097 $insert->($el);
6098 push @{$self->{open_elements}}, [$el, $el_category->{$token->{tag_name}} || 0];
6099 }
6100
6101
6102 if ({
6103 applet => 1, marquee => 1, object => 1,
6104 }->{$token->{tag_name}}) {
6105
6106
6107 push @$active_formatting_elements, ['#marker', ''];
6108
6109 delete $self->{frameset_ok};
6110
6111
6112 } elsif ({
6113 b => 1, big => 1, em => 1, font => 1, i => 1,
6114 s => 1, small => 1, strike => 1,
6115 strong => 1, tt => 1, u => 1,
6116 }->{$token->{tag_name}}) {
6117
6118 push @$active_formatting_elements, $self->{open_elements}->[-1];
6119
6120 } elsif ($token->{tag_name} eq 'input') {
6121
6122 ## TODO: associate with $self->{form_element} if defined
6123 pop @{$self->{open_elements}};
6124 delete $self->{self_closing};
6125 } elsif ({
6126 area => 1, basefont => 1, bgsound => 1, br => 1,
6127 embed => 1, img => 1, spacer => 1, wbr => 1,
6128 keygen => 1,
6129 }->{$token->{tag_name}}) {
6130
6131
6132 pop @{$self->{open_elements}};
6133
6134 delete $self->{frameset_ok};
6135
6136 delete $self->{self_closing};
6137 } elsif ($token->{tag_name} eq 'select') {
6138 ## TODO: associate with $self->{form_element} if defined
6139
6140 delete $self->{frameset_ok};
6141
6142 if ($self->{insertion_mode} & TABLE_IMS or
6143 $self->{insertion_mode} & BODY_TABLE_IMS or
6144 ($self->{insertion_mode} & IM_MASK) == IN_COLUMN_GROUP_IM) {
6145
6146 $self->{insertion_mode} = IN_SELECT_IN_TABLE_IM;
6147 } else {
6148
6149 $self->{insertion_mode} = IN_SELECT_IM;
6150 }
6151
6152 } else {
6153
6154 }
6155
6156 $token = $self->_get_next_token;
6157 next B;
6158 }
6159 } elsif ($token->{type} == END_TAG_TOKEN) {
6160 if ($token->{tag_name} eq 'body' or $token->{tag_name} eq 'html') {
6161
6162 ## 1. If not "have an element in scope":
6163 ## "has a |body| element in scope"
6164 my $i;
6165 INSCOPE: {
6166 for (reverse @{$self->{open_elements}}) {
6167 if ($_->[1] == BODY_EL) {
6168
6169 $i = $_;
6170 last INSCOPE;
6171 } elsif ($_->[1] & SCOPING_EL) {
6172
6173 last;
6174 }
6175 }
6176
6177 ## NOTE: |<marquee></body>|, |<svg><foreignobject></body>|,
6178 ## and fragment cases.
6179
6180 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
6181 text => $token->{tag_name}, token => $token);
6182 ## Ignore the token. (</body> or </html>)
6183 $token = $self->_get_next_token;
6184 next B;
6185 } # INSCOPE
6186
6187 ## 2. If unclosed elements:
6188 for (@{$self->{open_elements}}) {
6189 unless ($_->[1] & ALL_END_TAG_OPTIONAL_EL ||
6190 $_->[1] == OPTGROUP_EL ||
6191 $_->[1] == OPTION_EL ||
6192 $_->[1] == RUBY_COMPONENT_EL) {
6193
6194 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed',
6195 text => $_->[0]->manakai_local_name,
6196 token => $token);
6197 last;
6198 } else {
6199
6200 }
6201 }
6202
6203 ## 3. Switch the insertion mode.
6204 $self->{insertion_mode} = AFTER_BODY_IM;
6205 if ($token->{tag_name} eq 'body') {
6206 $token = $self->_get_next_token;
6207 } else { # html
6208 ## Reprocess.
6209 }
6210 next B;
6211 } elsif ({
6212 ## NOTE: End tags for non-phrasing flow content elements
6213
6214 ## NOTE: The normal ones
6215 address => 1, article => 1, aside => 1, blockquote => 1,
6216 center => 1, datagrid => 1, details => 1, dialog => 1,
6217 dir => 1, div => 1, dl => 1, fieldset => 1, figure => 1,
6218 footer => 1, header => 1, hgroup => 1,
6219 listing => 1, menu => 1, nav => 1,
6220 ol => 1, pre => 1, section => 1, ul => 1,
6221
6222 ## NOTE: As normal, but ... optional tags
6223 dd => 1, dt => 1, li => 1,
6224
6225 applet => 1, button => 1, marquee => 1, object => 1,
6226 }->{$token->{tag_name}}) {
6227 ## NOTE: Code for <li> start tags includes "as if </li>" code.
6228 ## Code for <dt> or <dd> start tags includes "as if </dt> or
6229 ## </dd>" code.
6230
6231 ## has an element in scope
6232 my $i;
6233 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6234 my $node = $self->{open_elements}->[$_];
6235 if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
6236
6237 $i = $_;
6238 last INSCOPE;
6239 } elsif ($node->[1] & SCOPING_EL) {
6240
6241 last INSCOPE;
6242 }
6243 } # INSCOPE
6244
6245 unless (defined $i) { # has an element in scope
6246
6247 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
6248 text => $token->{tag_name}, token => $token);
6249 ## NOTE: Ignore the token.
6250 } else {
6251 ## Step 1. generate implied end tags
6252 while ({
6253 ## END_TAG_OPTIONAL_EL
6254 dd => ($token->{tag_name} ne 'dd'),
6255 dt => ($token->{tag_name} ne 'dt'),
6256 li => ($token->{tag_name} ne 'li'),
6257 option => 1,
6258 optgroup => 1,
6259 p => 1,
6260 rt => 1,
6261 rp => 1,
6262 }->{$self->{open_elements}->[-1]->[0]->manakai_local_name}) {
6263
6264 pop @{$self->{open_elements}};
6265 }
6266
6267 ## Step 2.
6268 if ($self->{open_elements}->[-1]->[0]->manakai_local_name
6269 ne $token->{tag_name}) {
6270
6271 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed',
6272 text => $self->{open_elements}->[-1]->[0]
6273 ->manakai_local_name,
6274 token => $token);
6275 } else {
6276
6277 }
6278
6279 ## Step 3.
6280 splice @{$self->{open_elements}}, $i;
6281
6282 ## Step 4.
6283 $clear_up_to_marker->()
6284 if {
6285 applet => 1, button => 1, marquee => 1, object => 1,
6286 }->{$token->{tag_name}};
6287 }
6288 $token = $self->_get_next_token;
6289 next B;
6290 } elsif ($token->{tag_name} eq 'form') {
6291 ## NOTE: As normal, but interacts with the form element pointer
6292
6293 undef $self->{form_element};
6294
6295 ## has an element in scope
6296 my $i;
6297 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6298 my $node = $self->{open_elements}->[$_];
6299 if ($node->[1] == FORM_EL) {
6300
6301 $i = $_;
6302 last INSCOPE;
6303 } elsif ($node->[1] & SCOPING_EL) {
6304
6305 last INSCOPE;
6306 }
6307 } # INSCOPE
6308
6309 unless (defined $i) { # has an element in scope
6310
6311 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
6312 text => $token->{tag_name}, token => $token);
6313 ## NOTE: Ignore the token.
6314 } else {
6315 ## Step 1. generate implied end tags
6316 while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
6317
6318 pop @{$self->{open_elements}};
6319 }
6320
6321 ## Step 2.
6322 if ($self->{open_elements}->[-1]->[0]->manakai_local_name
6323 ne $token->{tag_name}) {
6324
6325 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed',
6326 text => $self->{open_elements}->[-1]->[0]
6327 ->manakai_local_name,
6328 token => $token);
6329 } else {
6330
6331 }
6332
6333 ## Step 3.
6334 splice @{$self->{open_elements}}, $i;
6335 }
6336
6337 $token = $self->_get_next_token;
6338 next B;
6339 } elsif ({
6340 ## NOTE: As normal, except acts as a closer for any ...
6341 h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,
6342 }->{$token->{tag_name}}) {
6343 ## has an element in scope
6344 my $i;
6345 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6346 my $node = $self->{open_elements}->[$_];
6347 if ($node->[1] == HEADING_EL) {
6348
6349 $i = $_;
6350 last INSCOPE;
6351 } elsif ($node->[1] & SCOPING_EL) {
6352
6353 last INSCOPE;
6354 }
6355 } # INSCOPE
6356
6357 unless (defined $i) { # has an element in scope
6358
6359 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
6360 text => $token->{tag_name}, token => $token);
6361 ## NOTE: Ignore the token.
6362 } else {
6363 ## Step 1. generate implied end tags
6364 while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
6365
6366 pop @{$self->{open_elements}};
6367 }
6368
6369 ## Step 2.
6370 if ($self->{open_elements}->[-1]->[0]->manakai_local_name
6371 ne $token->{tag_name}) {
6372
6373 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
6374 text => $token->{tag_name}, token => $token);
6375 } else {
6376
6377 }
6378
6379 ## Step 3.
6380 splice @{$self->{open_elements}}, $i;
6381 }
6382
6383 $token = $self->_get_next_token;
6384 next B;
6385 } elsif ($token->{tag_name} eq 'p') {
6386 ## NOTE: As normal, except </p> implies <p> and ...
6387
6388 ## has an element in scope
6389 my $non_optional;
6390 my $i;
6391 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6392 my $node = $self->{open_elements}->[$_];
6393 if ($node->[1] == P_EL) {
6394
6395 $i = $_;
6396 last INSCOPE;
6397 } elsif ($node->[1] & SCOPING_EL) {
6398
6399 last INSCOPE;
6400 } elsif ($node->[1] & END_TAG_OPTIONAL_EL) {
6401 ## NOTE: |END_TAG_OPTIONAL_EL| includes "p"
6402
6403 #
6404 } else {
6405
6406 $non_optional ||= $node;
6407 #
6408 }
6409 } # INSCOPE
6410
6411 if (defined $i) {
6412 ## 1. Generate implied end tags
6413 #
6414
6415 ## 2. If current node != "p", parse error
6416 if ($non_optional) {
6417
6418 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed',
6419 text => $non_optional->[0]->manakai_local_name,
6420 token => $token);
6421 } else {
6422
6423 }
6424
6425 ## 3. Pop
6426 splice @{$self->{open_elements}}, $i;
6427 } else {
6428
6429 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
6430 text => $token->{tag_name}, token => $token);
6431
6432
6433 ## As if <p>, then reprocess the current token
6434 my $el;
6435
6436 $el = $self->{document}->create_element_ns
6437 ($HTML_NS, [undef, 'p']);
6438
6439 $el->set_user_data (manakai_source_line => $token->{line})
6440 if defined $token->{line};
6441 $el->set_user_data (manakai_source_column => $token->{column})
6442 if defined $token->{column};
6443
6444 $insert->($el);
6445 ## NOTE: Not inserted into |$self->{open_elements}|.
6446 }
6447
6448 $token = $self->_get_next_token;
6449 next B;
6450 } elsif ({
6451 a => 1,
6452 b => 1, big => 1, em => 1, font => 1, i => 1,
6453 nobr => 1, s => 1, small => 1, strike => 1,
6454 strong => 1, tt => 1, u => 1,
6455 }->{$token->{tag_name}}) {
6456
6457 $formatting_end_tag->($token);
6458 next B;
6459 } elsif ($token->{tag_name} eq 'br') {
6460
6461 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
6462 text => 'br', token => $token);
6463
6464 ## As if <br>
6465 $reconstruct_active_formatting_elements->($insert_to_current);
6466
6467 my $el;
6468
6469 $el = $self->{document}->create_element_ns
6470 ($HTML_NS, [undef, 'br']);
6471
6472 $el->set_user_data (manakai_source_line => $token->{line})
6473 if defined $token->{line};
6474 $el->set_user_data (manakai_source_column => $token->{column})
6475 if defined $token->{column};
6476
6477 $insert->($el);
6478
6479 ## Ignore the token.
6480 $token = $self->_get_next_token;
6481 next B;
6482 } else {
6483 if ($token->{tag_name} eq 'sarcasm') {
6484 sleep 0.001; # take a deep breath
6485 }
6486
6487 ## Step 1
6488 my $node_i = -1;
6489 my $node = $self->{open_elements}->[$node_i];
6490
6491 ## Step 2
6492 S2: {
6493 my $node_tag_name = $node->[0]->manakai_local_name;
6494 $node_tag_name =~ tr/A-Z/a-z/; # for SVG camelCase tag names
6495 if ($node_tag_name eq $token->{tag_name}) {
6496 ## Step 1
6497 ## generate implied end tags
6498 while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
6499
6500 ## NOTE: |<ruby><rt></ruby>|.
6501 ## ISSUE: <ruby><rt></rt> will also take this code path,
6502 ## which seems wrong.
6503 pop @{$self->{open_elements}};
6504 $node_i++;
6505 }
6506
6507 ## Step 2
6508 my $current_tag_name
6509 = $self->{open_elements}->[-1]->[0]->manakai_local_name;
6510 $current_tag_name =~ tr/A-Z/a-z/;
6511 if ($current_tag_name ne $token->{tag_name}) {
6512
6513 ## NOTE: <x><y></x>
6514 $self->{parse_error}->(level => $self->{level}->{must}, type => 'not closed',
6515 text => $self->{open_elements}->[-1]->[0]
6516 ->manakai_local_name,
6517 token => $token);
6518 } else {
6519
6520 }
6521
6522 ## Step 3
6523 splice @{$self->{open_elements}}, $node_i if $node_i < 0;
6524
6525 $token = $self->_get_next_token;
6526 last S2;
6527 } else {
6528 ## Step 3
6529 if (not ($node->[1] & FORMATTING_EL) and
6530 #not $phrasing_category->{$node->[1]} and
6531 ($node->[1] & SPECIAL_EL or
6532 $node->[1] & SCOPING_EL)) {
6533
6534 $self->{parse_error}->(level => $self->{level}->{must}, type => 'unmatched end tag',
6535 text => $token->{tag_name}, token => $token);
6536 ## Ignore the token
6537 $token = $self->_get_next_token;
6538 last S2;
6539
6540 ## NOTE: |<span><dd></span>a|: In Safari 3.1.2 and Opera
6541 ## 9.27, "a" is a child of <dd> (conforming). In
6542 ## Firefox 3.0.2, "a" is a child of <body>. In WinIE 7,
6543 ## "a" is a child of both <body> and <dd>.
6544 }
6545
6546
6547 }
6548
6549 ## Step 4
6550 $node_i--;
6551 $node = $self->{open_elements}->[$node_i];
6552
6553 ## Step 5;
6554 redo S2;
6555 } # S2
6556 next B;
6557 }
6558 }
6559 next B;
6560 } continue { # B
6561 if ($self->{insertion_mode} & IN_FOREIGN_CONTENT_IM) {
6562 ## NOTE: The code below is executed in cases where it does not have
6563 ## to be, but it it is harmless even in those cases.
6564 ## has an element in scope
6565 INSCOPE: {
6566 for (reverse 0..$#{$self->{open_elements}}) {
6567 my $node = $self->{open_elements}->[$_];
6568 if ($node->[1] & FOREIGN_EL) {
6569 last INSCOPE;
6570 } elsif ($node->[1] & SCOPING_EL) {
6571 last;
6572 }
6573 }
6574
6575 ## NOTE: No foreign element in scope.
6576 $self->{insertion_mode} &= ~ IN_FOREIGN_CONTENT_IM;
6577 } # INSCOPE
6578 }
6579 } # B
6580
6581 ## Stop parsing # MUST
6582
6583 ## TODO: script stuffs
6584 } # _tree_construct_main
6585
6586 ## XXX: How this method is organized is somewhat out of date, although
6587 ## it still does what the current spec documents.
6588 sub set_inner_html ($$$$;$) {
6589 my $class = shift;
6590 my $node = shift; # /context/
6591 #my $s = \$_[0];
6592 my $onerror = $_[1];
6593 my $get_wrapper = $_[2] || sub ($) { return $_[0] };
6594
6595 my $nt = $node->node_type;
6596 if ($nt == 9) { # Document (invoke the algorithm with no /context/ element)
6597 # MUST
6598
6599 ## Step 1 # MUST
6600 ## TODO: If the document has an active parser, ...
6601 ## ISSUE: There is an issue in the spec.
6602
6603 ## Step 2 # MUST
6604 my @cn = @{$node->child_nodes};
6605 for (@cn) {
6606 $node->remove_child ($_);
6607 }
6608
6609 ## Step 3, 4, 5 # MUST
6610 $class->parse_char_string ($_[0] => $node, $onerror, $get_wrapper);
6611 } elsif ($nt == 1) { # Element (invoke the algorithm with /context/ element)
6612 ## TODO: If non-html element
6613
6614 ## NOTE: Most of this code is copied from |parse_string|
6615
6616 ## TODO: Support for $get_wrapper
6617
6618 ## F1. Create an HTML document.
6619 my $this_doc = $node->owner_document;
6620 my $doc = $this_doc->implementation->create_document;
6621 $doc->manakai_is_html (1);
6622
6623 ## F2. Propagate quirkness flag
6624 my $node_doc = $node->owner_document;
6625 $doc->manakai_compat_mode ($node_doc->manakai_compat_mode);
6626
6627 ## F3. Create an HTML parser
6628 my $p = $class->new;
6629 $p->{document} = $doc;
6630
6631 ## Step 8 # MUST
6632 my $i = 0;
6633 $p->{line_prev} = $p->{line} = 1;
6634 $p->{column_prev} = $p->{column} = 0;
6635 require Whatpm::Charset::DecodeHandle;
6636 my $input = Whatpm::Charset::DecodeHandle::CharString->new (\($_[0]));
6637 $input = $get_wrapper->($input);
6638 $p->{set_nc} = sub {
6639 my $self = shift;
6640
6641 my $char = '';
6642 if (defined $self->{next_nc}) {
6643 $char = $self->{next_nc};
6644 delete $self->{next_nc};
6645 $self->{nc} = ord $char;
6646 } else {
6647 $self->{char_buffer} = '';
6648 $self->{char_buffer_pos} = 0;
6649
6650 my $count = $input->manakai_read_until
6651 ($self->{char_buffer}, qr/[^\x00\x0A\x0D]/,
6652 $self->{char_buffer_pos});
6653 if ($count) {
6654 $self->{line_prev} = $self->{line};
6655 $self->{column_prev} = $self->{column};
6656 $self->{column}++;
6657 $self->{nc}
6658 = ord substr ($self->{char_buffer},
6659 $self->{char_buffer_pos}++, 1);
6660 return;
6661 }
6662
6663 if ($input->read ($char, 1)) {
6664 $self->{nc} = ord $char;
6665 } else {
6666 $self->{nc} = -1;
6667 return;
6668 }
6669 }
6670
6671 ($p->{line_prev}, $p->{column_prev}) = ($p->{line}, $p->{column});
6672 $p->{column}++;
6673
6674 if ($self->{nc} == 0x000A) { # LF
6675 $p->{line}++;
6676 $p->{column} = 0;
6677
6678 } elsif ($self->{nc} == 0x000D) { # CR
6679 ## TODO: support for abort/streaming
6680 my $next = '';
6681 if ($input->read ($next, 1) and $next ne "\x0A") {
6682 $self->{next_nc} = $next;
6683 }
6684 $self->{nc} = 0x000A; # LF # MUST
6685 $p->{line}++;
6686 $p->{column} = 0;
6687
6688 } elsif ($self->{nc} == 0x0000) { # NULL
6689
6690 $self->{parse_error}->(level => $self->{level}->{must}, type => 'NULL');
6691 $self->{nc} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
6692 }
6693 };
6694
6695 $p->{read_until} = sub {
6696 #my ($scalar, $specials_range, $offset) = @_;
6697 return 0 if defined $p->{next_nc};
6698
6699 my $pattern = qr/[^$_[1]\x00\x0A\x0D]/;
6700 my $offset = $_[2] || 0;
6701
6702 if ($p->{char_buffer_pos} < length $p->{char_buffer}) {
6703 pos ($p->{char_buffer}) = $p->{char_buffer_pos};
6704 if ($p->{char_buffer} =~ /\G(?>$pattern)+/) {
6705 substr ($_[0], $offset)
6706 = substr ($p->{char_buffer}, $-[0], $+[0] - $-[0]);
6707 my $count = $+[0] - $-[0];
6708 if ($count) {
6709 $p->{column} += $count;
6710 $p->{char_buffer_pos} += $count;
6711 $p->{line_prev} = $p->{line};
6712 $p->{column_prev} = $p->{column} - 1;
6713 $p->{nc} = -1;
6714 }
6715 return $count;
6716 } else {
6717 return 0;
6718 }
6719 } else {
6720 my $count = $input->manakai_read_until ($_[0], $pattern, $_[2]);
6721 if ($count) {
6722 $p->{column} += $count;
6723 $p->{column_prev} += $count;
6724 $p->{nc} = -1;
6725 }
6726 return $count;
6727 }
6728 }; # $p->{read_until}
6729
6730 my $ponerror = $onerror || sub {
6731 my (%opt) = @_;
6732 my $line = $opt{line};
6733 my $column = $opt{column};
6734 if (defined $opt{token} and defined $opt{token}->{line}) {
6735 $line = $opt{token}->{line};
6736 $column = $opt{token}->{column};
6737 }
6738 warn "Parse error ($opt{type}) at line $line column $column\n";
6739 };
6740 $p->{parse_error} = sub {
6741 $ponerror->(line => $p->{line}, column => $p->{column}, @_);
6742 };
6743
6744 my $char_onerror = sub {
6745 my (undef, $type, %opt) = @_;
6746 $ponerror->(layer => 'encode',
6747 line => $p->{line}, column => $p->{column} + 1,
6748 %opt, type => $type);
6749 }; # $char_onerror
6750 $input->onerror ($char_onerror);
6751
6752 $p->_initialize_tokenizer;
6753 $p->_initialize_tree_constructor;
6754
6755 ## F4. If /context/ is not undef...
6756
6757 ## F4.1. content model flag
6758 my $node_ln = $node->manakai_local_name;
6759 $p->{content_model} = {
6760 title => RCDATA_CONTENT_MODEL,
6761 textarea => RCDATA_CONTENT_MODEL,
6762 style => CDATA_CONTENT_MODEL,
6763 script => CDATA_CONTENT_MODEL,
6764 xmp => CDATA_CONTENT_MODEL,
6765 iframe => CDATA_CONTENT_MODEL,
6766 noembed => CDATA_CONTENT_MODEL,
6767 noframes => CDATA_CONTENT_MODEL,
6768 noscript => CDATA_CONTENT_MODEL,
6769 plaintext => PLAINTEXT_CONTENT_MODEL,
6770 }->{$node_ln};
6771 $p->{content_model} = PCDATA_CONTENT_MODEL
6772 unless defined $p->{content_model};
6773
6774 $p->{inner_html_node} = [$node, $el_category->{$node_ln}];
6775 ## TODO: Foreign element OK?
6776
6777 ## F4.2. Root |html| element
6778 my $root = $doc->create_element_ns
6779 ('http://www.w3.org/1999/xhtml', [undef, 'html']);
6780
6781 ## F4.3.
6782 $doc->append_child ($root);
6783
6784 ## F4.4.
6785 push @{$p->{open_elements}}, [$root, $el_category->{html}];
6786
6787 undef $p->{head_element};
6788 undef $p->{head_element_inserted};
6789
6790 ## F4.5.
6791 $p->_reset_insertion_mode;
6792
6793 ## F4.6.
6794 my $anode = $node;
6795 AN: while (defined $anode) {
6796 if ($anode->node_type == 1) {
6797 my $nsuri = $anode->namespace_uri;
6798 if (defined $nsuri and $nsuri eq 'http://www.w3.org/1999/xhtml') {
6799 if ($anode->manakai_local_name eq 'form') {
6800
6801 $p->{form_element} = $anode;
6802 last AN;
6803 }
6804 }
6805 }
6806 $anode = $anode->parent_node;
6807 } # AN
6808
6809 ## F.5. Set the input stream.
6810 $p->{confident} = 1; ## Confident: irrelevant.
6811
6812 ## F.6. Start the parser.
6813 {
6814 my $self = $p;
6815 $token = $self->_get_next_token;
6816 }
6817 $p->_tree_construction_main;
6818
6819 ## F.7.
6820 my @cn = @{$node->child_nodes};
6821 for (@cn) {
6822 $node->remove_child ($_);
6823 }
6824 ## ISSUE: mutation events? read-only?
6825
6826 ## Step 11 # MUST
6827 @cn = @{$root->child_nodes};
6828 for (@cn) {
6829 $this_doc->adopt_node ($_);
6830 $node->append_child ($_);
6831 }
6832 ## ISSUE: mutation events?
6833
6834 $p->_terminate_tree_constructor;
6835
6836 delete $p->{parse_error}; # delete loop
6837 } else {
6838 die "$0: |set_inner_html| is not defined for node of type $nt";
6839 }
6840 } # set_inner_html
6841
6842 } # tree construction stage
6843
6844 package Whatpm::HTML::RestartParser;
6845 push our @ISA, 'Error';
6846
6847 1;
6848 # $Date: 2009/09/06 13:52:06 $

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24