File Coverage

blib/lib/HTML/HTML5/Parser/TagSoupParser.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


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