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

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

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.48 by wakaba, Sat Jul 21 09:54:45 2007 UTC revision 1.204 by wakaba, Sun Oct 5 05:59:35 2008 UTC
# Line 1  Line 1 
1  package Whatpm::HTML;  package Whatpm::HTML;
2  use strict;  use strict;
3  our $VERSION=do{my @r=(q$Revision$=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};  our $VERSION=do{my @r=(q$Revision$=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
4    use Error qw(:try);
5    
6    ## NOTE: This module don't check all HTML5 parse errors; character
7    ## encoding related parse errors are expected to be handled by relevant
8    ## modules.
9    ## Parse errors for control characters that are not allowed in HTML5
10    ## documents, for surrogate code points, and for noncharacter code
11    ## points, as well as U+FFFD substitions for characters whose code points
12    ## is higher than U+10FFFF may be detected by combining the parser with
13    ## the checker implemented by Whatpm::Charset::UnicodeChecker (for its
14    ## usage example, see |t/HTML-tree.t| in the Whatpm package or the
15    ## WebHACC::Language::HTML module in the WebHACC package).
16    
17  ## ISSUE:  ## ISSUE:
18  ## var doc = implementation.createDocument (null, null, null);  ## var doc = implementation.createDocument (null, null, null);
19  ## doc.write ('');  ## doc.write ('');
20  ## alert (doc.compatMode);  ## alert (doc.compatMode);
21    
22  ## ISSUE: HTML5 revision 967 says that the encoding layer MUST NOT  require IO::Handle;
23  ## strip BOM and the HTML layer MUST ignore it.  Whether we can do it  
24  ## is not yet clear.  my $HTML_NS = q<http://www.w3.org/1999/xhtml>;
25  ## "{U+FEFF}..." in UTF-16BE/UTF-16LE is three or four characters?  my $MML_NS = q<http://www.w3.org/1998/Math/MathML>;
26  ## "{U+FEFF}..." in GB18030?  my $SVG_NS = q<http://www.w3.org/2000/svg>;
27    my $XLINK_NS = q<http://www.w3.org/1999/xlink>;
28  my $permitted_slash_tag_name = {  my $XML_NS = q<http://www.w3.org/XML/1998/namespace>;
29    base => 1,  my $XMLNS_NS = q<http://www.w3.org/2000/xmlns/>;
30    link => 1,  
31    meta => 1,  sub A_EL () { 0b1 }
32    hr => 1,  sub ADDRESS_EL () { 0b10 }
33    br => 1,  sub BODY_EL () { 0b100 }
34    img=> 1,  sub BUTTON_EL () { 0b1000 }
35    embed => 1,  sub CAPTION_EL () { 0b10000 }
36    param => 1,  sub DD_EL () { 0b100000 }
37    area => 1,  sub DIV_EL () { 0b1000000 }
38    col => 1,  sub DT_EL () { 0b10000000 }
39    input => 1,  sub FORM_EL () { 0b100000000 }
40    sub FORMATTING_EL () { 0b1000000000 }
41    sub FRAMESET_EL () { 0b10000000000 }
42    sub HEADING_EL () { 0b100000000000 }
43    sub HTML_EL () { 0b1000000000000 }
44    sub LI_EL () { 0b10000000000000 }
45    sub NOBR_EL () { 0b100000000000000 }
46    sub OPTION_EL () { 0b1000000000000000 }
47    sub OPTGROUP_EL () { 0b10000000000000000 }
48    sub P_EL () { 0b100000000000000000 }
49    sub SELECT_EL () { 0b1000000000000000000 }
50    sub TABLE_EL () { 0b10000000000000000000 }
51    sub TABLE_CELL_EL () { 0b100000000000000000000 }
52    sub TABLE_ROW_EL () { 0b1000000000000000000000 }
53    sub TABLE_ROW_GROUP_EL () { 0b10000000000000000000000 }
54    sub MISC_SCOPING_EL () { 0b100000000000000000000000 }
55    sub MISC_SPECIAL_EL () { 0b1000000000000000000000000 }
56    sub FOREIGN_EL () { 0b10000000000000000000000000 }
57    sub FOREIGN_FLOW_CONTENT_EL () { 0b100000000000000000000000000 }
58    sub MML_AXML_EL () { 0b1000000000000000000000000000 }
59    sub RUBY_EL () { 0b10000000000000000000000000000 }
60    sub RUBY_COMPONENT_EL () { 0b100000000000000000000000000000 }
61    
62    sub TABLE_ROWS_EL () {
63      TABLE_EL |
64      TABLE_ROW_EL |
65      TABLE_ROW_GROUP_EL
66    }
67    
68    ## NOTE: Used in "generate implied end tags" algorithm.
69    ## NOTE: There is a code where a modified version of
70    ## END_TAG_OPTIONAL_EL is used in "generate implied end tags"
71    ## implementation (search for the algorithm name).
72    sub END_TAG_OPTIONAL_EL () {
73      DD_EL |
74      DT_EL |
75      LI_EL |
76      OPTION_EL |
77      OPTGROUP_EL |
78      P_EL |
79      RUBY_COMPONENT_EL
80    }
81    
82    ## NOTE: Used in </body> and EOF algorithms.
83    sub ALL_END_TAG_OPTIONAL_EL () {
84      DD_EL |
85      DT_EL |
86      LI_EL |
87      P_EL |
88    
89      ## ISSUE: option, optgroup, rt, rp?
90    
91      BODY_EL |
92      HTML_EL |
93      TABLE_CELL_EL |
94      TABLE_ROW_EL |
95      TABLE_ROW_GROUP_EL
96    }
97    
98    sub SCOPING_EL () {
99      BUTTON_EL |
100      CAPTION_EL |
101      HTML_EL |
102      TABLE_EL |
103      TABLE_CELL_EL |
104      MISC_SCOPING_EL
105    }
106    
107    sub TABLE_SCOPING_EL () {
108      HTML_EL |
109      TABLE_EL
110    }
111    
112    sub TABLE_ROWS_SCOPING_EL () {
113      HTML_EL |
114      TABLE_ROW_GROUP_EL
115    }
116    
117    sub TABLE_ROW_SCOPING_EL () {
118      HTML_EL |
119      TABLE_ROW_EL
120    }
121    
122    sub SPECIAL_EL () {
123      ADDRESS_EL |
124      BODY_EL |
125      DIV_EL |
126    
127      DD_EL |
128      DT_EL |
129      LI_EL |
130      P_EL |
131    
132      FORM_EL |
133      FRAMESET_EL |
134      HEADING_EL |
135      SELECT_EL |
136      TABLE_ROW_EL |
137      TABLE_ROW_GROUP_EL |
138      MISC_SPECIAL_EL
139    }
140    
141    my $el_category = {
142      a => A_EL | FORMATTING_EL,
143      address => ADDRESS_EL,
144      applet => MISC_SCOPING_EL,
145      area => MISC_SPECIAL_EL,
146      article => MISC_SPECIAL_EL,
147      aside => MISC_SPECIAL_EL,
148      b => FORMATTING_EL,
149      base => MISC_SPECIAL_EL,
150      basefont => MISC_SPECIAL_EL,
151      bgsound => MISC_SPECIAL_EL,
152      big => FORMATTING_EL,
153      blockquote => MISC_SPECIAL_EL,
154      body => BODY_EL,
155      br => MISC_SPECIAL_EL,
156      button => BUTTON_EL,
157      caption => CAPTION_EL,
158      center => MISC_SPECIAL_EL,
159      col => MISC_SPECIAL_EL,
160      colgroup => MISC_SPECIAL_EL,
161      command => MISC_SPECIAL_EL,
162      datagrid => MISC_SPECIAL_EL,
163      dd => DD_EL,
164      details => MISC_SPECIAL_EL,
165      dialog => MISC_SPECIAL_EL,
166      dir => MISC_SPECIAL_EL,
167      div => DIV_EL,
168      dl => MISC_SPECIAL_EL,
169      dt => DT_EL,
170      em => FORMATTING_EL,
171      embed => MISC_SPECIAL_EL,
172      eventsource => MISC_SPECIAL_EL,
173      fieldset => MISC_SPECIAL_EL,
174      figure => MISC_SPECIAL_EL,
175      font => FORMATTING_EL,
176      footer => MISC_SPECIAL_EL,
177      form => FORM_EL,
178      frame => MISC_SPECIAL_EL,
179      frameset => FRAMESET_EL,
180      h1 => HEADING_EL,
181      h2 => HEADING_EL,
182      h3 => HEADING_EL,
183      h4 => HEADING_EL,
184      h5 => HEADING_EL,
185      h6 => HEADING_EL,
186      head => MISC_SPECIAL_EL,
187      header => MISC_SPECIAL_EL,
188      hr => MISC_SPECIAL_EL,
189      html => HTML_EL,
190      i => FORMATTING_EL,
191      iframe => MISC_SPECIAL_EL,
192      img => MISC_SPECIAL_EL,
193      #image => MISC_SPECIAL_EL, ## NOTE: Commented out in the spec.
194      input => MISC_SPECIAL_EL,
195      isindex => MISC_SPECIAL_EL,
196      li => LI_EL,
197      link => MISC_SPECIAL_EL,
198      listing => MISC_SPECIAL_EL,
199      marquee => MISC_SCOPING_EL,
200      menu => MISC_SPECIAL_EL,
201      meta => MISC_SPECIAL_EL,
202      nav => MISC_SPECIAL_EL,
203      nobr => NOBR_EL | FORMATTING_EL,
204      noembed => MISC_SPECIAL_EL,
205      noframes => MISC_SPECIAL_EL,
206      noscript => MISC_SPECIAL_EL,
207      object => MISC_SCOPING_EL,
208      ol => MISC_SPECIAL_EL,
209      optgroup => OPTGROUP_EL,
210      option => OPTION_EL,
211      p => P_EL,
212      param => MISC_SPECIAL_EL,
213      plaintext => MISC_SPECIAL_EL,
214      pre => MISC_SPECIAL_EL,
215      rp => RUBY_COMPONENT_EL,
216      rt => RUBY_COMPONENT_EL,
217      ruby => RUBY_EL,
218      s => FORMATTING_EL,
219      script => MISC_SPECIAL_EL,
220      select => SELECT_EL,
221      section => MISC_SPECIAL_EL,
222      small => FORMATTING_EL,
223      spacer => MISC_SPECIAL_EL,
224      strike => FORMATTING_EL,
225      strong => FORMATTING_EL,
226      style => MISC_SPECIAL_EL,
227      table => TABLE_EL,
228      tbody => TABLE_ROW_GROUP_EL,
229      td => TABLE_CELL_EL,
230      textarea => MISC_SPECIAL_EL,
231      tfoot => TABLE_ROW_GROUP_EL,
232      th => TABLE_CELL_EL,
233      thead => TABLE_ROW_GROUP_EL,
234      title => MISC_SPECIAL_EL,
235      tr => TABLE_ROW_EL,
236      tt => FORMATTING_EL,
237      u => FORMATTING_EL,
238      ul => MISC_SPECIAL_EL,
239      wbr => MISC_SPECIAL_EL,
240    };
241    
242    my $el_category_f = {
243      $MML_NS => {
244        'annotation-xml' => MML_AXML_EL,
245        mi => FOREIGN_FLOW_CONTENT_EL,
246        mo => FOREIGN_FLOW_CONTENT_EL,
247        mn => FOREIGN_FLOW_CONTENT_EL,
248        ms => FOREIGN_FLOW_CONTENT_EL,
249        mtext => FOREIGN_FLOW_CONTENT_EL,
250      },
251      $SVG_NS => {
252        foreignObject => FOREIGN_FLOW_CONTENT_EL | MISC_SCOPING_EL,
253        desc => FOREIGN_FLOW_CONTENT_EL,
254        title => FOREIGN_FLOW_CONTENT_EL,
255      },
256      ## NOTE: In addition, FOREIGN_EL is set to non-HTML elements.
257    };
258    
259    my $svg_attr_name = {
260      attributename => 'attributeName',
261      attributetype => 'attributeType',
262      basefrequency => 'baseFrequency',
263      baseprofile => 'baseProfile',
264      calcmode => 'calcMode',
265      clippathunits => 'clipPathUnits',
266      contentscripttype => 'contentScriptType',
267      contentstyletype => 'contentStyleType',
268      diffuseconstant => 'diffuseConstant',
269      edgemode => 'edgeMode',
270      externalresourcesrequired => 'externalResourcesRequired',
271      filterres => 'filterRes',
272      filterunits => 'filterUnits',
273      glyphref => 'glyphRef',
274      gradienttransform => 'gradientTransform',
275      gradientunits => 'gradientUnits',
276      kernelmatrix => 'kernelMatrix',
277      kernelunitlength => 'kernelUnitLength',
278      keypoints => 'keyPoints',
279      keysplines => 'keySplines',
280      keytimes => 'keyTimes',
281      lengthadjust => 'lengthAdjust',
282      limitingconeangle => 'limitingConeAngle',
283      markerheight => 'markerHeight',
284      markerunits => 'markerUnits',
285      markerwidth => 'markerWidth',
286      maskcontentunits => 'maskContentUnits',
287      maskunits => 'maskUnits',
288      numoctaves => 'numOctaves',
289      pathlength => 'pathLength',
290      patterncontentunits => 'patternContentUnits',
291      patterntransform => 'patternTransform',
292      patternunits => 'patternUnits',
293      pointsatx => 'pointsAtX',
294      pointsaty => 'pointsAtY',
295      pointsatz => 'pointsAtZ',
296      preservealpha => 'preserveAlpha',
297      preserveaspectratio => 'preserveAspectRatio',
298      primitiveunits => 'primitiveUnits',
299      refx => 'refX',
300      refy => 'refY',
301      repeatcount => 'repeatCount',
302      repeatdur => 'repeatDur',
303      requiredextensions => 'requiredExtensions',
304      requiredfeatures => 'requiredFeatures',
305      specularconstant => 'specularConstant',
306      specularexponent => 'specularExponent',
307      spreadmethod => 'spreadMethod',
308      startoffset => 'startOffset',
309      stddeviation => 'stdDeviation',
310      stitchtiles => 'stitchTiles',
311      surfacescale => 'surfaceScale',
312      systemlanguage => 'systemLanguage',
313      tablevalues => 'tableValues',
314      targetx => 'targetX',
315      targety => 'targetY',
316      textlength => 'textLength',
317      viewbox => 'viewBox',
318      viewtarget => 'viewTarget',
319      xchannelselector => 'xChannelSelector',
320      ychannelselector => 'yChannelSelector',
321      zoomandpan => 'zoomAndPan',
322    };
323    
324    my $foreign_attr_xname = {
325      'xlink:actuate' => [$XLINK_NS, ['xlink', 'actuate']],
326      'xlink:arcrole' => [$XLINK_NS, ['xlink', 'arcrole']],
327      'xlink:href' => [$XLINK_NS, ['xlink', 'href']],
328      'xlink:role' => [$XLINK_NS, ['xlink', 'role']],
329      'xlink:show' => [$XLINK_NS, ['xlink', 'show']],
330      'xlink:title' => [$XLINK_NS, ['xlink', 'title']],
331      'xlink:type' => [$XLINK_NS, ['xlink', 'type']],
332      'xml:base' => [$XML_NS, ['xml', 'base']],
333      'xml:lang' => [$XML_NS, ['xml', 'lang']],
334      'xml:space' => [$XML_NS, ['xml', 'space']],
335      'xmlns' => [$XMLNS_NS, [undef, 'xmlns']],
336      'xmlns:xlink' => [$XMLNS_NS, ['xmlns', 'xlink']],
337  };  };
338    
339  my $c1_entity_char = {  ## ISSUE: xmlns:xlink="non-xlink-ns" is not an error.
340    
341    my $charref_map = {
342      0x0D => 0x000A,
343    0x80 => 0x20AC,    0x80 => 0x20AC,
344    0x81 => 0xFFFD,    0x81 => 0xFFFD,
345    0x82 => 0x201A,    0x82 => 0x201A,
# Line 60  my $c1_entity_char = { Line 372  my $c1_entity_char = {
372    0x9D => 0xFFFD,    0x9D => 0xFFFD,
373    0x9E => 0x017E,    0x9E => 0x017E,
374    0x9F => 0x0178,    0x9F => 0x0178,
375  }; # $c1_entity_char  }; # $charref_map
376    $charref_map->{$_} = 0xFFFD
377        for 0x0000..0x0008, 0x000B, 0x000E..0x001F, 0x007F,
378            0xD800..0xDFFF, 0xFDD0..0xFDDF, ## ISSUE: 0xFDEF
379            0xFFFE, 0xFFFF, 0x1FFFE, 0x1FFFF, 0x2FFFE, 0x2FFFF, 0x3FFFE, 0x3FFFF,
380            0x4FFFE, 0x4FFFF, 0x5FFFE, 0x5FFFF, 0x6FFFE, 0x6FFFF, 0x7FFFE,
381            0x7FFFF, 0x8FFFE, 0x8FFFF, 0x9FFFE, 0x9FFFF, 0xAFFFE, 0xAFFFF,
382            0xBFFFE, 0xBFFFF, 0xCFFFE, 0xCFFFF, 0xDFFFE, 0xDFFFF, 0xEFFFE,
383            0xEFFFF, 0xFFFFE, 0xFFFFF, 0x10FFFE, 0x10FFFF;
384    
385  my $special_category = {  ## TODO: Invoke the reset algorithm when a resettable element is
386    address => 1, area => 1, base => 1, basefont => 1, bgsound => 1,  ## created (cf. HTML5 revision 2259).
   blockquote => 1, body => 1, br => 1, center => 1, col => 1, colgroup => 1,  
   dd => 1, dir => 1, div => 1, dl => 1, dt => 1, embed => 1, fieldset => 1,  
   form => 1, frame => 1, frameset => 1, h1 => 1, h2 => 1, h3 => 1,  
   h4 => 1, h5 => 1, h6 => 1, head => 1, hr => 1, iframe => 1, image => 1,  
   img => 1, input => 1, isindex => 1, li => 1, link => 1, listing => 1,  
   menu => 1, meta => 1, noembed => 1, noframes => 1, noscript => 1,  
   ol => 1, optgroup => 1, option => 1, p => 1, param => 1, plaintext => 1,  
   pre => 1, script => 1, select => 1, spacer => 1, style => 1, tbody => 1,  
   textarea => 1, tfoot => 1, thead => 1, title => 1, tr => 1, ul => 1, wbr => 1,  
 };  
 my $scoping_category = {  
   button => 1, caption => 1, html => 1, marquee => 1, object => 1,  
   table => 1, td => 1, th => 1,  
 };  
 my $formatting_category = {  
   a => 1, b => 1, big => 1, em => 1, font => 1, i => 1, nobr => 1,  
   s => 1, small => 1, strile => 1, strong => 1, tt => 1, u => 1,  
 };  
 # $phrasing_category: all other elements  
387    
388  sub parse_string ($$$;$) {  sub parse_byte_string ($$$$;$) {
389    my $self = shift->new;    my $self = shift;
390    my $s = \$_[0];    my $charset_name = shift;
391      open my $input, '<', ref $_[0] ? $_[0] : \($_[0]);
392      return $self->parse_byte_stream ($charset_name, $input, @_[1..$#_]);
393    } # parse_byte_string
394    
395    sub parse_byte_stream ($$$$;$$) {
396      # my ($self, $charset_name, $byte_stream, $doc, $onerror, $get_wrapper) = @_;
397      my $self = ref $_[0] ? shift : shift->new;
398      my $charset_name = shift;
399      my $byte_stream = $_[0];
400    
401      my $onerror = $_[2] || sub {
402        my (%opt) = @_;
403        warn "Parse error ($opt{type})\n";
404      };
405      $self->{parse_error} = $onerror; # updated later by parse_char_string
406    
407      my $get_wrapper = $_[3] || sub ($) {
408        return $_[0]; # $_[0] = byte stream handle, returned = arg to char handle
409      };
410    
411      ## HTML5 encoding sniffing algorithm
412      require Message::Charset::Info;
413      my $charset;
414      my $buffer;
415      my ($char_stream, $e_status);
416    
417      SNIFFING: {
418        ## NOTE: By setting |allow_fallback| option true when the
419        ## |get_decode_handle| method is invoked, we ignore what the HTML5
420        ## spec requires, i.e. unsupported encoding should be ignored.
421          ## TODO: We should not do this unless the parser is invoked
422          ## in the conformance checking mode, in which this behavior
423          ## would be useful.
424    
425        ## Step 1
426        if (defined $charset_name) {
427          $charset = Message::Charset::Info->get_by_html_name ($charset_name);
428              ## TODO: Is this ok?  Transfer protocol's parameter should be
429              ## interpreted in its semantics?
430    
431          ($char_stream, $e_status) = $charset->get_decode_handle
432              ($byte_stream, allow_error_reporting => 1,
433               allow_fallback => 1);
434          if ($char_stream) {
435            $self->{confident} = 1;
436            last SNIFFING;
437          } else {
438            !!!parse-error (type => 'charset:not supported',
439                            layer => 'encode',
440                            line => 1, column => 1,
441                            value => $charset_name,
442                            level => $self->{level}->{uncertain});
443          }
444        }
445    
446        ## Step 2
447        my $byte_buffer = '';
448        for (1..1024) {
449          my $char = $byte_stream->getc;
450          last unless defined $char;
451          $byte_buffer .= $char;
452        } ## TODO: timeout
453    
454        ## Step 3
455        if ($byte_buffer =~ /^\xFE\xFF/) {
456          $charset = Message::Charset::Info->get_by_html_name ('utf-16be');
457          ($char_stream, $e_status) = $charset->get_decode_handle
458              ($byte_stream, allow_error_reporting => 1,
459               allow_fallback => 1, byte_buffer => \$byte_buffer);
460          $self->{confident} = 1;
461          last SNIFFING;
462        } elsif ($byte_buffer =~ /^\xFF\xFE/) {
463          $charset = Message::Charset::Info->get_by_html_name ('utf-16le');
464          ($char_stream, $e_status) = $charset->get_decode_handle
465              ($byte_stream, allow_error_reporting => 1,
466               allow_fallback => 1, byte_buffer => \$byte_buffer);
467          $self->{confident} = 1;
468          last SNIFFING;
469        } elsif ($byte_buffer =~ /^\xEF\xBB\xBF/) {
470          $charset = Message::Charset::Info->get_by_html_name ('utf-8');
471          ($char_stream, $e_status) = $charset->get_decode_handle
472              ($byte_stream, allow_error_reporting => 1,
473               allow_fallback => 1, byte_buffer => \$byte_buffer);
474          $self->{confident} = 1;
475          last SNIFFING;
476        }
477    
478        ## Step 4
479        ## TODO: <meta charset>
480    
481        ## Step 5
482        ## TODO: from history
483    
484        ## Step 6
485        require Whatpm::Charset::UniversalCharDet;
486        $charset_name = Whatpm::Charset::UniversalCharDet->detect_byte_string
487            ($byte_buffer);
488        if (defined $charset_name) {
489          $charset = Message::Charset::Info->get_by_html_name ($charset_name);
490    
491          require Whatpm::Charset::DecodeHandle;
492          $buffer = Whatpm::Charset::DecodeHandle::ByteBuffer->new
493              ($byte_stream);
494          ($char_stream, $e_status) = $charset->get_decode_handle
495              ($buffer, allow_error_reporting => 1,
496               allow_fallback => 1, byte_buffer => \$byte_buffer);
497          if ($char_stream) {
498            $buffer->{buffer} = $byte_buffer;
499            !!!parse-error (type => 'sniffing:chardet',
500                            text => $charset_name,
501                            level => $self->{level}->{info},
502                            layer => 'encode',
503                            line => 1, column => 1);
504            $self->{confident} = 0;
505            last SNIFFING;
506          }
507        }
508    
509        ## Step 7: default
510        ## TODO: Make this configurable.
511        $charset = Message::Charset::Info->get_by_html_name ('windows-1252');
512            ## NOTE: We choose |windows-1252| here, since |utf-8| should be
513            ## detectable in the step 6.
514        require Whatpm::Charset::DecodeHandle;
515        $buffer = Whatpm::Charset::DecodeHandle::ByteBuffer->new
516            ($byte_stream);
517        ($char_stream, $e_status)
518            = $charset->get_decode_handle ($buffer,
519                                           allow_error_reporting => 1,
520                                           allow_fallback => 1,
521                                           byte_buffer => \$byte_buffer);
522        $buffer->{buffer} = $byte_buffer;
523        !!!parse-error (type => 'sniffing:default',
524                        text => 'windows-1252',
525                        level => $self->{level}->{info},
526                        line => 1, column => 1,
527                        layer => 'encode');
528        $self->{confident} = 0;
529      } # SNIFFING
530    
531      if ($e_status & Message::Charset::Info::FALLBACK_ENCODING_IMPL ()) {
532        $self->{input_encoding} = $charset->get_iana_name; ## TODO: Should we set actual charset decoder's encoding name?
533        !!!parse-error (type => 'chardecode:fallback',
534                        #text => $self->{input_encoding},
535                        level => $self->{level}->{uncertain},
536                        line => 1, column => 1,
537                        layer => 'encode');
538      } elsif (not ($e_status &
539                    Message::Charset::Info::ERROR_REPORTING_ENCODING_IMPL ())) {
540        $self->{input_encoding} = $charset->get_iana_name;
541        !!!parse-error (type => 'chardecode:no error',
542                        text => $self->{input_encoding},
543                        level => $self->{level}->{uncertain},
544                        line => 1, column => 1,
545                        layer => 'encode');
546      } else {
547        $self->{input_encoding} = $charset->get_iana_name;
548      }
549    
550      $self->{change_encoding} = sub {
551        my $self = shift;
552        $charset_name = shift;
553        my $token = shift;
554    
555        $charset = Message::Charset::Info->get_by_html_name ($charset_name);
556        ($char_stream, $e_status) = $charset->get_decode_handle
557            ($byte_stream, allow_error_reporting => 1, allow_fallback => 1,
558             byte_buffer => \ $buffer->{buffer});
559        
560        if ($char_stream) { # if supported
561          ## "Change the encoding" algorithm:
562    
563          ## Step 1    
564          if ($charset->{category} &
565              Message::Charset::Info::CHARSET_CATEGORY_UTF16 ()) {
566            $charset = Message::Charset::Info->get_by_html_name ('utf-8');
567            ($char_stream, $e_status) = $charset->get_decode_handle
568                ($byte_stream,
569                 byte_buffer => \ $buffer->{buffer});
570          }
571          $charset_name = $charset->get_iana_name;
572          
573          ## Step 2
574          if (defined $self->{input_encoding} and
575              $self->{input_encoding} eq $charset_name) {
576            !!!parse-error (type => 'charset label:matching',
577                            text => $charset_name,
578                            level => $self->{level}->{info});
579            $self->{confident} = 1;
580            return;
581          }
582    
583          !!!parse-error (type => 'charset label detected',
584                          text => $self->{input_encoding},
585                          value => $charset_name,
586                          level => $self->{level}->{warn},
587                          token => $token);
588          
589          ## Step 3
590          # if (can) {
591            ## change the encoding on the fly.
592            #$self->{confident} = 1;
593            #return;
594          # }
595          
596          ## Step 4
597          throw Whatpm::HTML::RestartParser ();
598        }
599      }; # $self->{change_encoding}
600    
601      my $char_onerror = sub {
602        my (undef, $type, %opt) = @_;
603        !!!parse-error (layer => 'encode',
604                        line => $self->{line}, column => $self->{column} + 1,
605                        %opt, type => $type);
606        if ($opt{octets}) {
607          ${$opt{octets}} = "\x{FFFD}"; # relacement character
608        }
609      };
610    
611      my $wrapped_char_stream = $get_wrapper->($char_stream);
612      $wrapped_char_stream->onerror ($char_onerror);
613    
614      my @args = ($_[1], $_[2]); # $doc, $onerror - $get_wrapper = undef;
615      my $return;
616      try {
617        $return = $self->parse_char_stream ($wrapped_char_stream, @args);  
618      } catch Whatpm::HTML::RestartParser with {
619        ## NOTE: Invoked after {change_encoding}.
620    
621        if ($e_status & Message::Charset::Info::FALLBACK_ENCODING_IMPL ()) {
622          $self->{input_encoding} = $charset->get_iana_name; ## TODO: Should we set actual charset decoder's encoding name?
623          !!!parse-error (type => 'chardecode:fallback',
624                          level => $self->{level}->{uncertain},
625                          #text => $self->{input_encoding},
626                          line => 1, column => 1,
627                          layer => 'encode');
628        } elsif (not ($e_status &
629                      Message::Charset::Info::ERROR_REPORTING_ENCODING_IMPL ())) {
630          $self->{input_encoding} = $charset->get_iana_name;
631          !!!parse-error (type => 'chardecode:no error',
632                          text => $self->{input_encoding},
633                          level => $self->{level}->{uncertain},
634                          line => 1, column => 1,
635                          layer => 'encode');
636        } else {
637          $self->{input_encoding} = $charset->get_iana_name;
638        }
639        $self->{confident} = 1;
640    
641        $wrapped_char_stream = $get_wrapper->($char_stream);
642        $wrapped_char_stream->onerror ($char_onerror);
643    
644        $return = $self->parse_char_stream ($wrapped_char_stream, @args);
645      };
646      return $return;
647    } # parse_byte_stream
648    
649    ## NOTE: HTML5 spec says that the encoding layer MUST NOT strip BOM
650    ## and the HTML layer MUST ignore it.  However, we does strip BOM in
651    ## the encoding layer and the HTML layer does not ignore any U+FEFF,
652    ## because the core part of our HTML parser expects a string of character,
653    ## not a string of bytes or code units or anything which might contain a BOM.
654    ## Therefore, any parser interface that accepts a string of bytes,
655    ## such as |parse_byte_string| in this module, must ensure that it does
656    ## strip the BOM and never strip any ZWNBSP.
657    
658    sub parse_char_string ($$$;$$) {
659      #my ($self, $s, $doc, $onerror, $get_wrapper) = @_;
660      my $self = shift;
661      my $s = ref $_[0] ? $_[0] : \($_[0]);
662      require Whatpm::Charset::DecodeHandle;
663      my $input = Whatpm::Charset::DecodeHandle::CharString->new ($s);
664      return $self->parse_char_stream ($input, @_[1..$#_]);
665    } # parse_char_string
666    *parse_string = \&parse_char_string; ## NOTE: Alias for backward compatibility.
667    
668    sub parse_char_stream ($$$;$$) {
669      my $self = ref $_[0] ? shift : shift->new;
670      my $input = $_[0];
671    $self->{document} = $_[1];    $self->{document} = $_[1];
672      @{$self->{document}->child_nodes} = ();
673    
674    ## NOTE: |set_inner_html| copies most of this method's code    ## NOTE: |set_inner_html| copies most of this method's code
675    
676    my $i = 0;    $self->{confident} = 1 unless exists $self->{confident};
677    my $line = 1;    $self->{document}->input_encoding ($self->{input_encoding})
678    my $column = 0;        if defined $self->{input_encoding};
679    $self->{set_next_input_character} = sub {  ## TODO: |{input_encoding}| is needless?
680    
681      $self->{line_prev} = $self->{line} = 1;
682      $self->{column_prev} = -1;
683      $self->{column} = 0;
684      $self->{set_nc} = sub {
685      my $self = shift;      my $self = shift;
686    
687      pop @{$self->{prev_input_character}};      my $char = '';
688      unshift @{$self->{prev_input_character}}, $self->{next_input_character};      if (defined $self->{next_nc}) {
689          $char = $self->{next_nc};
690          delete $self->{next_nc};
691          $self->{nc} = ord $char;
692        } else {
693          $self->{char_buffer} = '';
694          $self->{char_buffer_pos} = 0;
695    
696          my $count = $input->manakai_read_until
697             ($self->{char_buffer}, qr/[^\x00\x0A\x0D]/, $self->{char_buffer_pos});
698          if ($count) {
699            $self->{line_prev} = $self->{line};
700            $self->{column_prev} = $self->{column};
701            $self->{column}++;
702            $self->{nc}
703                = ord substr ($self->{char_buffer}, $self->{char_buffer_pos}++, 1);
704            return;
705          }
706    
707      $self->{next_input_character} = -1 and return if $i >= length $$s;        if ($input->read ($char, 1)) {
708      $self->{next_input_character} = ord substr $$s, $i++, 1;          $self->{nc} = ord $char;
709      $column++;        } else {
710            $self->{nc} = -1;
711            return;
712          }
713        }
714    
715        ($self->{line_prev}, $self->{column_prev})
716            = ($self->{line}, $self->{column});
717        $self->{column}++;
718            
719      if ($self->{next_input_character} == 0x000A) { # LF      if ($self->{nc} == 0x000A) { # LF
720        $line++;        !!!cp ('j1');
721        $column = 0;        $self->{line}++;
722      } elsif ($self->{next_input_character} == 0x000D) { # CR        $self->{column} = 0;
723        $i++ if substr ($$s, $i, 1) eq "\x0A";      } elsif ($self->{nc} == 0x000D) { # CR
724        $self->{next_input_character} = 0x000A; # LF # MUST        !!!cp ('j2');
725        $line++;  ## TODO: support for abort/streaming
726        $column = 0;        my $next = '';
727      } elsif ($self->{next_input_character} > 0x10FFFF) {        if ($input->read ($next, 1) and $next ne "\x0A") {
728        $self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST          $self->{next_nc} = $next;
729      } elsif ($self->{next_input_character} == 0x0000) { # NULL        }
730          $self->{nc} = 0x000A; # LF # MUST
731          $self->{line}++;
732          $self->{column} = 0;
733        } elsif ($self->{nc} == 0x0000) { # NULL
734          !!!cp ('j4');
735        !!!parse-error (type => 'NULL');        !!!parse-error (type => 'NULL');
736        $self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST        $self->{nc} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
737      }      }
738    };    };
739    $self->{prev_input_character} = [-1, -1, -1];  
740    $self->{next_input_character} = -1;    $self->{read_until} = sub {
741        #my ($scalar, $specials_range, $offset) = @_;
742        return 0 if defined $self->{next_nc};
743    
744        my $pattern = qr/[^$_[1]\x00\x0A\x0D]/;
745        my $offset = $_[2] || 0;
746    
747        if ($self->{char_buffer_pos} < length $self->{char_buffer}) {
748          pos ($self->{char_buffer}) = $self->{char_buffer_pos};
749          if ($self->{char_buffer} =~ /\G(?>$pattern)+/) {
750            substr ($_[0], $offset)
751                = substr ($self->{char_buffer}, $-[0], $+[0] - $-[0]);
752            my $count = $+[0] - $-[0];
753            if ($count) {
754              $self->{column} += $count;
755              $self->{char_buffer_pos} += $count;
756              $self->{line_prev} = $self->{line};
757              $self->{column_prev} = $self->{column} - 1;
758              $self->{nc} = -1;
759            }
760            return $count;
761          } else {
762            return 0;
763          }
764        } else {
765          my $count = $input->manakai_read_until ($_[0], $pattern, $_[2]);
766          if ($count) {
767            $self->{column} += $count;
768            $self->{line_prev} = $self->{line};
769            $self->{column_prev} = $self->{column} - 1;
770            $self->{nc} = -1;
771          }
772          return $count;
773        }
774      }; # $self->{read_until}
775    
776    my $onerror = $_[2] || sub {    my $onerror = $_[2] || sub {
777      my (%opt) = @_;      my (%opt) = @_;
778      warn "Parse error ($opt{type}) at line $opt{line} column $opt{column}\n";      my $line = $opt{token} ? $opt{token}->{line} : $opt{line};
779        my $column = $opt{token} ? $opt{token}->{column} : $opt{column};
780        warn "Parse error ($opt{type}) at line $line column $column\n";
781    };    };
782    $self->{parse_error} = sub {    $self->{parse_error} = sub {
783      $onerror->(@_, line => $line, column => $column);      $onerror->(line => $self->{line}, column => $self->{column}, @_);
784    };    };
785    
786      my $char_onerror = sub {
787        my (undef, $type, %opt) = @_;
788        !!!parse-error (layer => 'encode',
789                        line => $self->{line}, column => $self->{column} + 1,
790                        %opt, type => $type);
791      }; # $char_onerror
792    
793      if ($_[3]) {
794        $input = $_[3]->($input);
795        $input->onerror ($char_onerror);
796      } else {
797        $input->onerror ($char_onerror) unless defined $input->onerror;
798      }
799    
800    $self->_initialize_tokenizer;    $self->_initialize_tokenizer;
801    $self->_initialize_tree_constructor;    $self->_initialize_tree_constructor;
802    $self->_construct_tree;    $self->_construct_tree;
803    $self->_terminate_tree_constructor;    $self->_terminate_tree_constructor;
804    
805      delete $self->{parse_error}; # remove loop
806    
807    return $self->{document};    return $self->{document};
808  } # parse_string  } # parse_char_stream
809    
810  sub new ($) {  sub new ($) {
811    my $class = shift;    my $class = shift;
812    my $self = bless {}, $class;    my $self = bless {
813    $self->{set_next_input_character} = sub {      level => {must => 'm',
814      $self->{next_input_character} = -1;                should => 's',
815                  warn => 'w',
816                  info => 'i',
817                  uncertain => 'u'},
818      }, $class;
819      $self->{set_nc} = sub {
820        $self->{nc} = -1;
821    };    };
822    $self->{parse_error} = sub {    $self->{parse_error} = sub {
823      #      #
824    };    };
825      $self->{change_encoding} = sub {
826        # if ($_[0] is a supported encoding) {
827        #   run "change the encoding" algorithm;
828        #   throw Whatpm::HTML::RestartParser (charset => $new_encoding);
829        # }
830      };
831      $self->{application_cache_selection} = sub {
832        #
833      };
834    return $self;    return $self;
835  } # new  } # new
836    
# Line 159  sub CDATA_CONTENT_MODEL () { CM_LIMITED_ Line 843  sub CDATA_CONTENT_MODEL () { CM_LIMITED_
843  sub RCDATA_CONTENT_MODEL () { CM_ENTITY | CM_LIMITED_MARKUP }  sub RCDATA_CONTENT_MODEL () { CM_ENTITY | CM_LIMITED_MARKUP }
844  sub PCDATA_CONTENT_MODEL () { CM_ENTITY | CM_FULL_MARKUP }  sub PCDATA_CONTENT_MODEL () { CM_ENTITY | CM_FULL_MARKUP }
845    
846    sub DATA_STATE () { 0 }
847    #sub ENTITY_DATA_STATE () { 1 }
848    sub TAG_OPEN_STATE () { 2 }
849    sub CLOSE_TAG_OPEN_STATE () { 3 }
850    sub TAG_NAME_STATE () { 4 }
851    sub BEFORE_ATTRIBUTE_NAME_STATE () { 5 }
852    sub ATTRIBUTE_NAME_STATE () { 6 }
853    sub AFTER_ATTRIBUTE_NAME_STATE () { 7 }
854    sub BEFORE_ATTRIBUTE_VALUE_STATE () { 8 }
855    sub ATTRIBUTE_VALUE_DOUBLE_QUOTED_STATE () { 9 }
856    sub ATTRIBUTE_VALUE_SINGLE_QUOTED_STATE () { 10 }
857    sub ATTRIBUTE_VALUE_UNQUOTED_STATE () { 11 }
858    #sub ENTITY_IN_ATTRIBUTE_VALUE_STATE () { 12 }
859    sub MARKUP_DECLARATION_OPEN_STATE () { 13 }
860    sub COMMENT_START_STATE () { 14 }
861    sub COMMENT_START_DASH_STATE () { 15 }
862    sub COMMENT_STATE () { 16 }
863    sub COMMENT_END_STATE () { 17 }
864    sub COMMENT_END_DASH_STATE () { 18 }
865    sub BOGUS_COMMENT_STATE () { 19 }
866    sub DOCTYPE_STATE () { 20 }
867    sub BEFORE_DOCTYPE_NAME_STATE () { 21 }
868    sub DOCTYPE_NAME_STATE () { 22 }
869    sub AFTER_DOCTYPE_NAME_STATE () { 23 }
870    sub BEFORE_DOCTYPE_PUBLIC_IDENTIFIER_STATE () { 24 }
871    sub DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED_STATE () { 25 }
872    sub DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED_STATE () { 26 }
873    sub AFTER_DOCTYPE_PUBLIC_IDENTIFIER_STATE () { 27 }
874    sub BEFORE_DOCTYPE_SYSTEM_IDENTIFIER_STATE () { 28 }
875    sub DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED_STATE () { 29 }
876    sub DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED_STATE () { 30 }
877    sub AFTER_DOCTYPE_SYSTEM_IDENTIFIER_STATE () { 31 }
878    sub BOGUS_DOCTYPE_STATE () { 32 }
879    sub AFTER_ATTRIBUTE_VALUE_QUOTED_STATE () { 33 }
880    sub SELF_CLOSING_START_TAG_STATE () { 34 }
881    sub CDATA_SECTION_STATE () { 35 }
882    sub MD_HYPHEN_STATE () { 36 } # "markup declaration open state" in the spec
883    sub MD_DOCTYPE_STATE () { 37 } # "markup declaration open state" in the spec
884    sub MD_CDATA_STATE () { 38 } # "markup declaration open state" in the spec
885    sub CDATA_RCDATA_CLOSE_TAG_STATE () { 39 } # "close tag open state" in the spec
886    sub CDATA_SECTION_MSE1_STATE () { 40 } # "CDATA section state" in the spec
887    sub CDATA_SECTION_MSE2_STATE () { 41 } # "CDATA section state" in the spec
888    sub PUBLIC_STATE () { 42 } # "after DOCTYPE name state" in the spec
889    sub SYSTEM_STATE () { 43 } # "after DOCTYPE name state" in the spec
890    ## NOTE: "Entity data state", "entity in attribute value state", and
891    ## "consume a character reference" algorithm are jointly implemented
892    ## using the following six states:
893    sub ENTITY_STATE () { 44 }
894    sub ENTITY_HASH_STATE () { 45 }
895    sub NCR_NUM_STATE () { 46 }
896    sub HEXREF_X_STATE () { 47 }
897    sub HEXREF_HEX_STATE () { 48 }
898    sub ENTITY_NAME_STATE () { 49 }
899    sub PCDATA_STATE () { 50 } # "data state" in the spec
900    
901    sub DOCTYPE_TOKEN () { 1 }
902    sub COMMENT_TOKEN () { 2 }
903    sub START_TAG_TOKEN () { 3 }
904    sub END_TAG_TOKEN () { 4 }
905    sub END_OF_FILE_TOKEN () { 5 }
906    sub CHARACTER_TOKEN () { 6 }
907    
908    sub AFTER_HTML_IMS () { 0b100 }
909    sub HEAD_IMS ()       { 0b1000 }
910    sub BODY_IMS ()       { 0b10000 }
911    sub BODY_TABLE_IMS () { 0b100000 }
912    sub TABLE_IMS ()      { 0b1000000 }
913    sub ROW_IMS ()        { 0b10000000 }
914    sub BODY_AFTER_IMS () { 0b100000000 }
915    sub FRAME_IMS ()      { 0b1000000000 }
916    sub SELECT_IMS ()     { 0b10000000000 }
917    sub IN_FOREIGN_CONTENT_IM () { 0b100000000000 }
918        ## NOTE: "in foreign content" insertion mode is special; it is combined
919        ## with the secondary insertion mode.  In this parser, they are stored
920        ## together in the bit-or'ed form.
921    
922    ## NOTE: "initial" and "before html" insertion modes have no constants.
923    
924    ## NOTE: "after after body" insertion mode.
925    sub AFTER_HTML_BODY_IM () { AFTER_HTML_IMS | BODY_AFTER_IMS }
926    
927    ## NOTE: "after after frameset" insertion mode.
928    sub AFTER_HTML_FRAMESET_IM () { AFTER_HTML_IMS | FRAME_IMS }
929    
930    sub IN_HEAD_IM () { HEAD_IMS | 0b00 }
931    sub IN_HEAD_NOSCRIPT_IM () { HEAD_IMS | 0b01 }
932    sub AFTER_HEAD_IM () { HEAD_IMS | 0b10 }
933    sub BEFORE_HEAD_IM () { HEAD_IMS | 0b11 }
934    sub IN_BODY_IM () { BODY_IMS }
935    sub IN_CELL_IM () { BODY_IMS | BODY_TABLE_IMS | 0b01 }
936    sub IN_CAPTION_IM () { BODY_IMS | BODY_TABLE_IMS | 0b10 }
937    sub IN_ROW_IM () { TABLE_IMS | ROW_IMS | 0b01 }
938    sub IN_TABLE_BODY_IM () { TABLE_IMS | ROW_IMS | 0b10 }
939    sub IN_TABLE_IM () { TABLE_IMS }
940    sub AFTER_BODY_IM () { BODY_AFTER_IMS }
941    sub IN_FRAMESET_IM () { FRAME_IMS | 0b01 }
942    sub AFTER_FRAMESET_IM () { FRAME_IMS | 0b10 }
943    sub IN_SELECT_IM () { SELECT_IMS | 0b01 }
944    sub IN_SELECT_IN_TABLE_IM () { SELECT_IMS | 0b10 }
945    sub IN_COLUMN_GROUP_IM () { 0b10 }
946    
947  ## Implementations MUST act as if state machine in the spec  ## Implementations MUST act as if state machine in the spec
948    
949  sub _initialize_tokenizer ($) {  sub _initialize_tokenizer ($) {
950    my $self = shift;    my $self = shift;
951    $self->{state} = 'data'; # MUST    $self->{state} = DATA_STATE; # MUST
952      #$self->{s_kwd}; # state keyword - initialized when used
953      #$self->{entity__value}; # initialized when used
954      #$self->{entity__match}; # initialized when used
955    $self->{content_model} = PCDATA_CONTENT_MODEL; # be    $self->{content_model} = PCDATA_CONTENT_MODEL; # be
956    undef $self->{current_token}; # start tag, end tag, comment, or DOCTYPE    undef $self->{ct}; # current token
957    undef $self->{current_attribute};    undef $self->{ca}; # current attribute
958    undef $self->{last_emitted_start_tag_name};    undef $self->{last_stag_name}; # last emitted start tag name
959    undef $self->{last_attribute_value_state};    #$self->{prev_state}; # initialized when used
960    $self->{char} = [];    delete $self->{self_closing};
961    # $self->{next_input_character}    $self->{char_buffer} = '';
962      $self->{char_buffer_pos} = 0;
963      $self->{nc} = -1; # next input character
964      #$self->{next_nc}
965    !!!next-input-character;    !!!next-input-character;
966    $self->{token} = [];    $self->{token} = [];
967    # $self->{escape}    # $self->{escape}
968  } # _initialize_tokenizer  } # _initialize_tokenizer
969    
970  ## A token has:  ## A token has:
971  ##   ->{type} eq 'DOCTYPE', 'start tag', 'end tag', 'comment',  ##   ->{type} == DOCTYPE_TOKEN, START_TAG_TOKEN, END_TAG_TOKEN, COMMENT_TOKEN,
972  ##       'character', or 'end-of-file'  ##       CHARACTER_TOKEN, or END_OF_FILE_TOKEN
973  ##   ->{name} (DOCTYPE, start tag (tag name), end tag (tag name))  ##   ->{name} (DOCTYPE_TOKEN)
974  ##   ->{public_identifier} (DOCTYPE)  ##   ->{tag_name} (START_TAG_TOKEN, END_TAG_TOKEN)
975  ##   ->{system_identifier} (DOCTYPE)  ##   ->{pubid} (DOCTYPE_TOKEN)
976  ##   ->{correct} == 1 or 0 (DOCTYPE)  ##   ->{sysid} (DOCTYPE_TOKEN)
977  ##   ->{attributes} isa HASH (start tag, end tag)  ##   ->{quirks} == 1 or 0 (DOCTYPE_TOKEN): "force-quirks" flag
978  ##   ->{data} (comment, character)  ##   ->{attributes} isa HASH (START_TAG_TOKEN, END_TAG_TOKEN)
979    ##        ->{name}
980    ##        ->{value}
981    ##        ->{has_reference} == 1 or 0
982    ##   ->{data} (COMMENT_TOKEN, CHARACTER_TOKEN)
983    ## NOTE: The "self-closing flag" is hold as |$self->{self_closing}|.
984    ##     |->{self_closing}| is used to save the value of |$self->{self_closing}|
985    ##     while the token is pushed back to the stack.
986    
987  ## Emitted token MUST immediately be handled by the tree construction state.  ## Emitted token MUST immediately be handled by the tree construction state.
988    
# Line 194  sub _initialize_tokenizer ($) { Line 992  sub _initialize_tokenizer ($) {
992  ## has completed loading.  If one has, then it MUST be executed  ## has completed loading.  If one has, then it MUST be executed
993  ## and removed from the list.  ## and removed from the list.
994    
995    ## TODO: Polytheistic slash SHOULD NOT be used. (Applied only to atheists.)
996    ## (This requirement was dropped from HTML5 spec, unfortunately.)
997    
998    my $is_space = {
999      0x0009 => 1, # CHARACTER TABULATION (HT)
1000      0x000A => 1, # LINE FEED (LF)
1001      #0x000B => 0, # LINE TABULATION (VT)
1002      0x000C => 1, # FORM FEED (FF)
1003      #0x000D => 1, # CARRIAGE RETURN (CR)
1004      0x0020 => 1, # SPACE (SP)
1005    };
1006    
1007  sub _get_next_token ($) {  sub _get_next_token ($) {
1008    my $self = shift;    my $self = shift;
1009    
1010      if ($self->{self_closing}) {
1011        !!!parse-error (type => 'nestc', token => $self->{ct});
1012        ## NOTE: The |self_closing| flag is only set by start tag token.
1013        ## In addition, when a start tag token is emitted, it is always set to
1014        ## |ct|.
1015        delete $self->{self_closing};
1016      }
1017    
1018    if (@{$self->{token}}) {    if (@{$self->{token}}) {
1019        $self->{self_closing} = $self->{token}->[0]->{self_closing};
1020      return shift @{$self->{token}};      return shift @{$self->{token}};
1021    }    }
1022    
1023    A: {    A: {
1024      if ($self->{state} eq 'data') {      if ($self->{state} == PCDATA_STATE) {
1025        if ($self->{next_input_character} == 0x0026) { # &        ## NOTE: Same as |DATA_STATE|, but only for |PCDATA| content model.
1026          if ($self->{content_model} & CM_ENTITY) { # PCDATA | RCDATA  
1027            $self->{state} = 'entity data';        if ($self->{nc} == 0x0026) { # &
1028            !!!cp (0.1);
1029            ## NOTE: In the spec, the tokenizer is switched to the
1030            ## "entity data state".  In this implementation, the tokenizer
1031            ## is switched to the |ENTITY_STATE|, which is an implementation
1032            ## of the "consume a character reference" algorithm.
1033            $self->{entity_add} = -1;
1034            $self->{prev_state} = DATA_STATE;
1035            $self->{state} = ENTITY_STATE;
1036            !!!next-input-character;
1037            redo A;
1038          } elsif ($self->{nc} == 0x003C) { # <
1039            !!!cp (0.2);
1040            $self->{state} = TAG_OPEN_STATE;
1041            !!!next-input-character;
1042            redo A;
1043          } elsif ($self->{nc} == -1) {
1044            !!!cp (0.3);
1045            !!!emit ({type => END_OF_FILE_TOKEN,
1046                      line => $self->{line}, column => $self->{column}});
1047            last A; ## TODO: ok?
1048          } else {
1049            !!!cp (0.4);
1050            #
1051          }
1052    
1053          # Anything else
1054          my $token = {type => CHARACTER_TOKEN,
1055                       data => chr $self->{nc},
1056                       line => $self->{line}, column => $self->{column},
1057                      };
1058          $self->{read_until}->($token->{data}, q[<&], length $token->{data});
1059    
1060          ## Stay in the state.
1061          !!!next-input-character;
1062          !!!emit ($token);
1063          redo A;
1064        } elsif ($self->{state} == DATA_STATE) {
1065          $self->{s_kwd} = '' unless defined $self->{s_kwd};
1066          if ($self->{nc} == 0x0026) { # &
1067            $self->{s_kwd} = '';
1068            if ($self->{content_model} & CM_ENTITY and # PCDATA | RCDATA
1069                not $self->{escape}) {
1070              !!!cp (1);
1071              ## NOTE: In the spec, the tokenizer is switched to the
1072              ## "entity data state".  In this implementation, the tokenizer
1073              ## is switched to the |ENTITY_STATE|, which is an implementation
1074              ## of the "consume a character reference" algorithm.
1075              $self->{entity_add} = -1;
1076              $self->{prev_state} = DATA_STATE;
1077              $self->{state} = ENTITY_STATE;
1078            !!!next-input-character;            !!!next-input-character;
1079            redo A;            redo A;
1080          } else {          } else {
1081              !!!cp (2);
1082            #            #
1083          }          }
1084        } elsif ($self->{next_input_character} == 0x002D) { # -        } elsif ($self->{nc} == 0x002D) { # -
1085          if ($self->{content_model} & CM_LIMITED_MARKUP) { # RCDATA | CDATA          if ($self->{content_model} & CM_LIMITED_MARKUP) { # RCDATA | CDATA
1086            unless ($self->{escape}) {            $self->{s_kwd} .= '-';
1087              if ($self->{prev_input_character}->[0] == 0x002D and # -            
1088                  $self->{prev_input_character}->[1] == 0x0021 and # !            if ($self->{s_kwd} eq '<!--') {
1089                  $self->{prev_input_character}->[2] == 0x003C) { # <              !!!cp (3);
1090                $self->{escape} = 1;              $self->{escape} = 1; # unless $self->{escape};
1091              }              $self->{s_kwd} = '--';
1092                #
1093              } elsif ($self->{s_kwd} eq '---') {
1094                !!!cp (4);
1095                $self->{s_kwd} = '--';
1096                #
1097              } else {
1098                !!!cp (5);
1099                #
1100            }            }
1101          }          }
1102                    
1103          #          #
1104        } elsif ($self->{next_input_character} == 0x003C) { # <        } elsif ($self->{nc} == 0x0021) { # !
1105            if (length $self->{s_kwd}) {
1106              !!!cp (5.1);
1107              $self->{s_kwd} .= '!';
1108              #
1109            } else {
1110              !!!cp (5.2);
1111              #$self->{s_kwd} = '';
1112              #
1113            }
1114            #
1115          } elsif ($self->{nc} == 0x003C) { # <
1116          if ($self->{content_model} & CM_FULL_MARKUP or # PCDATA          if ($self->{content_model} & CM_FULL_MARKUP or # PCDATA
1117              (($self->{content_model} & CM_LIMITED_MARKUP) and # CDATA | RCDATA              (($self->{content_model} & CM_LIMITED_MARKUP) and # CDATA | RCDATA
1118               not $self->{escape})) {               not $self->{escape})) {
1119            $self->{state} = 'tag open';            !!!cp (6);
1120              $self->{state} = TAG_OPEN_STATE;
1121            !!!next-input-character;            !!!next-input-character;
1122            redo A;            redo A;
1123          } else {          } else {
1124              !!!cp (7);
1125              $self->{s_kwd} = '';
1126            #            #
1127          }          }
1128        } elsif ($self->{next_input_character} == 0x003E) { # >        } elsif ($self->{nc} == 0x003E) { # >
1129          if ($self->{escape} and          if ($self->{escape} and
1130              ($self->{content_model} & CM_LIMITED_MARKUP)) { # RCDATA | CDATA              ($self->{content_model} & CM_LIMITED_MARKUP)) { # RCDATA | CDATA
1131            if ($self->{prev_input_character}->[0] == 0x002D and # -            if ($self->{s_kwd} eq '--') {
1132                $self->{prev_input_character}->[1] == 0x002D) { # -              !!!cp (8);
1133              delete $self->{escape};              delete $self->{escape};
1134              } else {
1135                !!!cp (9);
1136            }            }
1137            } else {
1138              !!!cp (10);
1139          }          }
1140                    
1141            $self->{s_kwd} = '';
1142          #          #
1143        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
1144          !!!emit ({type => 'end-of-file'});          !!!cp (11);
1145            $self->{s_kwd} = '';
1146            !!!emit ({type => END_OF_FILE_TOKEN,
1147                      line => $self->{line}, column => $self->{column}});
1148          last A; ## TODO: ok?          last A; ## TODO: ok?
1149          } else {
1150            !!!cp (12);
1151            $self->{s_kwd} = '';
1152            #
1153        }        }
       # Anything else  
       my $token = {type => 'character',  
                    data => chr $self->{next_input_character}};  
       ## Stay in the data state  
       !!!next-input-character;  
   
       !!!emit ($token);  
   
       redo A;  
     } elsif ($self->{state} eq 'entity data') {  
       ## (cannot happen in CDATA state)  
         
       my $token = $self->_tokenize_attempt_to_consume_an_entity (0);  
   
       $self->{state} = 'data';  
       # next-input-character is already done  
1154    
1155        unless (defined $token) {        # Anything else
1156          !!!emit ({type => 'character', data => '&'});        my $token = {type => CHARACTER_TOKEN,
1157                       data => chr $self->{nc},
1158                       line => $self->{line}, column => $self->{column},
1159                      };
1160          if ($self->{read_until}->($token->{data}, q[-!<>&],
1161                                    length $token->{data})) {
1162            $self->{s_kwd} = '';
1163          }
1164    
1165          ## Stay in the data state.
1166          if ($self->{content_model} == PCDATA_CONTENT_MODEL) {
1167            !!!cp (13);
1168            $self->{state} = PCDATA_STATE;
1169        } else {        } else {
1170          !!!emit ($token);          !!!cp (14);
1171            ## Stay in the state.
1172        }        }
1173          !!!next-input-character;
1174          !!!emit ($token);
1175        redo A;        redo A;
1176      } elsif ($self->{state} eq 'tag open') {      } elsif ($self->{state} == TAG_OPEN_STATE) {
1177        if ($self->{content_model} & CM_LIMITED_MARKUP) { # RCDATA | CDATA        if ($self->{content_model} & CM_LIMITED_MARKUP) { # RCDATA | CDATA
1178          if ($self->{next_input_character} == 0x002F) { # /          if ($self->{nc} == 0x002F) { # /
1179              !!!cp (15);
1180            !!!next-input-character;            !!!next-input-character;
1181            $self->{state} = 'close tag open';            $self->{state} = CLOSE_TAG_OPEN_STATE;
1182            redo A;            redo A;
1183            } elsif ($self->{nc} == 0x0021) { # !
1184              !!!cp (15.1);
1185              $self->{s_kwd} = '<' unless $self->{escape};
1186              #
1187          } else {          } else {
1188            ## reconsume            !!!cp (16);
1189            $self->{state} = 'data';            #
   
           !!!emit ({type => 'character', data => '<'});  
   
           redo A;  
1190          }          }
1191    
1192            ## reconsume
1193            $self->{state} = DATA_STATE;
1194            !!!emit ({type => CHARACTER_TOKEN, data => '<',
1195                      line => $self->{line_prev},
1196                      column => $self->{column_prev},
1197                     });
1198            redo A;
1199        } elsif ($self->{content_model} & CM_FULL_MARKUP) { # PCDATA        } elsif ($self->{content_model} & CM_FULL_MARKUP) { # PCDATA
1200          if ($self->{next_input_character} == 0x0021) { # !          if ($self->{nc} == 0x0021) { # !
1201            $self->{state} = 'markup declaration open';            !!!cp (17);
1202              $self->{state} = MARKUP_DECLARATION_OPEN_STATE;
1203            !!!next-input-character;            !!!next-input-character;
1204            redo A;            redo A;
1205          } elsif ($self->{next_input_character} == 0x002F) { # /          } elsif ($self->{nc} == 0x002F) { # /
1206            $self->{state} = 'close tag open';            !!!cp (18);
1207              $self->{state} = CLOSE_TAG_OPEN_STATE;
1208            !!!next-input-character;            !!!next-input-character;
1209            redo A;            redo A;
1210          } elsif (0x0041 <= $self->{next_input_character} and          } elsif (0x0041 <= $self->{nc} and
1211                   $self->{next_input_character} <= 0x005A) { # A..Z                   $self->{nc} <= 0x005A) { # A..Z
1212            $self->{current_token}            !!!cp (19);
1213              = {type => 'start tag',            $self->{ct}
1214                 tag_name => chr ($self->{next_input_character} + 0x0020)};              = {type => START_TAG_TOKEN,
1215            $self->{state} = 'tag name';                 tag_name => chr ($self->{nc} + 0x0020),
1216                   line => $self->{line_prev},
1217                   column => $self->{column_prev}};
1218              $self->{state} = TAG_NAME_STATE;
1219            !!!next-input-character;            !!!next-input-character;
1220            redo A;            redo A;
1221          } elsif (0x0061 <= $self->{next_input_character} and          } elsif (0x0061 <= $self->{nc} and
1222                   $self->{next_input_character} <= 0x007A) { # a..z                   $self->{nc} <= 0x007A) { # a..z
1223            $self->{current_token} = {type => 'start tag',            !!!cp (20);
1224                              tag_name => chr ($self->{next_input_character})};            $self->{ct} = {type => START_TAG_TOKEN,
1225            $self->{state} = 'tag name';                                      tag_name => chr ($self->{nc}),
1226                                        line => $self->{line_prev},
1227                                        column => $self->{column_prev}};
1228              $self->{state} = TAG_NAME_STATE;
1229            !!!next-input-character;            !!!next-input-character;
1230            redo A;            redo A;
1231          } elsif ($self->{next_input_character} == 0x003E) { # >          } elsif ($self->{nc} == 0x003E) { # >
1232            !!!parse-error (type => 'empty start tag');            !!!cp (21);
1233            $self->{state} = 'data';            !!!parse-error (type => 'empty start tag',
1234                              line => $self->{line_prev},
1235                              column => $self->{column_prev});
1236              $self->{state} = DATA_STATE;
1237            !!!next-input-character;            !!!next-input-character;
1238    
1239            !!!emit ({type => 'character', data => '<>'});            !!!emit ({type => CHARACTER_TOKEN, data => '<>',
1240                        line => $self->{line_prev},
1241                        column => $self->{column_prev},
1242                       });
1243    
1244            redo A;            redo A;
1245          } elsif ($self->{next_input_character} == 0x003F) { # ?          } elsif ($self->{nc} == 0x003F) { # ?
1246            !!!parse-error (type => 'pio');            !!!cp (22);
1247            $self->{state} = 'bogus comment';            !!!parse-error (type => 'pio',
1248            ## $self->{next_input_character} is intentionally left as is                            line => $self->{line_prev},
1249                              column => $self->{column_prev});
1250              $self->{state} = BOGUS_COMMENT_STATE;
1251              $self->{ct} = {type => COMMENT_TOKEN, data => '',
1252                                        line => $self->{line_prev},
1253                                        column => $self->{column_prev},
1254                                       };
1255              ## $self->{nc} is intentionally left as is
1256            redo A;            redo A;
1257          } else {          } else {
1258            !!!parse-error (type => 'bare stago');            !!!cp (23);
1259            $self->{state} = 'data';            !!!parse-error (type => 'bare stago',
1260                              line => $self->{line_prev},
1261                              column => $self->{column_prev});
1262              $self->{state} = DATA_STATE;
1263            ## reconsume            ## reconsume
1264    
1265            !!!emit ({type => 'character', data => '<'});            !!!emit ({type => CHARACTER_TOKEN, data => '<',
1266                        line => $self->{line_prev},
1267                        column => $self->{column_prev},
1268                       });
1269    
1270            redo A;            redo A;
1271          }          }
1272        } else {        } else {
1273          die "$0: $self->{content_model} in tag open";          die "$0: $self->{content_model} in tag open";
1274        }        }
1275      } elsif ($self->{state} eq 'close tag open') {      } elsif ($self->{state} == CLOSE_TAG_OPEN_STATE) {
1276        if ($self->{content_model} & CM_LIMITED_MARKUP) { # RCDATA | CDATA        ## NOTE: The "close tag open state" in the spec is implemented as
1277          if (defined $self->{last_emitted_start_tag_name}) {        ## |CLOSE_TAG_OPEN_STATE| and |CDATA_RCDATA_CLOSE_TAG_STATE|.
           ## NOTE: <http://krijnhoetmer.nl/irc-logs/whatwg/20070626#l-564>  
           my @next_char;  
           TAGNAME: for (my $i = 0; $i < length $self->{last_emitted_start_tag_name}; $i++) {  
             push @next_char, $self->{next_input_character};  
             my $c = ord substr ($self->{last_emitted_start_tag_name}, $i, 1);  
             my $C = 0x0061 <= $c && $c <= 0x007A ? $c - 0x0020 : $c;  
             if ($self->{next_input_character} == $c or $self->{next_input_character} == $C) {  
               !!!next-input-character;  
               next TAGNAME;  
             } else {  
               $self->{next_input_character} = shift @next_char; # reconsume  
               !!!back-next-input-character (@next_char);  
               $self->{state} = 'data';  
1278    
1279                !!!emit ({type => 'character', data => '</'});        my ($l, $c) = ($self->{line_prev}, $self->{column_prev} - 1); # "<"of"</"
1280            if ($self->{content_model} & CM_LIMITED_MARKUP) { # RCDATA | CDATA
1281                redo A;          if (defined $self->{last_stag_name}) {
1282              }            $self->{state} = CDATA_RCDATA_CLOSE_TAG_STATE;
1283            }            $self->{s_kwd} = '';
1284            push @next_char, $self->{next_input_character};            ## Reconsume.
1285                    redo A;
           unless ($self->{next_input_character} == 0x0009 or # HT  
                   $self->{next_input_character} == 0x000A or # LF  
                   $self->{next_input_character} == 0x000B or # VT  
                   $self->{next_input_character} == 0x000C or # FF  
                   $self->{next_input_character} == 0x0020 or # SP  
                   $self->{next_input_character} == 0x003E or # >  
                   $self->{next_input_character} == 0x002F or # /  
                   $self->{next_input_character} == -1) {  
             $self->{next_input_character} = shift @next_char; # reconsume  
             !!!back-next-input-character (@next_char);  
             $self->{state} = 'data';  
             !!!emit ({type => 'character', data => '</'});  
             redo A;  
           } else {  
             $self->{next_input_character} = shift @next_char;  
             !!!back-next-input-character (@next_char);  
             # and consume...  
           }  
1286          } else {          } else {
1287            ## No start tag token has ever been emitted            ## No start tag token has ever been emitted
1288            # next-input-character is already done            ## NOTE: See <http://krijnhoetmer.nl/irc-logs/whatwg/20070626#l-564>.
1289            $self->{state} = 'data';            !!!cp (28);
1290            !!!emit ({type => 'character', data => '</'});            $self->{state} = DATA_STATE;
1291              ## Reconsume.
1292              !!!emit ({type => CHARACTER_TOKEN, data => '</',
1293                        line => $l, column => $c,
1294                       });
1295            redo A;            redo A;
1296          }          }
1297        }        }
1298          
1299        if (0x0041 <= $self->{next_input_character} and        if (0x0041 <= $self->{nc} and
1300            $self->{next_input_character} <= 0x005A) { # A..Z            $self->{nc} <= 0x005A) { # A..Z
1301          $self->{current_token} = {type => 'end tag',          !!!cp (29);
1302                            tag_name => chr ($self->{next_input_character} + 0x0020)};          $self->{ct}
1303          $self->{state} = 'tag name';              = {type => END_TAG_TOKEN,
1304          !!!next-input-character;                 tag_name => chr ($self->{nc} + 0x0020),
1305          redo A;                 line => $l, column => $c};
1306        } elsif (0x0061 <= $self->{next_input_character} and          $self->{state} = TAG_NAME_STATE;
1307                 $self->{next_input_character} <= 0x007A) { # a..z          !!!next-input-character;
1308          $self->{current_token} = {type => 'end tag',          redo A;
1309                            tag_name => chr ($self->{next_input_character})};        } elsif (0x0061 <= $self->{nc} and
1310          $self->{state} = 'tag name';                 $self->{nc} <= 0x007A) { # a..z
1311          !!!next-input-character;          !!!cp (30);
1312          redo A;          $self->{ct} = {type => END_TAG_TOKEN,
1313        } elsif ($self->{next_input_character} == 0x003E) { # >                                    tag_name => chr ($self->{nc}),
1314          !!!parse-error (type => 'empty end tag');                                    line => $l, column => $c};
1315          $self->{state} = 'data';          $self->{state} = TAG_NAME_STATE;
1316            !!!next-input-character;
1317            redo A;
1318          } elsif ($self->{nc} == 0x003E) { # >
1319            !!!cp (31);
1320            !!!parse-error (type => 'empty end tag',
1321                            line => $self->{line_prev}, ## "<" in "</>"
1322                            column => $self->{column_prev} - 1);
1323            $self->{state} = DATA_STATE;
1324          !!!next-input-character;          !!!next-input-character;
1325          redo A;          redo A;
1326        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
1327            !!!cp (32);
1328          !!!parse-error (type => 'bare etago');          !!!parse-error (type => 'bare etago');
1329          $self->{state} = 'data';          $self->{state} = DATA_STATE;
1330          # reconsume          # reconsume
1331    
1332          !!!emit ({type => 'character', data => '</'});          !!!emit ({type => CHARACTER_TOKEN, data => '</',
1333                      line => $l, column => $c,
1334                     });
1335    
1336          redo A;          redo A;
1337        } else {        } else {
1338            !!!cp (33);
1339          !!!parse-error (type => 'bogus end tag');          !!!parse-error (type => 'bogus end tag');
1340          $self->{state} = 'bogus comment';          $self->{state} = BOGUS_COMMENT_STATE;
1341          ## $self->{next_input_character} is intentionally left as is          $self->{ct} = {type => COMMENT_TOKEN, data => '',
1342          redo A;                                    line => $self->{line_prev}, # "<" of "</"
1343                                      column => $self->{column_prev} - 1,
1344                                     };
1345            ## NOTE: $self->{nc} is intentionally left as is.
1346            ## Although the "anything else" case of the spec not explicitly
1347            ## states that the next input character is to be reconsumed,
1348            ## it will be included to the |data| of the comment token
1349            ## generated from the bogus end tag, as defined in the
1350            ## "bogus comment state" entry.
1351            redo A;
1352          }
1353        } elsif ($self->{state} == CDATA_RCDATA_CLOSE_TAG_STATE) {
1354          my $ch = substr $self->{last_stag_name}, length $self->{s_kwd}, 1;
1355          if (length $ch) {
1356            my $CH = $ch;
1357            $ch =~ tr/a-z/A-Z/;
1358            my $nch = chr $self->{nc};
1359            if ($nch eq $ch or $nch eq $CH) {
1360              !!!cp (24);
1361              ## Stay in the state.
1362              $self->{s_kwd} .= $nch;
1363              !!!next-input-character;
1364              redo A;
1365            } else {
1366              !!!cp (25);
1367              $self->{state} = DATA_STATE;
1368              ## Reconsume.
1369              !!!emit ({type => CHARACTER_TOKEN,
1370                        data => '</' . $self->{s_kwd},
1371                        line => $self->{line_prev},
1372                        column => $self->{column_prev} - 1 - length $self->{s_kwd},
1373                       });
1374              redo A;
1375            }
1376          } else { # after "<{tag-name}"
1377            unless ($is_space->{$self->{nc}} or
1378                    {
1379                     0x003E => 1, # >
1380                     0x002F => 1, # /
1381                     -1 => 1, # EOF
1382                    }->{$self->{nc}}) {
1383              !!!cp (26);
1384              ## Reconsume.
1385              $self->{state} = DATA_STATE;
1386              !!!emit ({type => CHARACTER_TOKEN,
1387                        data => '</' . $self->{s_kwd},
1388                        line => $self->{line_prev},
1389                        column => $self->{column_prev} - 1 - length $self->{s_kwd},
1390                       });
1391              redo A;
1392            } else {
1393              !!!cp (27);
1394              $self->{ct}
1395                  = {type => END_TAG_TOKEN,
1396                     tag_name => $self->{last_stag_name},
1397                     line => $self->{line_prev},
1398                     column => $self->{column_prev} - 1 - length $self->{s_kwd}};
1399              $self->{state} = TAG_NAME_STATE;
1400              ## Reconsume.
1401              redo A;
1402            }
1403        }        }
1404      } elsif ($self->{state} eq 'tag name') {      } elsif ($self->{state} == TAG_NAME_STATE) {
1405        if ($self->{next_input_character} == 0x0009 or # HT        if ($is_space->{$self->{nc}}) {
1406            $self->{next_input_character} == 0x000A or # LF          !!!cp (34);
1407            $self->{next_input_character} == 0x000B or # VT          $self->{state} = BEFORE_ATTRIBUTE_NAME_STATE;
1408            $self->{next_input_character} == 0x000C or # FF          !!!next-input-character;
1409            $self->{next_input_character} == 0x0020) { # SP          redo A;
1410          $self->{state} = 'before attribute name';        } elsif ($self->{nc} == 0x003E) { # >
1411          !!!next-input-character;          if ($self->{ct}->{type} == START_TAG_TOKEN) {
1412          redo A;            !!!cp (35);
1413        } elsif ($self->{next_input_character} == 0x003E) { # >            $self->{last_stag_name} = $self->{ct}->{tag_name};
1414          if ($self->{current_token}->{type} eq 'start tag') {          } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
           $self->{current_token}->{first_start_tag}  
               = not defined $self->{last_emitted_start_tag_name};  
           $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};  
         } elsif ($self->{current_token}->{type} eq 'end tag') {  
1415            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1416            if ($self->{current_token}->{attributes}) {            #if ($self->{ct}->{attributes}) {
1417              !!!parse-error (type => 'end tag attribute');            #  ## NOTE: This should never be reached.
1418            }            #  !!! cp (36);
1419              #  !!! parse-error (type => 'end tag attribute');
1420              #} else {
1421                !!!cp (37);
1422              #}
1423          } else {          } else {
1424            die "$0: $self->{current_token}->{type}: Unknown token type";            die "$0: $self->{ct}->{type}: Unknown token type";
1425          }          }
1426          $self->{state} = 'data';          $self->{state} = DATA_STATE;
1427          !!!next-input-character;          !!!next-input-character;
1428    
1429          !!!emit ($self->{current_token}); # start tag or end tag          !!!emit ($self->{ct}); # start tag or end tag
1430    
1431          redo A;          redo A;
1432        } elsif (0x0041 <= $self->{next_input_character} and        } elsif (0x0041 <= $self->{nc} and
1433                 $self->{next_input_character} <= 0x005A) { # A..Z                 $self->{nc} <= 0x005A) { # A..Z
1434          $self->{current_token}->{tag_name} .= chr ($self->{next_input_character} + 0x0020);          !!!cp (38);
1435            $self->{ct}->{tag_name} .= chr ($self->{nc} + 0x0020);
1436            # start tag or end tag            # start tag or end tag
1437          ## Stay in this state          ## Stay in this state
1438          !!!next-input-character;          !!!next-input-character;
1439          redo A;          redo A;
1440        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
1441          !!!parse-error (type => 'unclosed tag');          !!!parse-error (type => 'unclosed tag');
1442          if ($self->{current_token}->{type} eq 'start tag') {          if ($self->{ct}->{type} == START_TAG_TOKEN) {
1443            $self->{current_token}->{first_start_tag}            !!!cp (39);
1444                = not defined $self->{last_emitted_start_tag_name};            $self->{last_stag_name} = $self->{ct}->{tag_name};
1445            $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};          } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
         } elsif ($self->{current_token}->{type} eq 'end tag') {  
1446            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1447            if ($self->{current_token}->{attributes}) {            #if ($self->{ct}->{attributes}) {
1448              !!!parse-error (type => 'end tag attribute');            #  ## NOTE: This state should never be reached.
1449            }            #  !!! cp (40);
1450              #  !!! parse-error (type => 'end tag attribute');
1451              #} else {
1452                !!!cp (41);
1453              #}
1454          } else {          } else {
1455            die "$0: $self->{current_token}->{type}: Unknown token type";            die "$0: $self->{ct}->{type}: Unknown token type";
1456          }          }
1457          $self->{state} = 'data';          $self->{state} = DATA_STATE;
1458          # reconsume          # reconsume
1459    
1460          !!!emit ($self->{current_token}); # start tag or end tag          !!!emit ($self->{ct}); # start tag or end tag
1461    
1462          redo A;          redo A;
1463        } elsif ($self->{next_input_character} == 0x002F) { # /        } elsif ($self->{nc} == 0x002F) { # /
1464            !!!cp (42);
1465            $self->{state} = SELF_CLOSING_START_TAG_STATE;
1466          !!!next-input-character;          !!!next-input-character;
         if ($self->{next_input_character} == 0x003E and # >  
             $self->{current_token}->{type} eq 'start tag' and  
             $permitted_slash_tag_name->{$self->{current_token}->{tag_name}}) {  
           # permitted slash  
           #  
         } else {  
           !!!parse-error (type => 'nestc');  
         }  
         $self->{state} = 'before attribute name';  
         # next-input-character is already done  
1467          redo A;          redo A;
1468        } else {        } else {
1469          $self->{current_token}->{tag_name} .= chr $self->{next_input_character};          !!!cp (44);
1470            $self->{ct}->{tag_name} .= chr $self->{nc};
1471            # start tag or end tag            # start tag or end tag
1472          ## Stay in the state          ## Stay in the state
1473          !!!next-input-character;          !!!next-input-character;
1474          redo A;          redo A;
1475        }        }
1476      } elsif ($self->{state} eq 'before attribute name') {      } elsif ($self->{state} == BEFORE_ATTRIBUTE_NAME_STATE) {
1477        if ($self->{next_input_character} == 0x0009 or # HT        if ($is_space->{$self->{nc}}) {
1478            $self->{next_input_character} == 0x000A or # LF          !!!cp (45);
           $self->{next_input_character} == 0x000B or # VT  
           $self->{next_input_character} == 0x000C or # FF  
           $self->{next_input_character} == 0x0020) { # SP  
1479          ## Stay in the state          ## Stay in the state
1480          !!!next-input-character;          !!!next-input-character;
1481          redo A;          redo A;
1482        } elsif ($self->{next_input_character} == 0x003E) { # >        } elsif ($self->{nc} == 0x003E) { # >
1483          if ($self->{current_token}->{type} eq 'start tag') {          if ($self->{ct}->{type} == START_TAG_TOKEN) {
1484            $self->{current_token}->{first_start_tag}            !!!cp (46);
1485                = not defined $self->{last_emitted_start_tag_name};            $self->{last_stag_name} = $self->{ct}->{tag_name};
1486            $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};          } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
         } elsif ($self->{current_token}->{type} eq 'end tag') {  
1487            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1488            if ($self->{current_token}->{attributes}) {            if ($self->{ct}->{attributes}) {
1489                !!!cp (47);
1490              !!!parse-error (type => 'end tag attribute');              !!!parse-error (type => 'end tag attribute');
1491              } else {
1492                !!!cp (48);
1493            }            }
1494          } else {          } else {
1495            die "$0: $self->{current_token}->{type}: Unknown token type";            die "$0: $self->{ct}->{type}: Unknown token type";
1496          }          }
1497          $self->{state} = 'data';          $self->{state} = DATA_STATE;
1498          !!!next-input-character;          !!!next-input-character;
1499    
1500          !!!emit ($self->{current_token}); # start tag or end tag          !!!emit ($self->{ct}); # start tag or end tag
1501    
1502          redo A;          redo A;
1503        } elsif (0x0041 <= $self->{next_input_character} and        } elsif (0x0041 <= $self->{nc} and
1504                 $self->{next_input_character} <= 0x005A) { # A..Z                 $self->{nc} <= 0x005A) { # A..Z
1505          $self->{current_attribute} = {name => chr ($self->{next_input_character} + 0x0020),          !!!cp (49);
1506                                value => ''};          $self->{ca}
1507          $self->{state} = 'attribute name';              = {name => chr ($self->{nc} + 0x0020),
1508                   value => '',
1509                   line => $self->{line}, column => $self->{column}};
1510            $self->{state} = ATTRIBUTE_NAME_STATE;
1511          !!!next-input-character;          !!!next-input-character;
1512          redo A;          redo A;
1513        } elsif ($self->{next_input_character} == 0x002F) { # /        } elsif ($self->{nc} == 0x002F) { # /
1514            !!!cp (50);
1515            $self->{state} = SELF_CLOSING_START_TAG_STATE;
1516          !!!next-input-character;          !!!next-input-character;
         if ($self->{next_input_character} == 0x003E and # >  
             $self->{current_token}->{type} eq 'start tag' and  
             $permitted_slash_tag_name->{$self->{current_token}->{tag_name}}) {  
           # permitted slash  
           #  
         } else {  
           !!!parse-error (type => 'nestc');  
         }  
         ## Stay in the state  
         # next-input-character is already done  
1517          redo A;          redo A;
1518        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
1519          !!!parse-error (type => 'unclosed tag');          !!!parse-error (type => 'unclosed tag');
1520          if ($self->{current_token}->{type} eq 'start tag') {          if ($self->{ct}->{type} == START_TAG_TOKEN) {
1521            $self->{current_token}->{first_start_tag}            !!!cp (52);
1522                = not defined $self->{last_emitted_start_tag_name};            $self->{last_stag_name} = $self->{ct}->{tag_name};
1523            $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};          } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
         } elsif ($self->{current_token}->{type} eq 'end tag') {  
1524            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1525            if ($self->{current_token}->{attributes}) {            if ($self->{ct}->{attributes}) {
1526                !!!cp (53);
1527              !!!parse-error (type => 'end tag attribute');              !!!parse-error (type => 'end tag attribute');
1528              } else {
1529                !!!cp (54);
1530            }            }
1531          } else {          } else {
1532            die "$0: $self->{current_token}->{type}: Unknown token type";            die "$0: $self->{ct}->{type}: Unknown token type";
1533          }          }
1534          $self->{state} = 'data';          $self->{state} = DATA_STATE;
1535          # reconsume          # reconsume
1536    
1537          !!!emit ($self->{current_token}); # start tag or end tag          !!!emit ($self->{ct}); # start tag or end tag
1538    
1539          redo A;          redo A;
1540        } else {        } else {
1541          $self->{current_attribute} = {name => chr ($self->{next_input_character}),          if ({
1542                                value => ''};               0x0022 => 1, # "
1543          $self->{state} = 'attribute name';               0x0027 => 1, # '
1544                 0x003D => 1, # =
1545                }->{$self->{nc}}) {
1546              !!!cp (55);
1547              !!!parse-error (type => 'bad attribute name');
1548            } else {
1549              !!!cp (56);
1550            }
1551            $self->{ca}
1552                = {name => chr ($self->{nc}),
1553                   value => '',
1554                   line => $self->{line}, column => $self->{column}};
1555            $self->{state} = ATTRIBUTE_NAME_STATE;
1556          !!!next-input-character;          !!!next-input-character;
1557          redo A;          redo A;
1558        }        }
1559      } elsif ($self->{state} eq 'attribute name') {      } elsif ($self->{state} == ATTRIBUTE_NAME_STATE) {
1560        my $before_leave = sub {        my $before_leave = sub {
1561          if (exists $self->{current_token}->{attributes} # start tag or end tag          if (exists $self->{ct}->{attributes} # start tag or end tag
1562              ->{$self->{current_attribute}->{name}}) { # MUST              ->{$self->{ca}->{name}}) { # MUST
1563            !!!parse-error (type => 'duplicate attribute:'.$self->{current_attribute}->{name});            !!!cp (57);
1564            ## Discard $self->{current_attribute} # MUST            !!!parse-error (type => 'duplicate attribute', text => $self->{ca}->{name}, line => $self->{ca}->{line}, column => $self->{ca}->{column});
1565          } else {            ## Discard $self->{ca} # MUST
1566            $self->{current_token}->{attributes}->{$self->{current_attribute}->{name}}          } else {
1567              = $self->{current_attribute};            !!!cp (58);
1568              $self->{ct}->{attributes}->{$self->{ca}->{name}}
1569                = $self->{ca};
1570          }          }
1571        }; # $before_leave        }; # $before_leave
1572    
1573        if ($self->{next_input_character} == 0x0009 or # HT        if ($is_space->{$self->{nc}}) {
1574            $self->{next_input_character} == 0x000A or # LF          !!!cp (59);
           $self->{next_input_character} == 0x000B or # VT  
           $self->{next_input_character} == 0x000C or # FF  
           $self->{next_input_character} == 0x0020) { # SP  
1575          $before_leave->();          $before_leave->();
1576          $self->{state} = 'after attribute name';          $self->{state} = AFTER_ATTRIBUTE_NAME_STATE;
1577          !!!next-input-character;          !!!next-input-character;
1578          redo A;          redo A;
1579        } elsif ($self->{next_input_character} == 0x003D) { # =        } elsif ($self->{nc} == 0x003D) { # =
1580            !!!cp (60);
1581          $before_leave->();          $before_leave->();
1582          $self->{state} = 'before attribute value';          $self->{state} = BEFORE_ATTRIBUTE_VALUE_STATE;
1583          !!!next-input-character;          !!!next-input-character;
1584          redo A;          redo A;
1585        } elsif ($self->{next_input_character} == 0x003E) { # >        } elsif ($self->{nc} == 0x003E) { # >
1586          $before_leave->();          $before_leave->();
1587          if ($self->{current_token}->{type} eq 'start tag') {          if ($self->{ct}->{type} == START_TAG_TOKEN) {
1588            $self->{current_token}->{first_start_tag}            !!!cp (61);
1589                = not defined $self->{last_emitted_start_tag_name};            $self->{last_stag_name} = $self->{ct}->{tag_name};
1590            $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};          } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
1591          } elsif ($self->{current_token}->{type} eq 'end tag') {            !!!cp (62);
1592            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1593            if ($self->{current_token}->{attributes}) {            if ($self->{ct}->{attributes}) {
1594              !!!parse-error (type => 'end tag attribute');              !!!parse-error (type => 'end tag attribute');
1595            }            }
1596          } else {          } else {
1597            die "$0: $self->{current_token}->{type}: Unknown token type";            die "$0: $self->{ct}->{type}: Unknown token type";
1598          }          }
1599          $self->{state} = 'data';          $self->{state} = DATA_STATE;
1600          !!!next-input-character;          !!!next-input-character;
1601    
1602          !!!emit ($self->{current_token}); # start tag or end tag          !!!emit ($self->{ct}); # start tag or end tag
1603    
1604          redo A;          redo A;
1605        } elsif (0x0041 <= $self->{next_input_character} and        } elsif (0x0041 <= $self->{nc} and
1606                 $self->{next_input_character} <= 0x005A) { # A..Z                 $self->{nc} <= 0x005A) { # A..Z
1607          $self->{current_attribute}->{name} .= chr ($self->{next_input_character} + 0x0020);          !!!cp (63);
1608            $self->{ca}->{name} .= chr ($self->{nc} + 0x0020);
1609          ## Stay in the state          ## Stay in the state
1610          !!!next-input-character;          !!!next-input-character;
1611          redo A;          redo A;
1612        } elsif ($self->{next_input_character} == 0x002F) { # /        } elsif ($self->{nc} == 0x002F) { # /
1613            !!!cp (64);
1614          $before_leave->();          $before_leave->();
1615            $self->{state} = SELF_CLOSING_START_TAG_STATE;
1616          !!!next-input-character;          !!!next-input-character;
         if ($self->{next_input_character} == 0x003E and # >  
             $self->{current_token}->{type} eq 'start tag' and  
             $permitted_slash_tag_name->{$self->{current_token}->{tag_name}}) {  
           # permitted slash  
           #  
         } else {  
           !!!parse-error (type => 'nestc');  
         }  
         $self->{state} = 'before attribute name';  
         # next-input-character is already done  
1617          redo A;          redo A;
1618        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
1619          !!!parse-error (type => 'unclosed tag');          !!!parse-error (type => 'unclosed tag');
1620          $before_leave->();          $before_leave->();
1621          if ($self->{current_token}->{type} eq 'start tag') {          if ($self->{ct}->{type} == START_TAG_TOKEN) {
1622            $self->{current_token}->{first_start_tag}            !!!cp (66);
1623                = not defined $self->{last_emitted_start_tag_name};            $self->{last_stag_name} = $self->{ct}->{tag_name};
1624            $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};          } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
         } elsif ($self->{current_token}->{type} eq 'end tag') {  
1625            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1626            if ($self->{current_token}->{attributes}) {            if ($self->{ct}->{attributes}) {
1627                !!!cp (67);
1628              !!!parse-error (type => 'end tag attribute');              !!!parse-error (type => 'end tag attribute');
1629              } else {
1630                ## NOTE: This state should never be reached.
1631                !!!cp (68);
1632            }            }
1633          } else {          } else {
1634            die "$0: $self->{current_token}->{type}: Unknown token type";            die "$0: $self->{ct}->{type}: Unknown token type";
1635          }          }
1636          $self->{state} = 'data';          $self->{state} = DATA_STATE;
1637          # reconsume          # reconsume
1638    
1639          !!!emit ($self->{current_token}); # start tag or end tag          !!!emit ($self->{ct}); # start tag or end tag
1640    
1641          redo A;          redo A;
1642        } else {        } else {
1643          $self->{current_attribute}->{name} .= chr ($self->{next_input_character});          if ($self->{nc} == 0x0022 or # "
1644                $self->{nc} == 0x0027) { # '
1645              !!!cp (69);
1646              !!!parse-error (type => 'bad attribute name');
1647            } else {
1648              !!!cp (70);
1649            }
1650            $self->{ca}->{name} .= chr ($self->{nc});
1651          ## Stay in the state          ## Stay in the state
1652          !!!next-input-character;          !!!next-input-character;
1653          redo A;          redo A;
1654        }        }
1655      } elsif ($self->{state} eq 'after attribute name') {      } elsif ($self->{state} == AFTER_ATTRIBUTE_NAME_STATE) {
1656        if ($self->{next_input_character} == 0x0009 or # HT        if ($is_space->{$self->{nc}}) {
1657            $self->{next_input_character} == 0x000A or # LF          !!!cp (71);
           $self->{next_input_character} == 0x000B or # VT  
           $self->{next_input_character} == 0x000C or # FF  
           $self->{next_input_character} == 0x0020) { # SP  
1658          ## Stay in the state          ## Stay in the state
1659          !!!next-input-character;          !!!next-input-character;
1660          redo A;          redo A;
1661        } elsif ($self->{next_input_character} == 0x003D) { # =        } elsif ($self->{nc} == 0x003D) { # =
1662          $self->{state} = 'before attribute value';          !!!cp (72);
1663            $self->{state} = BEFORE_ATTRIBUTE_VALUE_STATE;
1664          !!!next-input-character;          !!!next-input-character;
1665          redo A;          redo A;
1666        } elsif ($self->{next_input_character} == 0x003E) { # >        } elsif ($self->{nc} == 0x003E) { # >
1667          if ($self->{current_token}->{type} eq 'start tag') {          if ($self->{ct}->{type} == START_TAG_TOKEN) {
1668            $self->{current_token}->{first_start_tag}            !!!cp (73);
1669                = not defined $self->{last_emitted_start_tag_name};            $self->{last_stag_name} = $self->{ct}->{tag_name};
1670            $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};          } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
         } elsif ($self->{current_token}->{type} eq 'end tag') {  
1671            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1672            if ($self->{current_token}->{attributes}) {            if ($self->{ct}->{attributes}) {
1673                !!!cp (74);
1674              !!!parse-error (type => 'end tag attribute');              !!!parse-error (type => 'end tag attribute');
1675              } else {
1676                ## NOTE: This state should never be reached.
1677                !!!cp (75);
1678            }            }
1679          } else {          } else {
1680            die "$0: $self->{current_token}->{type}: Unknown token type";            die "$0: $self->{ct}->{type}: Unknown token type";
1681          }          }
1682          $self->{state} = 'data';          $self->{state} = DATA_STATE;
1683          !!!next-input-character;          !!!next-input-character;
1684    
1685          !!!emit ($self->{current_token}); # start tag or end tag          !!!emit ($self->{ct}); # start tag or end tag
1686    
1687          redo A;          redo A;
1688        } elsif (0x0041 <= $self->{next_input_character} and        } elsif (0x0041 <= $self->{nc} and
1689                 $self->{next_input_character} <= 0x005A) { # A..Z                 $self->{nc} <= 0x005A) { # A..Z
1690          $self->{current_attribute} = {name => chr ($self->{next_input_character} + 0x0020),          !!!cp (76);
1691                                value => ''};          $self->{ca}
1692          $self->{state} = 'attribute name';              = {name => chr ($self->{nc} + 0x0020),
1693                   value => '',
1694                   line => $self->{line}, column => $self->{column}};
1695            $self->{state} = ATTRIBUTE_NAME_STATE;
1696          !!!next-input-character;          !!!next-input-character;
1697          redo A;          redo A;
1698        } elsif ($self->{next_input_character} == 0x002F) { # /        } elsif ($self->{nc} == 0x002F) { # /
1699            !!!cp (77);
1700            $self->{state} = SELF_CLOSING_START_TAG_STATE;
1701          !!!next-input-character;          !!!next-input-character;
         if ($self->{next_input_character} == 0x003E and # >  
             $self->{current_token}->{type} eq 'start tag' and  
             $permitted_slash_tag_name->{$self->{current_token}->{tag_name}}) {  
           # permitted slash  
           #  
         } else {  
           !!!parse-error (type => 'nestc');  
           ## TODO: Different error type for <aa / bb> than <aa/>  
         }  
         $self->{state} = 'before attribute name';  
         # next-input-character is already done  
1702          redo A;          redo A;
1703        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
1704          !!!parse-error (type => 'unclosed tag');          !!!parse-error (type => 'unclosed tag');
1705          if ($self->{current_token}->{type} eq 'start tag') {          if ($self->{ct}->{type} == START_TAG_TOKEN) {
1706            $self->{current_token}->{first_start_tag}            !!!cp (79);
1707                = not defined $self->{last_emitted_start_tag_name};            $self->{last_stag_name} = $self->{ct}->{tag_name};
1708            $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};          } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
         } elsif ($self->{current_token}->{type} eq 'end tag') {  
1709            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1710            if ($self->{current_token}->{attributes}) {            if ($self->{ct}->{attributes}) {
1711                !!!cp (80);
1712              !!!parse-error (type => 'end tag attribute');              !!!parse-error (type => 'end tag attribute');
1713              } else {
1714                ## NOTE: This state should never be reached.
1715                !!!cp (81);
1716            }            }
1717          } else {          } else {
1718            die "$0: $self->{current_token}->{type}: Unknown token type";            die "$0: $self->{ct}->{type}: Unknown token type";
1719          }          }
1720          $self->{state} = 'data';          $self->{state} = DATA_STATE;
1721          # reconsume          # reconsume
1722    
1723          !!!emit ($self->{current_token}); # start tag or end tag          !!!emit ($self->{ct}); # start tag or end tag
1724    
1725          redo A;          redo A;
1726        } else {        } else {
1727          $self->{current_attribute} = {name => chr ($self->{next_input_character}),          if ($self->{nc} == 0x0022 or # "
1728                                value => ''};              $self->{nc} == 0x0027) { # '
1729          $self->{state} = 'attribute name';            !!!cp (78);
1730              !!!parse-error (type => 'bad attribute name');
1731            } else {
1732              !!!cp (82);
1733            }
1734            $self->{ca}
1735                = {name => chr ($self->{nc}),
1736                   value => '',
1737                   line => $self->{line}, column => $self->{column}};
1738            $self->{state} = ATTRIBUTE_NAME_STATE;
1739          !!!next-input-character;          !!!next-input-character;
1740          redo A;                  redo A;        
1741        }        }
1742      } elsif ($self->{state} eq 'before attribute value') {      } elsif ($self->{state} == BEFORE_ATTRIBUTE_VALUE_STATE) {
1743        if ($self->{next_input_character} == 0x0009 or # HT        if ($is_space->{$self->{nc}}) {
1744            $self->{next_input_character} == 0x000A or # LF          !!!cp (83);
           $self->{next_input_character} == 0x000B or # VT  
           $self->{next_input_character} == 0x000C or # FF  
           $self->{next_input_character} == 0x0020) { # SP        
1745          ## Stay in the state          ## Stay in the state
1746          !!!next-input-character;          !!!next-input-character;
1747          redo A;          redo A;
1748        } elsif ($self->{next_input_character} == 0x0022) { # "        } elsif ($self->{nc} == 0x0022) { # "
1749          $self->{state} = 'attribute value (double-quoted)';          !!!cp (84);
1750            $self->{state} = ATTRIBUTE_VALUE_DOUBLE_QUOTED_STATE;
1751          !!!next-input-character;          !!!next-input-character;
1752          redo A;          redo A;
1753        } elsif ($self->{next_input_character} == 0x0026) { # &        } elsif ($self->{nc} == 0x0026) { # &
1754          $self->{state} = 'attribute value (unquoted)';          !!!cp (85);
1755            $self->{state} = ATTRIBUTE_VALUE_UNQUOTED_STATE;
1756          ## reconsume          ## reconsume
1757          redo A;          redo A;
1758        } elsif ($self->{next_input_character} == 0x0027) { # '        } elsif ($self->{nc} == 0x0027) { # '
1759          $self->{state} = 'attribute value (single-quoted)';          !!!cp (86);
1760            $self->{state} = ATTRIBUTE_VALUE_SINGLE_QUOTED_STATE;
1761          !!!next-input-character;          !!!next-input-character;
1762          redo A;          redo A;
1763        } elsif ($self->{next_input_character} == 0x003E) { # >        } elsif ($self->{nc} == 0x003E) { # >
1764          if ($self->{current_token}->{type} eq 'start tag') {          !!!parse-error (type => 'empty unquoted attribute value');
1765            $self->{current_token}->{first_start_tag}          if ($self->{ct}->{type} == START_TAG_TOKEN) {
1766                = not defined $self->{last_emitted_start_tag_name};            !!!cp (87);
1767            $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};            $self->{last_stag_name} = $self->{ct}->{tag_name};
1768          } elsif ($self->{current_token}->{type} eq 'end tag') {          } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
1769            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1770            if ($self->{current_token}->{attributes}) {            if ($self->{ct}->{attributes}) {
1771                !!!cp (88);
1772              !!!parse-error (type => 'end tag attribute');              !!!parse-error (type => 'end tag attribute');
1773              } else {
1774                ## NOTE: This state should never be reached.
1775                !!!cp (89);
1776            }            }
1777          } else {          } else {
1778            die "$0: $self->{current_token}->{type}: Unknown token type";            die "$0: $self->{ct}->{type}: Unknown token type";
1779          }          }
1780          $self->{state} = 'data';          $self->{state} = DATA_STATE;
1781          !!!next-input-character;          !!!next-input-character;
1782    
1783          !!!emit ($self->{current_token}); # start tag or end tag          !!!emit ($self->{ct}); # start tag or end tag
1784    
1785          redo A;          redo A;
1786        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
1787          !!!parse-error (type => 'unclosed tag');          !!!parse-error (type => 'unclosed tag');
1788          if ($self->{current_token}->{type} eq 'start tag') {          if ($self->{ct}->{type} == START_TAG_TOKEN) {
1789            $self->{current_token}->{first_start_tag}            !!!cp (90);
1790                = not defined $self->{last_emitted_start_tag_name};            $self->{last_stag_name} = $self->{ct}->{tag_name};
1791            $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};          } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
         } elsif ($self->{current_token}->{type} eq 'end tag') {  
1792            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1793            if ($self->{current_token}->{attributes}) {            if ($self->{ct}->{attributes}) {
1794                !!!cp (91);
1795              !!!parse-error (type => 'end tag attribute');              !!!parse-error (type => 'end tag attribute');
1796              } else {
1797                ## NOTE: This state should never be reached.
1798                !!!cp (92);
1799            }            }
1800          } else {          } else {
1801            die "$0: $self->{current_token}->{type}: Unknown token type";            die "$0: $self->{ct}->{type}: Unknown token type";
1802          }          }
1803          $self->{state} = 'data';          $self->{state} = DATA_STATE;
1804          ## reconsume          ## reconsume
1805    
1806          !!!emit ($self->{current_token}); # start tag or end tag          !!!emit ($self->{ct}); # start tag or end tag
1807    
1808          redo A;          redo A;
1809        } else {        } else {
1810          $self->{current_attribute}->{value} .= chr ($self->{next_input_character});          if ($self->{nc} == 0x003D) { # =
1811          $self->{state} = 'attribute value (unquoted)';            !!!cp (93);
1812              !!!parse-error (type => 'bad attribute value');
1813            } else {
1814              !!!cp (94);
1815            }
1816            $self->{ca}->{value} .= chr ($self->{nc});
1817            $self->{state} = ATTRIBUTE_VALUE_UNQUOTED_STATE;
1818          !!!next-input-character;          !!!next-input-character;
1819          redo A;          redo A;
1820        }        }
1821      } elsif ($self->{state} eq 'attribute value (double-quoted)') {      } elsif ($self->{state} == ATTRIBUTE_VALUE_DOUBLE_QUOTED_STATE) {
1822        if ($self->{next_input_character} == 0x0022) { # "        if ($self->{nc} == 0x0022) { # "
1823          $self->{state} = 'before attribute name';          !!!cp (95);
1824            $self->{state} = AFTER_ATTRIBUTE_VALUE_QUOTED_STATE;
1825          !!!next-input-character;          !!!next-input-character;
1826          redo A;          redo A;
1827        } elsif ($self->{next_input_character} == 0x0026) { # &        } elsif ($self->{nc} == 0x0026) { # &
1828          $self->{last_attribute_value_state} = 'attribute value (double-quoted)';          !!!cp (96);
1829          $self->{state} = 'entity in attribute value';          ## NOTE: In the spec, the tokenizer is switched to the
1830            ## "entity in attribute value state".  In this implementation, the
1831            ## tokenizer is switched to the |ENTITY_STATE|, which is an
1832            ## implementation of the "consume a character reference" algorithm.
1833            $self->{prev_state} = $self->{state};
1834            $self->{entity_add} = 0x0022; # "
1835            $self->{state} = ENTITY_STATE;
1836          !!!next-input-character;          !!!next-input-character;
1837          redo A;          redo A;
1838        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
1839          !!!parse-error (type => 'unclosed attribute value');          !!!parse-error (type => 'unclosed attribute value');
1840          if ($self->{current_token}->{type} eq 'start tag') {          if ($self->{ct}->{type} == START_TAG_TOKEN) {
1841            $self->{current_token}->{first_start_tag}            !!!cp (97);
1842                = not defined $self->{last_emitted_start_tag_name};            $self->{last_stag_name} = $self->{ct}->{tag_name};
1843            $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};          } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
         } elsif ($self->{current_token}->{type} eq 'end tag') {  
1844            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1845            if ($self->{current_token}->{attributes}) {            if ($self->{ct}->{attributes}) {
1846                !!!cp (98);
1847              !!!parse-error (type => 'end tag attribute');              !!!parse-error (type => 'end tag attribute');
1848              } else {
1849                ## NOTE: This state should never be reached.
1850                !!!cp (99);
1851            }            }
1852          } else {          } else {
1853            die "$0: $self->{current_token}->{type}: Unknown token type";            die "$0: $self->{ct}->{type}: Unknown token type";
1854          }          }
1855          $self->{state} = 'data';          $self->{state} = DATA_STATE;
1856          ## reconsume          ## reconsume
1857    
1858          !!!emit ($self->{current_token}); # start tag or end tag          !!!emit ($self->{ct}); # start tag or end tag
1859    
1860          redo A;          redo A;
1861        } else {        } else {
1862          $self->{current_attribute}->{value} .= chr ($self->{next_input_character});          !!!cp (100);
1863            $self->{ca}->{value} .= chr ($self->{nc});
1864            $self->{read_until}->($self->{ca}->{value},
1865                                  q["&],
1866                                  length $self->{ca}->{value});
1867    
1868          ## Stay in the state          ## Stay in the state
1869          !!!next-input-character;          !!!next-input-character;
1870          redo A;          redo A;
1871        }        }
1872      } elsif ($self->{state} eq 'attribute value (single-quoted)') {      } elsif ($self->{state} == ATTRIBUTE_VALUE_SINGLE_QUOTED_STATE) {
1873        if ($self->{next_input_character} == 0x0027) { # '        if ($self->{nc} == 0x0027) { # '
1874          $self->{state} = 'before attribute name';          !!!cp (101);
1875          !!!next-input-character;          $self->{state} = AFTER_ATTRIBUTE_VALUE_QUOTED_STATE;
1876          redo A;          !!!next-input-character;
1877        } elsif ($self->{next_input_character} == 0x0026) { # &          redo A;
1878          $self->{last_attribute_value_state} = 'attribute value (single-quoted)';        } elsif ($self->{nc} == 0x0026) { # &
1879          $self->{state} = 'entity in attribute value';          !!!cp (102);
1880            ## NOTE: In the spec, the tokenizer is switched to the
1881            ## "entity in attribute value state".  In this implementation, the
1882            ## tokenizer is switched to the |ENTITY_STATE|, which is an
1883            ## implementation of the "consume a character reference" algorithm.
1884            $self->{entity_add} = 0x0027; # '
1885            $self->{prev_state} = $self->{state};
1886            $self->{state} = ENTITY_STATE;
1887          !!!next-input-character;          !!!next-input-character;
1888          redo A;          redo A;
1889        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
1890          !!!parse-error (type => 'unclosed attribute value');          !!!parse-error (type => 'unclosed attribute value');
1891          if ($self->{current_token}->{type} eq 'start tag') {          if ($self->{ct}->{type} == START_TAG_TOKEN) {
1892            $self->{current_token}->{first_start_tag}            !!!cp (103);
1893                = not defined $self->{last_emitted_start_tag_name};            $self->{last_stag_name} = $self->{ct}->{tag_name};
1894            $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};          } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
         } elsif ($self->{current_token}->{type} eq 'end tag') {  
1895            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1896            if ($self->{current_token}->{attributes}) {            if ($self->{ct}->{attributes}) {
1897                !!!cp (104);
1898              !!!parse-error (type => 'end tag attribute');              !!!parse-error (type => 'end tag attribute');
1899              } else {
1900                ## NOTE: This state should never be reached.
1901                !!!cp (105);
1902            }            }
1903          } else {          } else {
1904            die "$0: $self->{current_token}->{type}: Unknown token type";            die "$0: $self->{ct}->{type}: Unknown token type";
1905          }          }
1906          $self->{state} = 'data';          $self->{state} = DATA_STATE;
1907          ## reconsume          ## reconsume
1908    
1909          !!!emit ($self->{current_token}); # start tag or end tag          !!!emit ($self->{ct}); # start tag or end tag
1910    
1911          redo A;          redo A;
1912        } else {        } else {
1913          $self->{current_attribute}->{value} .= chr ($self->{next_input_character});          !!!cp (106);
1914            $self->{ca}->{value} .= chr ($self->{nc});
1915            $self->{read_until}->($self->{ca}->{value},
1916                                  q['&],
1917                                  length $self->{ca}->{value});
1918    
1919          ## Stay in the state          ## Stay in the state
1920          !!!next-input-character;          !!!next-input-character;
1921          redo A;          redo A;
1922        }        }
1923      } elsif ($self->{state} eq 'attribute value (unquoted)') {      } elsif ($self->{state} == ATTRIBUTE_VALUE_UNQUOTED_STATE) {
1924        if ($self->{next_input_character} == 0x0009 or # HT        if ($is_space->{$self->{nc}}) {
1925            $self->{next_input_character} == 0x000A or # LF          !!!cp (107);
1926            $self->{next_input_character} == 0x000B or # HT          $self->{state} = BEFORE_ATTRIBUTE_NAME_STATE;
1927            $self->{next_input_character} == 0x000C or # FF          !!!next-input-character;
1928            $self->{next_input_character} == 0x0020) { # SP          redo A;
1929          $self->{state} = 'before attribute name';        } elsif ($self->{nc} == 0x0026) { # &
1930          !!!next-input-character;          !!!cp (108);
1931          redo A;          ## NOTE: In the spec, the tokenizer is switched to the
1932        } elsif ($self->{next_input_character} == 0x0026) { # &          ## "entity in attribute value state".  In this implementation, the
1933          $self->{last_attribute_value_state} = 'attribute value (unquoted)';          ## tokenizer is switched to the |ENTITY_STATE|, which is an
1934          $self->{state} = 'entity in attribute value';          ## implementation of the "consume a character reference" algorithm.
1935          !!!next-input-character;          $self->{entity_add} = -1;
1936          redo A;          $self->{prev_state} = $self->{state};
1937        } elsif ($self->{next_input_character} == 0x003E) { # >          $self->{state} = ENTITY_STATE;
1938          if ($self->{current_token}->{type} eq 'start tag') {          !!!next-input-character;
1939            $self->{current_token}->{first_start_tag}          redo A;
1940                = not defined $self->{last_emitted_start_tag_name};        } elsif ($self->{nc} == 0x003E) { # >
1941            $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};          if ($self->{ct}->{type} == START_TAG_TOKEN) {
1942          } elsif ($self->{current_token}->{type} eq 'end tag') {            !!!cp (109);
1943              $self->{last_stag_name} = $self->{ct}->{tag_name};
1944            } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
1945            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1946            if ($self->{current_token}->{attributes}) {            if ($self->{ct}->{attributes}) {
1947                !!!cp (110);
1948              !!!parse-error (type => 'end tag attribute');              !!!parse-error (type => 'end tag attribute');
1949              } else {
1950                ## NOTE: This state should never be reached.
1951                !!!cp (111);
1952            }            }
1953          } else {          } else {
1954            die "$0: $self->{current_token}->{type}: Unknown token type";            die "$0: $self->{ct}->{type}: Unknown token type";
1955          }          }
1956          $self->{state} = 'data';          $self->{state} = DATA_STATE;
1957          !!!next-input-character;          !!!next-input-character;
1958    
1959          !!!emit ($self->{current_token}); # start tag or end tag          !!!emit ($self->{ct}); # start tag or end tag
1960    
1961          redo A;          redo A;
1962        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
1963          !!!parse-error (type => 'unclosed tag');          !!!parse-error (type => 'unclosed tag');
1964          if ($self->{current_token}->{type} eq 'start tag') {          if ($self->{ct}->{type} == START_TAG_TOKEN) {
1965            $self->{current_token}->{first_start_tag}            !!!cp (112);
1966                = not defined $self->{last_emitted_start_tag_name};            $self->{last_stag_name} = $self->{ct}->{tag_name};
1967            $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};          } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
         } elsif ($self->{current_token}->{type} eq 'end tag') {  
1968            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
1969            if ($self->{current_token}->{attributes}) {            if ($self->{ct}->{attributes}) {
1970                !!!cp (113);
1971              !!!parse-error (type => 'end tag attribute');              !!!parse-error (type => 'end tag attribute');
1972              } else {
1973                ## NOTE: This state should never be reached.
1974                !!!cp (114);
1975            }            }
1976          } else {          } else {
1977            die "$0: $self->{current_token}->{type}: Unknown token type";            die "$0: $self->{ct}->{type}: Unknown token type";
1978          }          }
1979          $self->{state} = 'data';          $self->{state} = DATA_STATE;
1980          ## reconsume          ## reconsume
1981    
1982          !!!emit ($self->{current_token}); # start tag or end tag          !!!emit ($self->{ct}); # start tag or end tag
1983    
1984          redo A;          redo A;
1985        } else {        } else {
1986          $self->{current_attribute}->{value} .= chr ($self->{next_input_character});          if ({
1987                 0x0022 => 1, # "
1988                 0x0027 => 1, # '
1989                 0x003D => 1, # =
1990                }->{$self->{nc}}) {
1991              !!!cp (115);
1992              !!!parse-error (type => 'bad attribute value');
1993            } else {
1994              !!!cp (116);
1995            }
1996            $self->{ca}->{value} .= chr ($self->{nc});
1997            $self->{read_until}->($self->{ca}->{value},
1998                                  q["'=& >],
1999                                  length $self->{ca}->{value});
2000    
2001          ## Stay in the state          ## Stay in the state
2002          !!!next-input-character;          !!!next-input-character;
2003          redo A;          redo A;
2004        }        }
2005      } elsif ($self->{state} eq 'entity in attribute value') {      } elsif ($self->{state} == AFTER_ATTRIBUTE_VALUE_QUOTED_STATE) {
2006        my $token = $self->_tokenize_attempt_to_consume_an_entity (1);        if ($is_space->{$self->{nc}}) {
2007            !!!cp (118);
2008            $self->{state} = BEFORE_ATTRIBUTE_NAME_STATE;
2009            !!!next-input-character;
2010            redo A;
2011          } elsif ($self->{nc} == 0x003E) { # >
2012            if ($self->{ct}->{type} == START_TAG_TOKEN) {
2013              !!!cp (119);
2014              $self->{last_stag_name} = $self->{ct}->{tag_name};
2015            } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
2016              $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
2017              if ($self->{ct}->{attributes}) {
2018                !!!cp (120);
2019                !!!parse-error (type => 'end tag attribute');
2020              } else {
2021                ## NOTE: This state should never be reached.
2022                !!!cp (121);
2023              }
2024            } else {
2025              die "$0: $self->{ct}->{type}: Unknown token type";
2026            }
2027            $self->{state} = DATA_STATE;
2028            !!!next-input-character;
2029    
2030        unless (defined $token) {          !!!emit ($self->{ct}); # start tag or end tag
2031          $self->{current_attribute}->{value} .= '&';  
2032            redo A;
2033          } elsif ($self->{nc} == 0x002F) { # /
2034            !!!cp (122);
2035            $self->{state} = SELF_CLOSING_START_TAG_STATE;
2036            !!!next-input-character;
2037            redo A;
2038          } elsif ($self->{nc} == -1) {
2039            !!!parse-error (type => 'unclosed tag');
2040            if ($self->{ct}->{type} == START_TAG_TOKEN) {
2041              !!!cp (122.3);
2042              $self->{last_stag_name} = $self->{ct}->{tag_name};
2043            } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
2044              if ($self->{ct}->{attributes}) {
2045                !!!cp (122.1);
2046                !!!parse-error (type => 'end tag attribute');
2047              } else {
2048                ## NOTE: This state should never be reached.
2049                !!!cp (122.2);
2050              }
2051            } else {
2052              die "$0: $self->{ct}->{type}: Unknown token type";
2053            }
2054            $self->{state} = DATA_STATE;
2055            ## Reconsume.
2056            !!!emit ($self->{ct}); # start tag or end tag
2057            redo A;
2058        } else {        } else {
2059          $self->{current_attribute}->{value} .= $token->{data};          !!!cp ('124.1');
2060          ## ISSUE: spec says "append the returned character token to the current attribute's value"          !!!parse-error (type => 'no space between attributes');
2061            $self->{state} = BEFORE_ATTRIBUTE_NAME_STATE;
2062            ## reconsume
2063            redo A;
2064        }        }
2065        } elsif ($self->{state} == SELF_CLOSING_START_TAG_STATE) {
2066          if ($self->{nc} == 0x003E) { # >
2067            if ($self->{ct}->{type} == END_TAG_TOKEN) {
2068              !!!cp ('124.2');
2069              !!!parse-error (type => 'nestc', token => $self->{ct});
2070              ## TODO: Different type than slash in start tag
2071              $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
2072              if ($self->{ct}->{attributes}) {
2073                !!!cp ('124.4');
2074                !!!parse-error (type => 'end tag attribute');
2075              } else {
2076                !!!cp ('124.5');
2077              }
2078              ## TODO: Test |<title></title/>|
2079            } else {
2080              !!!cp ('124.3');
2081              $self->{self_closing} = 1;
2082            }
2083    
2084        $self->{state} = $self->{last_attribute_value_state};          $self->{state} = DATA_STATE;
2085        # next-input-character is already done          !!!next-input-character;
       redo A;  
     } elsif ($self->{state} eq 'bogus comment') {  
       ## (only happen if PCDATA state)  
         
       my $token = {type => 'comment', data => ''};  
   
       BC: {  
         if ($self->{next_input_character} == 0x003E) { # >  
           $self->{state} = 'data';  
           !!!next-input-character;  
   
           !!!emit ($token);  
   
           redo A;  
         } elsif ($self->{next_input_character} == -1) {  
           $self->{state} = 'data';  
           ## reconsume  
2086    
2087            !!!emit ($token);          !!!emit ($self->{ct}); # start tag or end tag
2088    
2089            redo A;          redo A;
2090          } elsif ($self->{nc} == -1) {
2091            !!!parse-error (type => 'unclosed tag');
2092            if ($self->{ct}->{type} == START_TAG_TOKEN) {
2093              !!!cp (124.7);
2094              $self->{last_stag_name} = $self->{ct}->{tag_name};
2095            } elsif ($self->{ct}->{type} == END_TAG_TOKEN) {
2096              if ($self->{ct}->{attributes}) {
2097                !!!cp (124.5);
2098                !!!parse-error (type => 'end tag attribute');
2099              } else {
2100                ## NOTE: This state should never be reached.
2101                !!!cp (124.6);
2102              }
2103          } else {          } else {
2104            $token->{data} .= chr ($self->{next_input_character});            die "$0: $self->{ct}->{type}: Unknown token type";
           !!!next-input-character;  
           redo BC;  
2105          }          }
2106        } # BC          $self->{state} = DATA_STATE;
2107      } elsif ($self->{state} eq 'markup declaration open') {          ## Reconsume.
2108            !!!emit ($self->{ct}); # start tag or end tag
2109            redo A;
2110          } else {
2111            !!!cp ('124.4');
2112            !!!parse-error (type => 'nestc');
2113            ## TODO: This error type is wrong.
2114            $self->{state} = BEFORE_ATTRIBUTE_NAME_STATE;
2115            ## Reconsume.
2116            redo A;
2117          }
2118        } elsif ($self->{state} == BOGUS_COMMENT_STATE) {
2119        ## (only happen if PCDATA state)        ## (only happen if PCDATA state)
2120    
2121        my @next_char;        ## NOTE: Unlike spec's "bogus comment state", this implementation
2122        push @next_char, $self->{next_input_character};        ## consumes characters one-by-one basis.
2123                
2124        if ($self->{next_input_character} == 0x002D) { # -        if ($self->{nc} == 0x003E) { # >
2125            !!!cp (124);
2126            $self->{state} = DATA_STATE;
2127          !!!next-input-character;          !!!next-input-character;
2128          push @next_char, $self->{next_input_character};  
2129          if ($self->{next_input_character} == 0x002D) { # -          !!!emit ($self->{ct}); # comment
2130            $self->{current_token} = {type => 'comment', data => ''};          redo A;
2131            $self->{state} = 'comment start';        } elsif ($self->{nc} == -1) {
2132            !!!next-input-character;          !!!cp (125);
2133            redo A;          $self->{state} = DATA_STATE;
2134          }          ## reconsume
2135        } elsif ($self->{next_input_character} == 0x0044 or # D  
2136                 $self->{next_input_character} == 0x0064) { # d          !!!emit ($self->{ct}); # comment
2137            redo A;
2138          } else {
2139            !!!cp (126);
2140            $self->{ct}->{data} .= chr ($self->{nc}); # comment
2141            $self->{read_until}->($self->{ct}->{data},
2142                                  q[>],
2143                                  length $self->{ct}->{data});
2144    
2145            ## Stay in the state.
2146          !!!next-input-character;          !!!next-input-character;
2147          push @next_char, $self->{next_input_character};          redo A;
2148          if ($self->{next_input_character} == 0x004F or # O        }
2149              $self->{next_input_character} == 0x006F) { # o      } elsif ($self->{state} == MARKUP_DECLARATION_OPEN_STATE) {
2150            !!!next-input-character;        ## (only happen if PCDATA state)
2151            push @next_char, $self->{next_input_character};        
2152            if ($self->{next_input_character} == 0x0043 or # C        if ($self->{nc} == 0x002D) { # -
2153                $self->{next_input_character} == 0x0063) { # c          !!!cp (133);
2154              !!!next-input-character;          $self->{state} = MD_HYPHEN_STATE;
2155              push @next_char, $self->{next_input_character};          !!!next-input-character;
2156              if ($self->{next_input_character} == 0x0054 or # T          redo A;
2157                  $self->{next_input_character} == 0x0074) { # t        } elsif ($self->{nc} == 0x0044 or # D
2158                !!!next-input-character;                 $self->{nc} == 0x0064) { # d
2159                push @next_char, $self->{next_input_character};          ## ASCII case-insensitive.
2160                if ($self->{next_input_character} == 0x0059 or # Y          !!!cp (130);
2161                    $self->{next_input_character} == 0x0079) { # y          $self->{state} = MD_DOCTYPE_STATE;
2162                  !!!next-input-character;          $self->{s_kwd} = chr $self->{nc};
2163                  push @next_char, $self->{next_input_character};          !!!next-input-character;
2164                  if ($self->{next_input_character} == 0x0050 or # P          redo A;
2165                      $self->{next_input_character} == 0x0070) { # p        } elsif ($self->{insertion_mode} & IN_FOREIGN_CONTENT_IM and
2166                    !!!next-input-character;                 $self->{open_elements}->[-1]->[1] & FOREIGN_EL and
2167                    push @next_char, $self->{next_input_character};                 $self->{nc} == 0x005B) { # [
2168                    if ($self->{next_input_character} == 0x0045 or # E          !!!cp (135.4);                
2169                        $self->{next_input_character} == 0x0065) { # e          $self->{state} = MD_CDATA_STATE;
2170                      ## ISSUE: What a stupid code this is!          $self->{s_kwd} = '[';
2171                      $self->{state} = 'DOCTYPE';          !!!next-input-character;
2172                      !!!next-input-character;          redo A;
2173                      redo A;        } else {
2174                    }          !!!cp (136);
                 }  
               }  
             }  
           }  
         }  
2175        }        }
2176    
2177        !!!parse-error (type => 'bogus comment');        !!!parse-error (type => 'bogus comment',
2178        $self->{next_input_character} = shift @next_char;                        line => $self->{line_prev},
2179        !!!back-next-input-character (@next_char);                        column => $self->{column_prev} - 1);
2180        $self->{state} = 'bogus comment';        ## Reconsume.
2181          $self->{state} = BOGUS_COMMENT_STATE;
2182          $self->{ct} = {type => COMMENT_TOKEN, data => '',
2183                                    line => $self->{line_prev},
2184                                    column => $self->{column_prev} - 1,
2185                                   };
2186        redo A;        redo A;
2187              } elsif ($self->{state} == MD_HYPHEN_STATE) {
2188        ## ISSUE: typos in spec: chacacters, is is a parse error        if ($self->{nc} == 0x002D) { # -
2189        ## ISSUE: spec is somewhat unclear on "is the first character that will be in the comment"; what is "that will be in the comment" is what the algorithm defines, isn't it?          !!!cp (127);
2190      } elsif ($self->{state} eq 'comment start') {          $self->{ct} = {type => COMMENT_TOKEN, data => '',
2191        if ($self->{next_input_character} == 0x002D) { # -                                    line => $self->{line_prev},
2192          $self->{state} = 'comment start dash';                                    column => $self->{column_prev} - 2,
2193                                     };
2194            $self->{state} = COMMENT_START_STATE;
2195            !!!next-input-character;
2196            redo A;
2197          } else {
2198            !!!cp (128);
2199            !!!parse-error (type => 'bogus comment',
2200                            line => $self->{line_prev},
2201                            column => $self->{column_prev} - 2);
2202            $self->{state} = BOGUS_COMMENT_STATE;
2203            ## Reconsume.
2204            $self->{ct} = {type => COMMENT_TOKEN,
2205                                      data => '-',
2206                                      line => $self->{line_prev},
2207                                      column => $self->{column_prev} - 2,
2208                                     };
2209            redo A;
2210          }
2211        } elsif ($self->{state} == MD_DOCTYPE_STATE) {
2212          ## ASCII case-insensitive.
2213          if ($self->{nc} == [
2214                undef,
2215                0x004F, # O
2216                0x0043, # C
2217                0x0054, # T
2218                0x0059, # Y
2219                0x0050, # P
2220              ]->[length $self->{s_kwd}] or
2221              $self->{nc} == [
2222                undef,
2223                0x006F, # o
2224                0x0063, # c
2225                0x0074, # t
2226                0x0079, # y
2227                0x0070, # p
2228              ]->[length $self->{s_kwd}]) {
2229            !!!cp (131);
2230            ## Stay in the state.
2231            $self->{s_kwd} .= chr $self->{nc};
2232            !!!next-input-character;
2233            redo A;
2234          } elsif ((length $self->{s_kwd}) == 6 and
2235                   ($self->{nc} == 0x0045 or # E
2236                    $self->{nc} == 0x0065)) { # e
2237            !!!cp (129);
2238            $self->{state} = DOCTYPE_STATE;
2239            $self->{ct} = {type => DOCTYPE_TOKEN,
2240                                      quirks => 1,
2241                                      line => $self->{line_prev},
2242                                      column => $self->{column_prev} - 7,
2243                                     };
2244            !!!next-input-character;
2245            redo A;
2246          } else {
2247            !!!cp (132);        
2248            !!!parse-error (type => 'bogus comment',
2249                            line => $self->{line_prev},
2250                            column => $self->{column_prev} - 1 - length $self->{s_kwd});
2251            $self->{state} = BOGUS_COMMENT_STATE;
2252            ## Reconsume.
2253            $self->{ct} = {type => COMMENT_TOKEN,
2254                                      data => $self->{s_kwd},
2255                                      line => $self->{line_prev},
2256                                      column => $self->{column_prev} - 1 - length $self->{s_kwd},
2257                                     };
2258            redo A;
2259          }
2260        } elsif ($self->{state} == MD_CDATA_STATE) {
2261          if ($self->{nc} == {
2262                '[' => 0x0043, # C
2263                '[C' => 0x0044, # D
2264                '[CD' => 0x0041, # A
2265                '[CDA' => 0x0054, # T
2266                '[CDAT' => 0x0041, # A
2267              }->{$self->{s_kwd}}) {
2268            !!!cp (135.1);
2269            ## Stay in the state.
2270            $self->{s_kwd} .= chr $self->{nc};
2271            !!!next-input-character;
2272            redo A;
2273          } elsif ($self->{s_kwd} eq '[CDATA' and
2274                   $self->{nc} == 0x005B) { # [
2275            !!!cp (135.2);
2276            $self->{ct} = {type => CHARACTER_TOKEN,
2277                                      data => '',
2278                                      line => $self->{line_prev},
2279                                      column => $self->{column_prev} - 7};
2280            $self->{state} = CDATA_SECTION_STATE;
2281            !!!next-input-character;
2282            redo A;
2283          } else {
2284            !!!cp (135.3);
2285            !!!parse-error (type => 'bogus comment',
2286                            line => $self->{line_prev},
2287                            column => $self->{column_prev} - 1 - length $self->{s_kwd});
2288            $self->{state} = BOGUS_COMMENT_STATE;
2289            ## Reconsume.
2290            $self->{ct} = {type => COMMENT_TOKEN,
2291                                      data => $self->{s_kwd},
2292                                      line => $self->{line_prev},
2293                                      column => $self->{column_prev} - 1 - length $self->{s_kwd},
2294                                     };
2295            redo A;
2296          }
2297        } elsif ($self->{state} == COMMENT_START_STATE) {
2298          if ($self->{nc} == 0x002D) { # -
2299            !!!cp (137);
2300            $self->{state} = COMMENT_START_DASH_STATE;
2301          !!!next-input-character;          !!!next-input-character;
2302          redo A;          redo A;
2303        } elsif ($self->{next_input_character} == 0x003E) { # >        } elsif ($self->{nc} == 0x003E) { # >
2304            !!!cp (138);
2305          !!!parse-error (type => 'bogus comment');          !!!parse-error (type => 'bogus comment');
2306          $self->{state} = 'data';          $self->{state} = DATA_STATE;
2307          !!!next-input-character;          !!!next-input-character;
2308    
2309          !!!emit ($self->{current_token}); # comment          !!!emit ($self->{ct}); # comment
2310    
2311          redo A;          redo A;
2312        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
2313            !!!cp (139);
2314          !!!parse-error (type => 'unclosed comment');          !!!parse-error (type => 'unclosed comment');
2315          $self->{state} = 'data';          $self->{state} = DATA_STATE;
2316          ## reconsume          ## reconsume
2317    
2318          !!!emit ($self->{current_token}); # comment          !!!emit ($self->{ct}); # comment
2319    
2320          redo A;          redo A;
2321        } else {        } else {
2322          $self->{current_token}->{data} # comment          !!!cp (140);
2323              .= chr ($self->{next_input_character});          $self->{ct}->{data} # comment
2324          $self->{state} = 'comment';              .= chr ($self->{nc});
2325            $self->{state} = COMMENT_STATE;
2326          !!!next-input-character;          !!!next-input-character;
2327          redo A;          redo A;
2328        }        }
2329      } elsif ($self->{state} eq 'comment start dash') {      } elsif ($self->{state} == COMMENT_START_DASH_STATE) {
2330        if ($self->{next_input_character} == 0x002D) { # -        if ($self->{nc} == 0x002D) { # -
2331          $self->{state} = 'comment end';          !!!cp (141);
2332            $self->{state} = COMMENT_END_STATE;
2333          !!!next-input-character;          !!!next-input-character;
2334          redo A;          redo A;
2335        } elsif ($self->{next_input_character} == 0x003E) { # >        } elsif ($self->{nc} == 0x003E) { # >
2336            !!!cp (142);
2337          !!!parse-error (type => 'bogus comment');          !!!parse-error (type => 'bogus comment');
2338          $self->{state} = 'data';          $self->{state} = DATA_STATE;
2339          !!!next-input-character;          !!!next-input-character;
2340    
2341          !!!emit ($self->{current_token}); # comment          !!!emit ($self->{ct}); # comment
2342    
2343          redo A;          redo A;
2344        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
2345            !!!cp (143);
2346          !!!parse-error (type => 'unclosed comment');          !!!parse-error (type => 'unclosed comment');
2347          $self->{state} = 'data';          $self->{state} = DATA_STATE;
2348          ## reconsume          ## reconsume
2349    
2350          !!!emit ($self->{current_token}); # comment          !!!emit ($self->{ct}); # comment
2351    
2352          redo A;          redo A;
2353        } else {        } else {
2354          $self->{current_token}->{data} # comment          !!!cp (144);
2355              .= '-' . chr ($self->{next_input_character});          $self->{ct}->{data} # comment
2356          $self->{state} = 'comment';              .= '-' . chr ($self->{nc});
2357            $self->{state} = COMMENT_STATE;
2358          !!!next-input-character;          !!!next-input-character;
2359          redo A;          redo A;
2360        }        }
2361      } elsif ($self->{state} eq 'comment') {      } elsif ($self->{state} == COMMENT_STATE) {
2362        if ($self->{next_input_character} == 0x002D) { # -        if ($self->{nc} == 0x002D) { # -
2363          $self->{state} = 'comment end dash';          !!!cp (145);
2364            $self->{state} = COMMENT_END_DASH_STATE;
2365          !!!next-input-character;          !!!next-input-character;
2366          redo A;          redo A;
2367        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
2368            !!!cp (146);
2369          !!!parse-error (type => 'unclosed comment');          !!!parse-error (type => 'unclosed comment');
2370          $self->{state} = 'data';          $self->{state} = DATA_STATE;
2371          ## reconsume          ## reconsume
2372    
2373          !!!emit ($self->{current_token}); # comment          !!!emit ($self->{ct}); # comment
2374    
2375          redo A;          redo A;
2376        } else {        } else {
2377          $self->{current_token}->{data} .= chr ($self->{next_input_character}); # comment          !!!cp (147);
2378            $self->{ct}->{data} .= chr ($self->{nc}); # comment
2379            $self->{read_until}->($self->{ct}->{data},
2380                                  q[-],
2381                                  length $self->{ct}->{data});
2382    
2383          ## Stay in the state          ## Stay in the state
2384          !!!next-input-character;          !!!next-input-character;
2385          redo A;          redo A;
2386        }        }
2387      } elsif ($self->{state} eq 'comment end dash') {      } elsif ($self->{state} == COMMENT_END_DASH_STATE) {
2388        if ($self->{next_input_character} == 0x002D) { # -        if ($self->{nc} == 0x002D) { # -
2389          $self->{state} = 'comment end';          !!!cp (148);
2390            $self->{state} = COMMENT_END_STATE;
2391          !!!next-input-character;          !!!next-input-character;
2392          redo A;          redo A;
2393        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
2394            !!!cp (149);
2395          !!!parse-error (type => 'unclosed comment');          !!!parse-error (type => 'unclosed comment');
2396          $self->{state} = 'data';          $self->{state} = DATA_STATE;
2397          ## reconsume          ## reconsume
2398    
2399          !!!emit ($self->{current_token}); # comment          !!!emit ($self->{ct}); # comment
2400    
2401          redo A;          redo A;
2402        } else {        } else {
2403          $self->{current_token}->{data} .= '-' . chr ($self->{next_input_character}); # comment          !!!cp (150);
2404          $self->{state} = 'comment';          $self->{ct}->{data} .= '-' . chr ($self->{nc}); # comment
2405            $self->{state} = COMMENT_STATE;
2406          !!!next-input-character;          !!!next-input-character;
2407          redo A;          redo A;
2408        }        }
2409      } elsif ($self->{state} eq 'comment end') {      } elsif ($self->{state} == COMMENT_END_STATE) {
2410        if ($self->{next_input_character} == 0x003E) { # >        if ($self->{nc} == 0x003E) { # >
2411          $self->{state} = 'data';          !!!cp (151);
2412            $self->{state} = DATA_STATE;
2413          !!!next-input-character;          !!!next-input-character;
2414    
2415          !!!emit ($self->{current_token}); # comment          !!!emit ($self->{ct}); # comment
2416    
2417          redo A;          redo A;
2418        } elsif ($self->{next_input_character} == 0x002D) { # -        } elsif ($self->{nc} == 0x002D) { # -
2419          !!!parse-error (type => 'dash in comment');          !!!cp (152);
2420          $self->{current_token}->{data} .= '-'; # comment          !!!parse-error (type => 'dash in comment',
2421                            line => $self->{line_prev},
2422                            column => $self->{column_prev});
2423            $self->{ct}->{data} .= '-'; # comment
2424          ## Stay in the state          ## Stay in the state
2425          !!!next-input-character;          !!!next-input-character;
2426          redo A;          redo A;
2427        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
2428            !!!cp (153);
2429          !!!parse-error (type => 'unclosed comment');          !!!parse-error (type => 'unclosed comment');
2430          $self->{state} = 'data';          $self->{state} = DATA_STATE;
2431          ## reconsume          ## reconsume
2432    
2433          !!!emit ($self->{current_token}); # comment          !!!emit ($self->{ct}); # comment
2434    
2435          redo A;          redo A;
2436        } else {        } else {
2437          !!!parse-error (type => 'dash in comment');          !!!cp (154);
2438          $self->{current_token}->{data} .= '--' . chr ($self->{next_input_character}); # comment          !!!parse-error (type => 'dash in comment',
2439          $self->{state} = 'comment';                          line => $self->{line_prev},
2440                            column => $self->{column_prev});
2441            $self->{ct}->{data} .= '--' . chr ($self->{nc}); # comment
2442            $self->{state} = COMMENT_STATE;
2443          !!!next-input-character;          !!!next-input-character;
2444          redo A;          redo A;
2445        }        }
2446      } elsif ($self->{state} eq 'DOCTYPE') {      } elsif ($self->{state} == DOCTYPE_STATE) {
2447        if ($self->{next_input_character} == 0x0009 or # HT        if ($is_space->{$self->{nc}}) {
2448            $self->{next_input_character} == 0x000A or # LF          !!!cp (155);
2449            $self->{next_input_character} == 0x000B or # VT          $self->{state} = BEFORE_DOCTYPE_NAME_STATE;
           $self->{next_input_character} == 0x000C or # FF  
           $self->{next_input_character} == 0x0020) { # SP  
         $self->{state} = 'before DOCTYPE name';  
2450          !!!next-input-character;          !!!next-input-character;
2451          redo A;          redo A;
2452        } else {        } else {
2453            !!!cp (156);
2454          !!!parse-error (type => 'no space before DOCTYPE name');          !!!parse-error (type => 'no space before DOCTYPE name');
2455          $self->{state} = 'before DOCTYPE name';          $self->{state} = BEFORE_DOCTYPE_NAME_STATE;
2456          ## reconsume          ## reconsume
2457          redo A;          redo A;
2458        }        }
2459      } elsif ($self->{state} eq 'before DOCTYPE name') {      } elsif ($self->{state} == BEFORE_DOCTYPE_NAME_STATE) {
2460        if ($self->{next_input_character} == 0x0009 or # HT        if ($is_space->{$self->{nc}}) {
2461            $self->{next_input_character} == 0x000A or # LF          !!!cp (157);
           $self->{next_input_character} == 0x000B or # VT  
           $self->{next_input_character} == 0x000C or # FF  
           $self->{next_input_character} == 0x0020) { # SP  
2462          ## Stay in the state          ## Stay in the state
2463          !!!next-input-character;          !!!next-input-character;
2464          redo A;          redo A;
2465        } elsif ($self->{next_input_character} == 0x003E) { # >        } elsif ($self->{nc} == 0x003E) { # >
2466            !!!cp (158);
2467          !!!parse-error (type => 'no DOCTYPE name');          !!!parse-error (type => 'no DOCTYPE name');
2468          $self->{state} = 'data';          $self->{state} = DATA_STATE;
2469          !!!next-input-character;          !!!next-input-character;
2470    
2471          !!!emit ({type => 'DOCTYPE'}); # incorrect          !!!emit ($self->{ct}); # DOCTYPE (quirks)
2472    
2473          redo A;          redo A;
2474        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
2475            !!!cp (159);
2476          !!!parse-error (type => 'no DOCTYPE name');          !!!parse-error (type => 'no DOCTYPE name');
2477          $self->{state} = 'data';          $self->{state} = DATA_STATE;
2478          ## reconsume          ## reconsume
2479    
2480          !!!emit ({type => 'DOCTYPE'}); # incorrect          !!!emit ($self->{ct}); # DOCTYPE (quirks)
2481    
2482          redo A;          redo A;
2483        } else {        } else {
2484          $self->{current_token}          !!!cp (160);
2485              = {type => 'DOCTYPE',          $self->{ct}->{name} = chr $self->{nc};
2486                 name => chr ($self->{next_input_character}),          delete $self->{ct}->{quirks};
2487                 correct => 1};          $self->{state} = DOCTYPE_NAME_STATE;
 ## ISSUE: "Set the token's name name to the" in the spec  
         $self->{state} = 'DOCTYPE name';  
2488          !!!next-input-character;          !!!next-input-character;
2489          redo A;          redo A;
2490        }        }
2491      } elsif ($self->{state} eq 'DOCTYPE name') {      } elsif ($self->{state} == DOCTYPE_NAME_STATE) {
2492  ## ISSUE: Redundant "First," in the spec.  ## ISSUE: Redundant "First," in the spec.
2493        if ($self->{next_input_character} == 0x0009 or # HT        if ($is_space->{$self->{nc}}) {
2494            $self->{next_input_character} == 0x000A or # LF          !!!cp (161);
2495            $self->{next_input_character} == 0x000B or # VT          $self->{state} = AFTER_DOCTYPE_NAME_STATE;
           $self->{next_input_character} == 0x000C or # FF  
           $self->{next_input_character} == 0x0020) { # SP  
         $self->{state} = 'after DOCTYPE name';  
2496          !!!next-input-character;          !!!next-input-character;
2497          redo A;          redo A;
2498        } elsif ($self->{next_input_character} == 0x003E) { # >        } elsif ($self->{nc} == 0x003E) { # >
2499          $self->{state} = 'data';          !!!cp (162);
2500            $self->{state} = DATA_STATE;
2501          !!!next-input-character;          !!!next-input-character;
2502    
2503          !!!emit ($self->{current_token}); # DOCTYPE          !!!emit ($self->{ct}); # DOCTYPE
2504    
2505          redo A;          redo A;
2506        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
2507            !!!cp (163);
2508          !!!parse-error (type => 'unclosed DOCTYPE');          !!!parse-error (type => 'unclosed DOCTYPE');
2509          $self->{state} = 'data';          $self->{state} = DATA_STATE;
2510          ## reconsume          ## reconsume
2511    
2512          delete $self->{current_token}->{correct};          $self->{ct}->{quirks} = 1;
2513          !!!emit ($self->{current_token}); # DOCTYPE          !!!emit ($self->{ct}); # DOCTYPE
2514    
2515          redo A;          redo A;
2516        } else {        } else {
2517          $self->{current_token}->{name}          !!!cp (164);
2518            .= chr ($self->{next_input_character}); # DOCTYPE          $self->{ct}->{name}
2519              .= chr ($self->{nc}); # DOCTYPE
2520          ## Stay in the state          ## Stay in the state
2521          !!!next-input-character;          !!!next-input-character;
2522          redo A;          redo A;
2523        }        }
2524      } elsif ($self->{state} eq 'after DOCTYPE name') {      } elsif ($self->{state} == AFTER_DOCTYPE_NAME_STATE) {
2525        if ($self->{next_input_character} == 0x0009 or # HT        if ($is_space->{$self->{nc}}) {
2526            $self->{next_input_character} == 0x000A or # LF          !!!cp (165);
           $self->{next_input_character} == 0x000B or # VT  
           $self->{next_input_character} == 0x000C or # FF  
           $self->{next_input_character} == 0x0020) { # SP  
2527          ## Stay in the state          ## Stay in the state
2528          !!!next-input-character;          !!!next-input-character;
2529          redo A;          redo A;
2530        } elsif ($self->{next_input_character} == 0x003E) { # >        } elsif ($self->{nc} == 0x003E) { # >
2531          $self->{state} = 'data';          !!!cp (166);
2532            $self->{state} = DATA_STATE;
2533          !!!next-input-character;          !!!next-input-character;
2534    
2535          !!!emit ($self->{current_token}); # DOCTYPE          !!!emit ($self->{ct}); # DOCTYPE
2536    
2537          redo A;          redo A;
2538        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
2539            !!!cp (167);
2540          !!!parse-error (type => 'unclosed DOCTYPE');          !!!parse-error (type => 'unclosed DOCTYPE');
2541          $self->{state} = 'data';          $self->{state} = DATA_STATE;
2542          ## reconsume          ## reconsume
2543    
2544          delete $self->{current_token}->{correct};          $self->{ct}->{quirks} = 1;
2545          !!!emit ($self->{current_token}); # DOCTYPE          !!!emit ($self->{ct}); # DOCTYPE
2546    
2547          redo A;          redo A;
2548        } elsif ($self->{next_input_character} == 0x0050 or # P        } elsif ($self->{nc} == 0x0050 or # P
2549                 $self->{next_input_character} == 0x0070) { # p                 $self->{nc} == 0x0070) { # p
2550            $self->{state} = PUBLIC_STATE;
2551            $self->{s_kwd} = chr $self->{nc};
2552          !!!next-input-character;          !!!next-input-character;
2553          if ($self->{next_input_character} == 0x0055 or # U          redo A;
2554              $self->{next_input_character} == 0x0075) { # u        } elsif ($self->{nc} == 0x0053 or # S
2555            !!!next-input-character;                 $self->{nc} == 0x0073) { # s
2556            if ($self->{next_input_character} == 0x0042 or # B          $self->{state} = SYSTEM_STATE;
2557                $self->{next_input_character} == 0x0062) { # b          $self->{s_kwd} = chr $self->{nc};
             !!!next-input-character;  
             if ($self->{next_input_character} == 0x004C or # L  
                 $self->{next_input_character} == 0x006C) { # l  
               !!!next-input-character;  
               if ($self->{next_input_character} == 0x0049 or # I  
                   $self->{next_input_character} == 0x0069) { # i  
                 !!!next-input-character;  
                 if ($self->{next_input_character} == 0x0043 or # C  
                     $self->{next_input_character} == 0x0063) { # c  
                   $self->{state} = 'before DOCTYPE public identifier';  
                   !!!next-input-character;  
                   redo A;  
                 }  
               }  
             }  
           }  
         }  
   
         #  
       } elsif ($self->{next_input_character} == 0x0053 or # S  
                $self->{next_input_character} == 0x0073) { # s  
2558          !!!next-input-character;          !!!next-input-character;
2559          if ($self->{next_input_character} == 0x0059 or # Y          redo A;
             $self->{next_input_character} == 0x0079) { # y  
           !!!next-input-character;  
           if ($self->{next_input_character} == 0x0053 or # S  
               $self->{next_input_character} == 0x0073) { # s  
             !!!next-input-character;  
             if ($self->{next_input_character} == 0x0054 or # T  
                 $self->{next_input_character} == 0x0074) { # t  
               !!!next-input-character;  
               if ($self->{next_input_character} == 0x0045 or # E  
                   $self->{next_input_character} == 0x0065) { # e  
                 !!!next-input-character;  
                 if ($self->{next_input_character} == 0x004D or # M  
                     $self->{next_input_character} == 0x006D) { # m  
                   $self->{state} = 'before DOCTYPE system identifier';  
                   !!!next-input-character;  
                   redo A;  
                 }  
               }  
             }  
           }  
         }  
   
         #  
2560        } else {        } else {
2561            !!!cp (180);
2562            !!!parse-error (type => 'string after DOCTYPE name');
2563            $self->{ct}->{quirks} = 1;
2564    
2565            $self->{state} = BOGUS_DOCTYPE_STATE;
2566          !!!next-input-character;          !!!next-input-character;
2567          #          redo A;
2568        }        }
2569        } elsif ($self->{state} == PUBLIC_STATE) {
2570          ## ASCII case-insensitive
2571          if ($self->{nc} == [
2572                undef,
2573                0x0055, # U
2574                0x0042, # B
2575                0x004C, # L
2576                0x0049, # I
2577              ]->[length $self->{s_kwd}] or
2578              $self->{nc} == [
2579                undef,
2580                0x0075, # u
2581                0x0062, # b
2582                0x006C, # l
2583                0x0069, # i
2584              ]->[length $self->{s_kwd}]) {
2585            !!!cp (175);
2586            ## Stay in the state.
2587            $self->{s_kwd} .= chr $self->{nc};
2588            !!!next-input-character;
2589            redo A;
2590          } elsif ((length $self->{s_kwd}) == 5 and
2591                   ($self->{nc} == 0x0043 or # C
2592                    $self->{nc} == 0x0063)) { # c
2593            !!!cp (168);
2594            $self->{state} = BEFORE_DOCTYPE_PUBLIC_IDENTIFIER_STATE;
2595            !!!next-input-character;
2596            redo A;
2597          } else {
2598            !!!cp (169);
2599            !!!parse-error (type => 'string after DOCTYPE name',
2600                            line => $self->{line_prev},
2601                            column => $self->{column_prev} + 1 - length $self->{s_kwd});
2602            $self->{ct}->{quirks} = 1;
2603    
2604        !!!parse-error (type => 'string after DOCTYPE name');          $self->{state} = BOGUS_DOCTYPE_STATE;
2605        $self->{state} = 'bogus DOCTYPE';          ## Reconsume.
2606        # next-input-character is already done          redo A;
2607        redo A;        }
2608      } elsif ($self->{state} eq 'before DOCTYPE public identifier') {      } elsif ($self->{state} == SYSTEM_STATE) {
2609        if ({        ## ASCII case-insensitive
2610              0x0009 => 1, 0x000A => 1, 0x000B => 1, 0x000C => 1, 0x0020 => 1,        if ($self->{nc} == [
2611              #0x000D => 1, # HT, LF, VT, FF, SP, CR              undef,
2612            }->{$self->{next_input_character}}) {              0x0059, # Y
2613                0x0053, # S
2614                0x0054, # T
2615                0x0045, # E
2616              ]->[length $self->{s_kwd}] or
2617              $self->{nc} == [
2618                undef,
2619                0x0079, # y
2620                0x0073, # s
2621                0x0074, # t
2622                0x0065, # e
2623              ]->[length $self->{s_kwd}]) {
2624            !!!cp (170);
2625            ## Stay in the state.
2626            $self->{s_kwd} .= chr $self->{nc};
2627            !!!next-input-character;
2628            redo A;
2629          } elsif ((length $self->{s_kwd}) == 5 and
2630                   ($self->{nc} == 0x004D or # M
2631                    $self->{nc} == 0x006D)) { # m
2632            !!!cp (171);
2633            $self->{state} = BEFORE_DOCTYPE_SYSTEM_IDENTIFIER_STATE;
2634            !!!next-input-character;
2635            redo A;
2636          } else {
2637            !!!cp (172);
2638            !!!parse-error (type => 'string after DOCTYPE name',
2639                            line => $self->{line_prev},
2640                            column => $self->{column_prev} + 1 - length $self->{s_kwd});
2641            $self->{ct}->{quirks} = 1;
2642    
2643            $self->{state} = BOGUS_DOCTYPE_STATE;
2644            ## Reconsume.
2645            redo A;
2646          }
2647        } elsif ($self->{state} == BEFORE_DOCTYPE_PUBLIC_IDENTIFIER_STATE) {
2648          if ($is_space->{$self->{nc}}) {
2649            !!!cp (181);
2650          ## Stay in the state          ## Stay in the state
2651          !!!next-input-character;          !!!next-input-character;
2652          redo A;          redo A;
2653        } elsif ($self->{next_input_character} eq 0x0022) { # "        } elsif ($self->{nc} eq 0x0022) { # "
2654          $self->{current_token}->{public_identifier} = ''; # DOCTYPE          !!!cp (182);
2655          $self->{state} = 'DOCTYPE public identifier (double-quoted)';          $self->{ct}->{pubid} = ''; # DOCTYPE
2656            $self->{state} = DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED_STATE;
2657          !!!next-input-character;          !!!next-input-character;
2658          redo A;          redo A;
2659        } elsif ($self->{next_input_character} eq 0x0027) { # '        } elsif ($self->{nc} eq 0x0027) { # '
2660          $self->{current_token}->{public_identifier} = ''; # DOCTYPE          !!!cp (183);
2661          $self->{state} = 'DOCTYPE public identifier (single-quoted)';          $self->{ct}->{pubid} = ''; # DOCTYPE
2662            $self->{state} = DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED_STATE;
2663          !!!next-input-character;          !!!next-input-character;
2664          redo A;          redo A;
2665        } elsif ($self->{next_input_character} eq 0x003E) { # >        } elsif ($self->{nc} eq 0x003E) { # >
2666            !!!cp (184);
2667          !!!parse-error (type => 'no PUBLIC literal');          !!!parse-error (type => 'no PUBLIC literal');
2668    
2669          $self->{state} = 'data';          $self->{state} = DATA_STATE;
2670          !!!next-input-character;          !!!next-input-character;
2671    
2672          delete $self->{current_token}->{correct};          $self->{ct}->{quirks} = 1;
2673          !!!emit ($self->{current_token}); # DOCTYPE          !!!emit ($self->{ct}); # DOCTYPE
2674    
2675          redo A;          redo A;
2676        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
2677            !!!cp (185);
2678          !!!parse-error (type => 'unclosed DOCTYPE');          !!!parse-error (type => 'unclosed DOCTYPE');
2679    
2680          $self->{state} = 'data';          $self->{state} = DATA_STATE;
2681          ## reconsume          ## reconsume
2682    
2683          delete $self->{current_token}->{correct};          $self->{ct}->{quirks} = 1;
2684          !!!emit ($self->{current_token}); # DOCTYPE          !!!emit ($self->{ct}); # DOCTYPE
2685    
2686          redo A;          redo A;
2687        } else {        } else {
2688            !!!cp (186);
2689          !!!parse-error (type => 'string after PUBLIC');          !!!parse-error (type => 'string after PUBLIC');
2690          $self->{state} = 'bogus DOCTYPE';          $self->{ct}->{quirks} = 1;
2691    
2692            $self->{state} = BOGUS_DOCTYPE_STATE;
2693          !!!next-input-character;          !!!next-input-character;
2694          redo A;          redo A;
2695        }        }
2696      } elsif ($self->{state} eq 'DOCTYPE public identifier (double-quoted)') {      } elsif ($self->{state} == DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED_STATE) {
2697        if ($self->{next_input_character} == 0x0022) { # "        if ($self->{nc} == 0x0022) { # "
2698          $self->{state} = 'after DOCTYPE public identifier';          !!!cp (187);
2699            $self->{state} = AFTER_DOCTYPE_PUBLIC_IDENTIFIER_STATE;
2700          !!!next-input-character;          !!!next-input-character;
2701          redo A;          redo A;
2702        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == 0x003E) { # >
2703            !!!cp (188);
2704          !!!parse-error (type => 'unclosed PUBLIC literal');          !!!parse-error (type => 'unclosed PUBLIC literal');
2705    
2706          $self->{state} = 'data';          $self->{state} = DATA_STATE;
2707            !!!next-input-character;
2708    
2709            $self->{ct}->{quirks} = 1;
2710            !!!emit ($self->{ct}); # DOCTYPE
2711    
2712            redo A;
2713          } elsif ($self->{nc} == -1) {
2714            !!!cp (189);
2715            !!!parse-error (type => 'unclosed PUBLIC literal');
2716    
2717            $self->{state} = DATA_STATE;
2718          ## reconsume          ## reconsume
2719    
2720          delete $self->{current_token}->{correct};          $self->{ct}->{quirks} = 1;
2721          !!!emit ($self->{current_token}); # DOCTYPE          !!!emit ($self->{ct}); # DOCTYPE
2722    
2723          redo A;          redo A;
2724        } else {        } else {
2725          $self->{current_token}->{public_identifier} # DOCTYPE          !!!cp (190);
2726              .= chr $self->{next_input_character};          $self->{ct}->{pubid} # DOCTYPE
2727                .= chr $self->{nc};
2728            $self->{read_until}->($self->{ct}->{pubid}, q[">],
2729                                  length $self->{ct}->{pubid});
2730    
2731          ## Stay in the state          ## Stay in the state
2732          !!!next-input-character;          !!!next-input-character;
2733          redo A;          redo A;
2734        }        }
2735      } elsif ($self->{state} eq 'DOCTYPE public identifier (single-quoted)') {      } elsif ($self->{state} == DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED_STATE) {
2736        if ($self->{next_input_character} == 0x0027) { # '        if ($self->{nc} == 0x0027) { # '
2737          $self->{state} = 'after DOCTYPE public identifier';          !!!cp (191);
2738            $self->{state} = AFTER_DOCTYPE_PUBLIC_IDENTIFIER_STATE;
2739            !!!next-input-character;
2740            redo A;
2741          } elsif ($self->{nc} == 0x003E) { # >
2742            !!!cp (192);
2743            !!!parse-error (type => 'unclosed PUBLIC literal');
2744    
2745            $self->{state} = DATA_STATE;
2746          !!!next-input-character;          !!!next-input-character;
2747    
2748            $self->{ct}->{quirks} = 1;
2749            !!!emit ($self->{ct}); # DOCTYPE
2750    
2751          redo A;          redo A;
2752        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
2753            !!!cp (193);
2754          !!!parse-error (type => 'unclosed PUBLIC literal');          !!!parse-error (type => 'unclosed PUBLIC literal');
2755    
2756          $self->{state} = 'data';          $self->{state} = DATA_STATE;
2757          ## reconsume          ## reconsume
2758    
2759          delete $self->{current_token}->{correct};          $self->{ct}->{quirks} = 1;
2760          !!!emit ($self->{current_token}); # DOCTYPE          !!!emit ($self->{ct}); # DOCTYPE
2761    
2762          redo A;          redo A;
2763        } else {        } else {
2764          $self->{current_token}->{public_identifier} # DOCTYPE          !!!cp (194);
2765              .= chr $self->{next_input_character};          $self->{ct}->{pubid} # DOCTYPE
2766                .= chr $self->{nc};
2767            $self->{read_until}->($self->{ct}->{pubid}, q['>],
2768                                  length $self->{ct}->{pubid});
2769    
2770          ## Stay in the state          ## Stay in the state
2771          !!!next-input-character;          !!!next-input-character;
2772          redo A;          redo A;
2773        }        }
2774      } elsif ($self->{state} eq 'after DOCTYPE public identifier') {      } elsif ($self->{state} == AFTER_DOCTYPE_PUBLIC_IDENTIFIER_STATE) {
2775        if ({        if ($is_space->{$self->{nc}}) {
2776              0x0009 => 1, 0x000A => 1, 0x000B => 1, 0x000C => 1, 0x0020 => 1,          !!!cp (195);
             #0x000D => 1, # HT, LF, VT, FF, SP, CR  
           }->{$self->{next_input_character}}) {  
2777          ## Stay in the state          ## Stay in the state
2778          !!!next-input-character;          !!!next-input-character;
2779          redo A;          redo A;
2780        } elsif ($self->{next_input_character} == 0x0022) { # "        } elsif ($self->{nc} == 0x0022) { # "
2781          $self->{current_token}->{system_identifier} = ''; # DOCTYPE          !!!cp (196);
2782          $self->{state} = 'DOCTYPE system identifier (double-quoted)';          $self->{ct}->{sysid} = ''; # DOCTYPE
2783            $self->{state} = DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED_STATE;
2784          !!!next-input-character;          !!!next-input-character;
2785          redo A;          redo A;
2786        } elsif ($self->{next_input_character} == 0x0027) { # '        } elsif ($self->{nc} == 0x0027) { # '
2787          $self->{current_token}->{system_identifier} = ''; # DOCTYPE          !!!cp (197);
2788          $self->{state} = 'DOCTYPE system identifier (single-quoted)';          $self->{ct}->{sysid} = ''; # DOCTYPE
2789            $self->{state} = DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED_STATE;
2790          !!!next-input-character;          !!!next-input-character;
2791          redo A;          redo A;
2792        } elsif ($self->{next_input_character} == 0x003E) { # >        } elsif ($self->{nc} == 0x003E) { # >
2793          $self->{state} = 'data';          !!!cp (198);
2794            $self->{state} = DATA_STATE;
2795          !!!next-input-character;          !!!next-input-character;
2796    
2797          !!!emit ($self->{current_token}); # DOCTYPE          !!!emit ($self->{ct}); # DOCTYPE
2798    
2799          redo A;          redo A;
2800        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
2801            !!!cp (199);
2802          !!!parse-error (type => 'unclosed DOCTYPE');          !!!parse-error (type => 'unclosed DOCTYPE');
2803    
2804          $self->{state} = 'data';          $self->{state} = DATA_STATE;
2805          ## reconsume          ## reconsume
2806    
2807          delete $self->{current_token}->{correct};          $self->{ct}->{quirks} = 1;
2808          !!!emit ($self->{current_token}); # DOCTYPE          !!!emit ($self->{ct}); # DOCTYPE
2809    
2810          redo A;          redo A;
2811        } else {        } else {
2812            !!!cp (200);
2813          !!!parse-error (type => 'string after PUBLIC literal');          !!!parse-error (type => 'string after PUBLIC literal');
2814          $self->{state} = 'bogus DOCTYPE';          $self->{ct}->{quirks} = 1;
2815    
2816            $self->{state} = BOGUS_DOCTYPE_STATE;
2817          !!!next-input-character;          !!!next-input-character;
2818          redo A;          redo A;
2819        }        }
2820      } elsif ($self->{state} eq 'before DOCTYPE system identifier') {      } elsif ($self->{state} == BEFORE_DOCTYPE_SYSTEM_IDENTIFIER_STATE) {
2821        if ({        if ($is_space->{$self->{nc}}) {
2822              0x0009 => 1, 0x000A => 1, 0x000B => 1, 0x000C => 1, 0x0020 => 1,          !!!cp (201);
             #0x000D => 1, # HT, LF, VT, FF, SP, CR  
           }->{$self->{next_input_character}}) {  
2823          ## Stay in the state          ## Stay in the state
2824          !!!next-input-character;          !!!next-input-character;
2825          redo A;          redo A;
2826        } elsif ($self->{next_input_character} == 0x0022) { # "        } elsif ($self->{nc} == 0x0022) { # "
2827          $self->{current_token}->{system_identifier} = ''; # DOCTYPE          !!!cp (202);
2828          $self->{state} = 'DOCTYPE system identifier (double-quoted)';          $self->{ct}->{sysid} = ''; # DOCTYPE
2829            $self->{state} = DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED_STATE;
2830          !!!next-input-character;          !!!next-input-character;
2831          redo A;          redo A;
2832        } elsif ($self->{next_input_character} == 0x0027) { # '        } elsif ($self->{nc} == 0x0027) { # '
2833          $self->{current_token}->{system_identifier} = ''; # DOCTYPE          !!!cp (203);
2834          $self->{state} = 'DOCTYPE system identifier (single-quoted)';          $self->{ct}->{sysid} = ''; # DOCTYPE
2835            $self->{state} = DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED_STATE;
2836          !!!next-input-character;          !!!next-input-character;
2837          redo A;          redo A;
2838        } elsif ($self->{next_input_character} == 0x003E) { # >        } elsif ($self->{nc} == 0x003E) { # >
2839            !!!cp (204);
2840          !!!parse-error (type => 'no SYSTEM literal');          !!!parse-error (type => 'no SYSTEM literal');
2841          $self->{state} = 'data';          $self->{state} = DATA_STATE;
2842          !!!next-input-character;          !!!next-input-character;
2843    
2844          delete $self->{current_token}->{correct};          $self->{ct}->{quirks} = 1;
2845          !!!emit ($self->{current_token}); # DOCTYPE          !!!emit ($self->{ct}); # DOCTYPE
2846    
2847          redo A;          redo A;
2848        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
2849            !!!cp (205);
2850          !!!parse-error (type => 'unclosed DOCTYPE');          !!!parse-error (type => 'unclosed DOCTYPE');
2851    
2852          $self->{state} = 'data';          $self->{state} = DATA_STATE;
2853          ## reconsume          ## reconsume
2854    
2855          delete $self->{current_token}->{correct};          $self->{ct}->{quirks} = 1;
2856          !!!emit ($self->{current_token}); # DOCTYPE          !!!emit ($self->{ct}); # DOCTYPE
2857    
2858          redo A;          redo A;
2859        } else {        } else {
2860            !!!cp (206);
2861          !!!parse-error (type => 'string after SYSTEM');          !!!parse-error (type => 'string after SYSTEM');
2862          $self->{state} = 'bogus DOCTYPE';          $self->{ct}->{quirks} = 1;
2863    
2864            $self->{state} = BOGUS_DOCTYPE_STATE;
2865          !!!next-input-character;          !!!next-input-character;
2866          redo A;          redo A;
2867        }        }
2868      } elsif ($self->{state} eq 'DOCTYPE system identifier (double-quoted)') {      } elsif ($self->{state} == DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED_STATE) {
2869        if ($self->{next_input_character} == 0x0022) { # "        if ($self->{nc} == 0x0022) { # "
2870          $self->{state} = 'after DOCTYPE system identifier';          !!!cp (207);
2871            $self->{state} = AFTER_DOCTYPE_SYSTEM_IDENTIFIER_STATE;
2872            !!!next-input-character;
2873            redo A;
2874          } elsif ($self->{nc} == 0x003E) { # >
2875            !!!cp (208);
2876            !!!parse-error (type => 'unclosed SYSTEM literal');
2877    
2878            $self->{state} = DATA_STATE;
2879          !!!next-input-character;          !!!next-input-character;
2880    
2881            $self->{ct}->{quirks} = 1;
2882            !!!emit ($self->{ct}); # DOCTYPE
2883    
2884          redo A;          redo A;
2885        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
2886            !!!cp (209);
2887          !!!parse-error (type => 'unclosed SYSTEM literal');          !!!parse-error (type => 'unclosed SYSTEM literal');
2888    
2889          $self->{state} = 'data';          $self->{state} = DATA_STATE;
2890          ## reconsume          ## reconsume
2891    
2892          delete $self->{current_token}->{correct};          $self->{ct}->{quirks} = 1;
2893          !!!emit ($self->{current_token}); # DOCTYPE          !!!emit ($self->{ct}); # DOCTYPE
2894    
2895          redo A;          redo A;
2896        } else {        } else {
2897          $self->{current_token}->{system_identifier} # DOCTYPE          !!!cp (210);
2898              .= chr $self->{next_input_character};          $self->{ct}->{sysid} # DOCTYPE
2899                .= chr $self->{nc};
2900            $self->{read_until}->($self->{ct}->{sysid}, q[">],
2901                                  length $self->{ct}->{sysid});
2902    
2903          ## Stay in the state          ## Stay in the state
2904          !!!next-input-character;          !!!next-input-character;
2905          redo A;          redo A;
2906        }        }
2907      } elsif ($self->{state} eq 'DOCTYPE system identifier (single-quoted)') {      } elsif ($self->{state} == DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED_STATE) {
2908        if ($self->{next_input_character} == 0x0027) { # '        if ($self->{nc} == 0x0027) { # '
2909          $self->{state} = 'after DOCTYPE system identifier';          !!!cp (211);
2910            $self->{state} = AFTER_DOCTYPE_SYSTEM_IDENTIFIER_STATE;
2911          !!!next-input-character;          !!!next-input-character;
2912          redo A;          redo A;
2913        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == 0x003E) { # >
2914            !!!cp (212);
2915          !!!parse-error (type => 'unclosed SYSTEM literal');          !!!parse-error (type => 'unclosed SYSTEM literal');
2916    
2917          $self->{state} = 'data';          $self->{state} = DATA_STATE;
2918            !!!next-input-character;
2919    
2920            $self->{ct}->{quirks} = 1;
2921            !!!emit ($self->{ct}); # DOCTYPE
2922    
2923            redo A;
2924          } elsif ($self->{nc} == -1) {
2925            !!!cp (213);
2926            !!!parse-error (type => 'unclosed SYSTEM literal');
2927    
2928            $self->{state} = DATA_STATE;
2929          ## reconsume          ## reconsume
2930    
2931          delete $self->{current_token}->{correct};          $self->{ct}->{quirks} = 1;
2932          !!!emit ($self->{current_token}); # DOCTYPE          !!!emit ($self->{ct}); # DOCTYPE
2933    
2934          redo A;          redo A;
2935        } else {        } else {
2936          $self->{current_token}->{system_identifier} # DOCTYPE          !!!cp (214);
2937              .= chr $self->{next_input_character};          $self->{ct}->{sysid} # DOCTYPE
2938                .= chr $self->{nc};
2939            $self->{read_until}->($self->{ct}->{sysid}, q['>],
2940                                  length $self->{ct}->{sysid});
2941    
2942          ## Stay in the state          ## Stay in the state
2943          !!!next-input-character;          !!!next-input-character;
2944          redo A;          redo A;
2945        }        }
2946      } elsif ($self->{state} eq 'after DOCTYPE system identifier') {      } elsif ($self->{state} == AFTER_DOCTYPE_SYSTEM_IDENTIFIER_STATE) {
2947        if ({        if ($is_space->{$self->{nc}}) {
2948              0x0009 => 1, 0x000A => 1, 0x000B => 1, 0x000C => 1, 0x0020 => 1,          !!!cp (215);
             #0x000D => 1, # HT, LF, VT, FF, SP, CR  
           }->{$self->{next_input_character}}) {  
2949          ## Stay in the state          ## Stay in the state
2950          !!!next-input-character;          !!!next-input-character;
2951          redo A;          redo A;
2952        } elsif ($self->{next_input_character} == 0x003E) { # >        } elsif ($self->{nc} == 0x003E) { # >
2953          $self->{state} = 'data';          !!!cp (216);
2954            $self->{state} = DATA_STATE;
2955          !!!next-input-character;          !!!next-input-character;
2956    
2957          !!!emit ($self->{current_token}); # DOCTYPE          !!!emit ($self->{ct}); # DOCTYPE
2958    
2959          redo A;          redo A;
2960        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
2961            !!!cp (217);
2962          !!!parse-error (type => 'unclosed DOCTYPE');          !!!parse-error (type => 'unclosed DOCTYPE');
2963            $self->{state} = DATA_STATE;
         $self->{state} = 'data';  
2964          ## reconsume          ## reconsume
2965    
2966          delete $self->{current_token}->{correct};          $self->{ct}->{quirks} = 1;
2967          !!!emit ($self->{current_token}); # DOCTYPE          !!!emit ($self->{ct}); # DOCTYPE
2968    
2969          redo A;          redo A;
2970        } else {        } else {
2971            !!!cp (218);
2972          !!!parse-error (type => 'string after SYSTEM literal');          !!!parse-error (type => 'string after SYSTEM literal');
2973          $self->{state} = 'bogus DOCTYPE';          #$self->{ct}->{quirks} = 1;
2974    
2975            $self->{state} = BOGUS_DOCTYPE_STATE;
2976          !!!next-input-character;          !!!next-input-character;
2977          redo A;          redo A;
2978        }        }
2979      } elsif ($self->{state} eq 'bogus DOCTYPE') {      } elsif ($self->{state} == BOGUS_DOCTYPE_STATE) {
2980        if ($self->{next_input_character} == 0x003E) { # >        if ($self->{nc} == 0x003E) { # >
2981          $self->{state} = 'data';          !!!cp (219);
2982            $self->{state} = DATA_STATE;
2983          !!!next-input-character;          !!!next-input-character;
2984    
2985          delete $self->{current_token}->{correct};          !!!emit ($self->{ct}); # DOCTYPE
         !!!emit ($self->{current_token}); # DOCTYPE  
2986    
2987          redo A;          redo A;
2988        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{nc} == -1) {
2989          !!!parse-error (type => 'unclosed DOCTYPE');          !!!cp (220);
2990          $self->{state} = 'data';          $self->{state} = DATA_STATE;
2991          ## reconsume          ## reconsume
2992    
2993          delete $self->{current_token}->{correct};          !!!emit ($self->{ct}); # DOCTYPE
         !!!emit ($self->{current_token}); # DOCTYPE  
2994    
2995          redo A;          redo A;
2996        } else {        } else {
2997            !!!cp (221);
2998            my $s = '';
2999            $self->{read_until}->($s, q[>], 0);
3000    
3001          ## Stay in the state          ## Stay in the state
3002          !!!next-input-character;          !!!next-input-character;
3003          redo A;          redo A;
3004        }        }
3005      } else {      } elsif ($self->{state} == CDATA_SECTION_STATE) {
3006        die "$0: $self->{state}: Unknown state";        ## NOTE: "CDATA section state" in the state is jointly implemented
3007      }        ## by three states, |CDATA_SECTION_STATE|, |CDATA_SECTION_MSE1_STATE|,
3008    } # A          ## and |CDATA_SECTION_MSE2_STATE|.
3009          
3010    die "$0: _get_next_token: unexpected case";        if ($self->{nc} == 0x005D) { # ]
3011  } # _get_next_token          !!!cp (221.1);
3012            $self->{state} = CDATA_SECTION_MSE1_STATE;
3013            !!!next-input-character;
3014            redo A;
3015          } elsif ($self->{nc} == -1) {
3016            $self->{state} = DATA_STATE;
3017            !!!next-input-character;
3018            if (length $self->{ct}->{data}) { # character
3019              !!!cp (221.2);
3020              !!!emit ($self->{ct}); # character
3021            } else {
3022              !!!cp (221.3);
3023              ## No token to emit. $self->{ct} is discarded.
3024            }        
3025            redo A;
3026          } else {
3027            !!!cp (221.4);
3028            $self->{ct}->{data} .= chr $self->{nc};
3029            $self->{read_until}->($self->{ct}->{data},
3030                                  q<]>,
3031                                  length $self->{ct}->{data});
3032    
3033  sub _tokenize_attempt_to_consume_an_entity ($$) {          ## Stay in the state.
3034    my ($self, $in_attr) = @_;          !!!next-input-character;
3035            redo A;
3036          }
3037    
3038    if ({        ## ISSUE: "text tokens" in spec.
3039         0x0009 => 1, 0x000A => 1, 0x000B => 1, 0x000C => 1, # HT, LF, VT, FF,      } elsif ($self->{state} == CDATA_SECTION_MSE1_STATE) {
3040         0x0020 => 1, 0x003C => 1, 0x0026 => 1, -1 => 1, # SP, <, & # 0x000D # CR        if ($self->{nc} == 0x005D) { # ]
3041        }->{$self->{next_input_character}}) {          !!!cp (221.5);
3042      ## Don't consume          $self->{state} = CDATA_SECTION_MSE2_STATE;
3043      ## No error          !!!next-input-character;
3044      return undef;          redo A;
3045    } elsif ($self->{next_input_character} == 0x0023) { # #        } else {
3046      !!!next-input-character;          !!!cp (221.6);
3047      if ($self->{next_input_character} == 0x0078 or # x          $self->{ct}->{data} .= ']';
3048          $self->{next_input_character} == 0x0058) { # X          $self->{state} = CDATA_SECTION_STATE;
3049        my $code;          ## Reconsume.
3050        X: {          redo A;
3051          my $x_char = $self->{next_input_character};        }
3052          !!!next-input-character;      } elsif ($self->{state} == CDATA_SECTION_MSE2_STATE) {
3053          if (0x0030 <= $self->{next_input_character} and        if ($self->{nc} == 0x003E) { # >
3054              $self->{next_input_character} <= 0x0039) { # 0..9          $self->{state} = DATA_STATE;
3055            $code ||= 0;          !!!next-input-character;
3056            $code *= 0x10;          if (length $self->{ct}->{data}) { # character
3057            $code += $self->{next_input_character} - 0x0030;            !!!cp (221.7);
3058            redo X;            !!!emit ($self->{ct}); # character
         } elsif (0x0061 <= $self->{next_input_character} and  
                  $self->{next_input_character} <= 0x0066) { # a..f  
           $code ||= 0;  
           $code *= 0x10;  
           $code += $self->{next_input_character} - 0x0060 + 9;  
           redo X;  
         } elsif (0x0041 <= $self->{next_input_character} and  
                  $self->{next_input_character} <= 0x0046) { # A..F  
           $code ||= 0;  
           $code *= 0x10;  
           $code += $self->{next_input_character} - 0x0040 + 9;  
           redo X;  
         } elsif (not defined $code) { # no hexadecimal digit  
           !!!parse-error (type => 'bare hcro');  
           !!!back-next-input-character ($x_char, $self->{next_input_character});  
           $self->{next_input_character} = 0x0023; # #  
           return undef;  
         } elsif ($self->{next_input_character} == 0x003B) { # ;  
           !!!next-input-character;  
3059          } else {          } else {
3060            !!!parse-error (type => 'no refc');            !!!cp (221.8);
3061              ## No token to emit. $self->{ct} is discarded.
3062          }          }
3063            redo A;
3064          if ($code == 0 or (0xD800 <= $code and $code <= 0xDFFF)) {        } elsif ($self->{nc} == 0x005D) { # ]
3065            !!!parse-error (type => sprintf 'invalid character reference:U+%04X', $code);          !!!cp (221.9); # character
3066            $code = 0xFFFD;          $self->{ct}->{data} .= ']'; ## Add first "]" of "]]]".
3067          } elsif ($code > 0x10FFFF) {          ## Stay in the state.
           !!!parse-error (type => sprintf 'invalid character reference:U-%08X', $code);  
           $code = 0xFFFD;  
         } elsif ($code == 0x000D) {  
           !!!parse-error (type => 'CR character reference');  
           $code = 0x000A;  
         } elsif (0x80 <= $code and $code <= 0x9F) {  
           !!!parse-error (type => sprintf 'C1 character reference:U+%04X', $code);  
           $code = $c1_entity_char->{$code};  
         }  
   
         return {type => 'character', data => chr $code};  
       } # X  
     } elsif (0x0030 <= $self->{next_input_character} and  
              $self->{next_input_character} <= 0x0039) { # 0..9  
       my $code = $self->{next_input_character} - 0x0030;  
       !!!next-input-character;  
         
       while (0x0030 <= $self->{next_input_character} and  
                 $self->{next_input_character} <= 0x0039) { # 0..9  
         $code *= 10;  
         $code += $self->{next_input_character} - 0x0030;  
           
3068          !!!next-input-character;          !!!next-input-character;
3069            redo A;
3070          } else {
3071            !!!cp (221.11);
3072            $self->{ct}->{data} .= ']]'; # character
3073            $self->{state} = CDATA_SECTION_STATE;
3074            ## Reconsume.
3075            redo A;
3076          }
3077        } elsif ($self->{state} == ENTITY_STATE) {
3078          if ($is_space->{$self->{nc}} or
3079              {
3080                0x003C => 1, 0x0026 => 1, -1 => 1, # <, &
3081                $self->{entity_add} => 1,
3082              }->{$self->{nc}}) {
3083            !!!cp (1001);
3084            ## Don't consume
3085            ## No error
3086            ## Return nothing.
3087            #
3088          } elsif ($self->{nc} == 0x0023) { # #
3089            !!!cp (999);
3090            $self->{state} = ENTITY_HASH_STATE;
3091            $self->{s_kwd} = '#';
3092            !!!next-input-character;
3093            redo A;
3094          } elsif ((0x0041 <= $self->{nc} and
3095                    $self->{nc} <= 0x005A) or # A..Z
3096                   (0x0061 <= $self->{nc} and
3097                    $self->{nc} <= 0x007A)) { # a..z
3098            !!!cp (998);
3099            require Whatpm::_NamedEntityList;
3100            $self->{state} = ENTITY_NAME_STATE;
3101            $self->{s_kwd} = chr $self->{nc};
3102            $self->{entity__value} = $self->{s_kwd};
3103            $self->{entity__match} = 0;
3104            !!!next-input-character;
3105            redo A;
3106          } else {
3107            !!!cp (1027);
3108            !!!parse-error (type => 'bare ero');
3109            ## Return nothing.
3110            #
3111        }        }
3112    
3113        if ($self->{next_input_character} == 0x003B) { # ;        ## NOTE: No character is consumed by the "consume a character
3114          ## reference" algorithm.  In other word, there is an "&" character
3115          ## that does not introduce a character reference, which would be
3116          ## appended to the parent element or the attribute value in later
3117          ## process of the tokenizer.
3118    
3119          if ($self->{prev_state} == DATA_STATE) {
3120            !!!cp (997);
3121            $self->{state} = $self->{prev_state};
3122            ## Reconsume.
3123            !!!emit ({type => CHARACTER_TOKEN, data => '&',
3124                      line => $self->{line_prev},
3125                      column => $self->{column_prev},
3126                     });
3127            redo A;
3128          } else {
3129            !!!cp (996);
3130            $self->{ca}->{value} .= '&';
3131            $self->{state} = $self->{prev_state};
3132            ## Reconsume.
3133            redo A;
3134          }
3135        } elsif ($self->{state} == ENTITY_HASH_STATE) {
3136          if ($self->{nc} == 0x0078 or # x
3137              $self->{nc} == 0x0058) { # X
3138            !!!cp (995);
3139            $self->{state} = HEXREF_X_STATE;
3140            $self->{s_kwd} .= chr $self->{nc};
3141            !!!next-input-character;
3142            redo A;
3143          } elsif (0x0030 <= $self->{nc} and
3144                   $self->{nc} <= 0x0039) { # 0..9
3145            !!!cp (994);
3146            $self->{state} = NCR_NUM_STATE;
3147            $self->{s_kwd} = $self->{nc} - 0x0030;
3148            !!!next-input-character;
3149            redo A;
3150          } else {
3151            !!!parse-error (type => 'bare nero',
3152                            line => $self->{line_prev},
3153                            column => $self->{column_prev} - 1);
3154    
3155            ## NOTE: According to the spec algorithm, nothing is returned,
3156            ## and then "&#" is appended to the parent element or the attribute
3157            ## value in the later processing.
3158    
3159            if ($self->{prev_state} == DATA_STATE) {
3160              !!!cp (1019);
3161              $self->{state} = $self->{prev_state};
3162              ## Reconsume.
3163              !!!emit ({type => CHARACTER_TOKEN,
3164                        data => '&#',
3165                        line => $self->{line_prev},
3166                        column => $self->{column_prev} - 1,
3167                       });
3168              redo A;
3169            } else {
3170              !!!cp (993);
3171              $self->{ca}->{value} .= '&#';
3172              $self->{state} = $self->{prev_state};
3173              ## Reconsume.
3174              redo A;
3175            }
3176          }
3177        } elsif ($self->{state} == NCR_NUM_STATE) {
3178          if (0x0030 <= $self->{nc} and
3179              $self->{nc} <= 0x0039) { # 0..9
3180            !!!cp (1012);
3181            $self->{s_kwd} *= 10;
3182            $self->{s_kwd} += $self->{nc} - 0x0030;
3183            
3184            ## Stay in the state.
3185            !!!next-input-character;
3186            redo A;
3187          } elsif ($self->{nc} == 0x003B) { # ;
3188            !!!cp (1013);
3189          !!!next-input-character;          !!!next-input-character;
3190            #
3191        } else {        } else {
3192            !!!cp (1014);
3193          !!!parse-error (type => 'no refc');          !!!parse-error (type => 'no refc');
3194            ## Reconsume.
3195            #
3196        }        }
3197    
3198        if ($code == 0 or (0xD800 <= $code and $code <= 0xDFFF)) {        my $code = $self->{s_kwd};
3199          !!!parse-error (type => sprintf 'invalid character reference:U+%04X', $code);        my $l = $self->{line_prev};
3200          my $c = $self->{column_prev};
3201          if ($charref_map->{$code}) {
3202            !!!cp (1015);
3203            !!!parse-error (type => 'invalid character reference',
3204                            text => (sprintf 'U+%04X', $code),
3205                            line => $l, column => $c);
3206            $code = $charref_map->{$code};
3207          } elsif ($code > 0x10FFFF) {
3208            !!!cp (1016);
3209            !!!parse-error (type => 'invalid character reference',
3210                            text => (sprintf 'U-%08X', $code),
3211                            line => $l, column => $c);
3212          $code = 0xFFFD;          $code = 0xFFFD;
3213          }
3214    
3215          if ($self->{prev_state} == DATA_STATE) {
3216            !!!cp (992);
3217            $self->{state} = $self->{prev_state};
3218            ## Reconsume.
3219            !!!emit ({type => CHARACTER_TOKEN, data => chr $code,
3220                      line => $l, column => $c,
3221                     });
3222            redo A;
3223          } else {
3224            !!!cp (991);
3225            $self->{ca}->{value} .= chr $code;
3226            $self->{ca}->{has_reference} = 1;
3227            $self->{state} = $self->{prev_state};
3228            ## Reconsume.
3229            redo A;
3230          }
3231        } elsif ($self->{state} == HEXREF_X_STATE) {
3232          if ((0x0030 <= $self->{nc} and $self->{nc} <= 0x0039) or
3233              (0x0041 <= $self->{nc} and $self->{nc} <= 0x0046) or
3234              (0x0061 <= $self->{nc} and $self->{nc} <= 0x0066)) {
3235            # 0..9, A..F, a..f
3236            !!!cp (990);
3237            $self->{state} = HEXREF_HEX_STATE;
3238            $self->{s_kwd} = 0;
3239            ## Reconsume.
3240            redo A;
3241          } else {
3242            !!!parse-error (type => 'bare hcro',
3243                            line => $self->{line_prev},
3244                            column => $self->{column_prev} - 2);
3245    
3246            ## NOTE: According to the spec algorithm, nothing is returned,
3247            ## and then "&#" followed by "X" or "x" is appended to the parent
3248            ## element or the attribute value in the later processing.
3249    
3250            if ($self->{prev_state} == DATA_STATE) {
3251              !!!cp (1005);
3252              $self->{state} = $self->{prev_state};
3253              ## Reconsume.
3254              !!!emit ({type => CHARACTER_TOKEN,
3255                        data => '&' . $self->{s_kwd},
3256                        line => $self->{line_prev},
3257                        column => $self->{column_prev} - length $self->{s_kwd},
3258                       });
3259              redo A;
3260            } else {
3261              !!!cp (989);
3262              $self->{ca}->{value} .= '&' . $self->{s_kwd};
3263              $self->{state} = $self->{prev_state};
3264              ## Reconsume.
3265              redo A;
3266            }
3267          }
3268        } elsif ($self->{state} == HEXREF_HEX_STATE) {
3269          if (0x0030 <= $self->{nc} and $self->{nc} <= 0x0039) {
3270            # 0..9
3271            !!!cp (1002);
3272            $self->{s_kwd} *= 0x10;
3273            $self->{s_kwd} += $self->{nc} - 0x0030;
3274            ## Stay in the state.
3275            !!!next-input-character;
3276            redo A;
3277          } elsif (0x0061 <= $self->{nc} and
3278                   $self->{nc} <= 0x0066) { # a..f
3279            !!!cp (1003);
3280            $self->{s_kwd} *= 0x10;
3281            $self->{s_kwd} += $self->{nc} - 0x0060 + 9;
3282            ## Stay in the state.
3283            !!!next-input-character;
3284            redo A;
3285          } elsif (0x0041 <= $self->{nc} and
3286                   $self->{nc} <= 0x0046) { # A..F
3287            !!!cp (1004);
3288            $self->{s_kwd} *= 0x10;
3289            $self->{s_kwd} += $self->{nc} - 0x0040 + 9;
3290            ## Stay in the state.
3291            !!!next-input-character;
3292            redo A;
3293          } elsif ($self->{nc} == 0x003B) { # ;
3294            !!!cp (1006);
3295            !!!next-input-character;
3296            #
3297          } else {
3298            !!!cp (1007);
3299            !!!parse-error (type => 'no refc',
3300                            line => $self->{line},
3301                            column => $self->{column});
3302            ## Reconsume.
3303            #
3304          }
3305    
3306          my $code = $self->{s_kwd};
3307          my $l = $self->{line_prev};
3308          my $c = $self->{column_prev};
3309          if ($charref_map->{$code}) {
3310            !!!cp (1008);
3311            !!!parse-error (type => 'invalid character reference',
3312                            text => (sprintf 'U+%04X', $code),
3313                            line => $l, column => $c);
3314            $code = $charref_map->{$code};
3315        } elsif ($code > 0x10FFFF) {        } elsif ($code > 0x10FFFF) {
3316          !!!parse-error (type => sprintf 'invalid character reference:U-%08X', $code);          !!!cp (1009);
3317            !!!parse-error (type => 'invalid character reference',
3318                            text => (sprintf 'U-%08X', $code),
3319                            line => $l, column => $c);
3320          $code = 0xFFFD;          $code = 0xFFFD;
       } elsif ($code == 0x000D) {  
         !!!parse-error (type => 'CR character reference');  
         $code = 0x000A;  
       } elsif (0x80 <= $code and $code <= 0x9F) {  
         !!!parse-error (type => sprintf 'C1 character reference:U+%04X', $code);  
         $code = $c1_entity_char->{$code};  
3321        }        }
3322          
3323        return {type => 'character', data => chr $code};        if ($self->{prev_state} == DATA_STATE) {
3324      } else {          !!!cp (988);
3325        !!!parse-error (type => 'bare nero');          $self->{state} = $self->{prev_state};
3326        !!!back-next-input-character ($self->{next_input_character});          ## Reconsume.
3327        $self->{next_input_character} = 0x0023; # #          !!!emit ({type => CHARACTER_TOKEN, data => chr $code,
3328        return undef;                    line => $l, column => $c,
3329      }                   });
3330    } elsif ((0x0041 <= $self->{next_input_character} and          redo A;
3331              $self->{next_input_character} <= 0x005A) or        } else {
3332             (0x0061 <= $self->{next_input_character} and          !!!cp (987);
3333              $self->{next_input_character} <= 0x007A)) {          $self->{ca}->{value} .= chr $code;
3334      my $entity_name = chr $self->{next_input_character};          $self->{ca}->{has_reference} = 1;
3335      !!!next-input-character;          $self->{state} = $self->{prev_state};
3336            ## Reconsume.
3337      my $value = $entity_name;          redo A;
3338      my $match = 0;        }
3339      require Whatpm::_NamedEntityList;      } elsif ($self->{state} == ENTITY_NAME_STATE) {
3340      our $EntityChar;        if (length $self->{s_kwd} < 30 and
3341              ## NOTE: Some number greater than the maximum length of entity name
3342      while (length $entity_name < 10 and            ((0x0041 <= $self->{nc} and # a
3343             ## NOTE: Some number greater than the maximum length of entity name              $self->{nc} <= 0x005A) or # x
3344             ((0x0041 <= $self->{next_input_character} and # a             (0x0061 <= $self->{nc} and # a
3345               $self->{next_input_character} <= 0x005A) or # x              $self->{nc} <= 0x007A) or # z
3346              (0x0061 <= $self->{next_input_character} and # a             (0x0030 <= $self->{nc} and # 0
3347               $self->{next_input_character} <= 0x007A) or # z              $self->{nc} <= 0x0039) or # 9
3348              (0x0030 <= $self->{next_input_character} and # 0             $self->{nc} == 0x003B)) { # ;
3349               $self->{next_input_character} <= 0x0039) or # 9          our $EntityChar;
3350              $self->{next_input_character} == 0x003B)) { # ;          $self->{s_kwd} .= chr $self->{nc};
3351        $entity_name .= chr $self->{next_input_character};          if (defined $EntityChar->{$self->{s_kwd}}) {
3352        if (defined $EntityChar->{$entity_name}) {            if ($self->{nc} == 0x003B) { # ;
3353          if ($self->{next_input_character} == 0x003B) { # ;              !!!cp (1020);
3354            $value = $EntityChar->{$entity_name};              $self->{entity__value} = $EntityChar->{$self->{s_kwd}};
3355            $match = 1;              $self->{entity__match} = 1;
3356            !!!next-input-character;              !!!next-input-character;
3357            last;              #
3358              } else {
3359                !!!cp (1021);
3360                $self->{entity__value} = $EntityChar->{$self->{s_kwd}};
3361                $self->{entity__match} = -1;
3362                ## Stay in the state.
3363                !!!next-input-character;
3364                redo A;
3365              }
3366          } else {          } else {
3367            $value = $EntityChar->{$entity_name};            !!!cp (1022);
3368            $match = -1;            $self->{entity__value} .= chr $self->{nc};
3369              $self->{entity__match} *= 2;
3370              ## Stay in the state.
3371            !!!next-input-character;            !!!next-input-character;
3372              redo A;
3373          }          }
       } else {  
         $value .= chr $self->{next_input_character};  
         $match *= 2;  
         !!!next-input-character;  
3374        }        }
3375      }  
3376              my $data;
3377      if ($match > 0) {        my $has_ref;
3378        return {type => 'character', data => $value};        if ($self->{entity__match} > 0) {
3379      } elsif ($match < 0) {          !!!cp (1023);
3380        !!!parse-error (type => 'no refc');          $data = $self->{entity__value};
3381        if ($in_attr and $match < -1) {          $has_ref = 1;
3382          return {type => 'character', data => '&'.$entity_name};          #
3383          } elsif ($self->{entity__match} < 0) {
3384            !!!parse-error (type => 'no refc');
3385            if ($self->{prev_state} != DATA_STATE and # in attribute
3386                $self->{entity__match} < -1) {
3387              !!!cp (1024);
3388              $data = '&' . $self->{s_kwd};
3389              #
3390            } else {
3391              !!!cp (1025);
3392              $data = $self->{entity__value};
3393              $has_ref = 1;
3394              #
3395            }
3396        } else {        } else {
3397          return {type => 'character', data => $value};          !!!cp (1026);
3398            !!!parse-error (type => 'bare ero',
3399                            line => $self->{line_prev},
3400                            column => $self->{column_prev} - length $self->{s_kwd});
3401            $data = '&' . $self->{s_kwd};
3402            #
3403          }
3404      
3405          ## NOTE: In these cases, when a character reference is found,
3406          ## it is consumed and a character token is returned, or, otherwise,
3407          ## nothing is consumed and returned, according to the spec algorithm.
3408          ## In this implementation, anything that has been examined by the
3409          ## tokenizer is appended to the parent element or the attribute value
3410          ## as string, either literal string when no character reference or
3411          ## entity-replaced string otherwise, in this stage, since any characters
3412          ## that would not be consumed are appended in the data state or in an
3413          ## appropriate attribute value state anyway.
3414    
3415          if ($self->{prev_state} == DATA_STATE) {
3416            !!!cp (986);
3417            $self->{state} = $self->{prev_state};
3418            ## Reconsume.
3419            !!!emit ({type => CHARACTER_TOKEN,
3420                      data => $data,
3421                      line => $self->{line_prev},
3422                      column => $self->{column_prev} + 1 - length $self->{s_kwd},
3423                     });
3424            redo A;
3425          } else {
3426            !!!cp (985);
3427            $self->{ca}->{value} .= $data;
3428            $self->{ca}->{has_reference} = 1 if $has_ref;
3429            $self->{state} = $self->{prev_state};
3430            ## Reconsume.
3431            redo A;
3432        }        }
3433      } else {      } else {
3434        !!!parse-error (type => 'bare ero');        die "$0: $self->{state}: Unknown state";
       ## NOTE: No characters are consumed in the spec.  
       return {type => 'character', data => '&'.$value};  
3435      }      }
3436    } else {    } # A  
3437      ## no characters are consumed  
3438      !!!parse-error (type => 'bare ero');    die "$0: _get_next_token: unexpected case";
3439      return undef;  } # _get_next_token
   }  
 } # _tokenize_attempt_to_consume_an_entity  
3440    
3441  sub _initialize_tree_constructor ($) {  sub _initialize_tree_constructor ($) {
3442    my $self = shift;    my $self = shift;
# Line 1780  sub _initialize_tree_constructor ($) { Line 3445  sub _initialize_tree_constructor ($) {
3445    ## TODO: Turn mutation events off # MUST    ## TODO: Turn mutation events off # MUST
3446    ## TODO: Turn loose Document option (manakai extension) on    ## TODO: Turn loose Document option (manakai extension) on
3447    $self->{document}->manakai_is_html (1); # MUST    $self->{document}->manakai_is_html (1); # MUST
3448      $self->{document}->set_user_data (manakai_source_line => 1);
3449      $self->{document}->set_user_data (manakai_source_column => 1);
3450  } # _initialize_tree_constructor  } # _initialize_tree_constructor
3451    
3452  sub _terminate_tree_constructor ($) {  sub _terminate_tree_constructor ($) {
# Line 1799  sub _construct_tree ($) { Line 3466  sub _construct_tree ($) {
3466    ## When an interactive UA render the $self->{document} available    ## When an interactive UA render the $self->{document} available
3467    ## to the user, or when it begin accepting user input, are    ## to the user, or when it begin accepting user input, are
3468    ## not defined.    ## not defined.
   
   ## Append a character: collect it and all subsequent consecutive  
   ## characters and insert one Text node whose data is concatenation  
   ## of all those characters. # MUST  
3469        
3470    !!!next-token;    !!!next-token;
3471    
   $self->{insertion_mode} = 'before head';  
3472    undef $self->{form_element};    undef $self->{form_element};
3473    undef $self->{head_element};    undef $self->{head_element};
3474      undef $self->{head_element_inserted};
3475    $self->{open_elements} = [];    $self->{open_elements} = [];
3476    undef $self->{inner_html_node};    undef $self->{inner_html_node};
3477    
3478      ## NOTE: The "initial" insertion mode.
3479    $self->_tree_construction_initial; # MUST    $self->_tree_construction_initial; # MUST
3480    
3481      ## NOTE: The "before html" insertion mode.
3482    $self->_tree_construction_root_element;    $self->_tree_construction_root_element;
3483      $self->{insertion_mode} = BEFORE_HEAD_IM;
3484    
3485      ## NOTE: The "before head" insertion mode and so on.
3486    $self->_tree_construction_main;    $self->_tree_construction_main;
3487  } # _construct_tree  } # _construct_tree
3488    
3489  sub _tree_construction_initial ($) {  sub _tree_construction_initial ($) {
3490    my $self = shift;    my $self = shift;
3491    
3492      ## NOTE: "initial" insertion mode
3493    
3494    INITIAL: {    INITIAL: {
3495      if ($token->{type} eq 'DOCTYPE') {      if ($token->{type} == DOCTYPE_TOKEN) {
3496        ## NOTE: Conformance checkers MAY, instead of reporting "not HTML5"        ## NOTE: Conformance checkers MAY, instead of reporting "not HTML5"
3497        ## error, switch to a conformance checking mode for another        ## error, switch to a conformance checking mode for another
3498        ## language.        ## language.
3499        my $doctype_name = $token->{name};        my $doctype_name = $token->{name};
3500        $doctype_name = '' unless defined $doctype_name;        $doctype_name = '' unless defined $doctype_name;
3501        $doctype_name =~ tr/a-z/A-Z/;        $doctype_name =~ tr/a-z/A-Z/; # ASCII case-insensitive
3502        if (not defined $token->{name} or # <!DOCTYPE>        if (not defined $token->{name} or # <!DOCTYPE>
3503            defined $token->{public_identifier} or            defined $token->{sysid}) {
3504            defined $token->{system_identifier}) {          !!!cp ('t1');
3505          !!!parse-error (type => 'not HTML5');          !!!parse-error (type => 'not HTML5', token => $token);
3506        } elsif ($doctype_name ne 'HTML') {        } elsif ($doctype_name ne 'HTML') {
3507          ## ISSUE: ASCII case-insensitive? (in fact it does not matter)          !!!cp ('t2');
3508          !!!parse-error (type => 'not HTML5');          !!!parse-error (type => 'not HTML5', token => $token);
3509          } elsif (defined $token->{pubid}) {
3510            if ($token->{pubid} eq 'XSLT-compat') {
3511              !!!cp ('t1.2');
3512              !!!parse-error (type => 'XSLT-compat', token => $token,
3513                              level => $self->{level}->{should});
3514            } else {
3515              !!!parse-error (type => 'not HTML5', token => $token);
3516            }
3517          } else {
3518            !!!cp ('t3');
3519            #
3520        }        }
3521                
3522        my $doctype = $self->{document}->create_document_type_definition        my $doctype = $self->{document}->create_document_type_definition
3523          ($token->{name}); ## ISSUE: If name is missing (e.g. <!DOCTYPE>)?          ($token->{name}); ## ISSUE: If name is missing (e.g. <!DOCTYPE>)?
3524        $doctype->public_id ($token->{public_identifier})        ## NOTE: Default value for both |public_id| and |system_id| attributes
3525            if defined $token->{public_identifier};        ## are empty strings, so that we don't set any value in missing cases.
3526        $doctype->system_id ($token->{system_identifier})        $doctype->public_id ($token->{pubid}) if defined $token->{pubid};
3527            if defined $token->{system_identifier};        $doctype->system_id ($token->{sysid}) if defined $token->{sysid};
3528        ## NOTE: Other DocumentType attributes are null or empty lists.        ## NOTE: Other DocumentType attributes are null or empty lists.
3529        ## ISSUE: internalSubset = null??        ## ISSUE: internalSubset = null??
3530        $self->{document}->append_child ($doctype);        $self->{document}->append_child ($doctype);
3531                
3532        if (not $token->{correct} or $doctype_name ne 'HTML') {        if ($token->{quirks} or $doctype_name ne 'HTML') {
3533            !!!cp ('t4');
3534          $self->{document}->manakai_compat_mode ('quirks');          $self->{document}->manakai_compat_mode ('quirks');
3535        } elsif (defined $token->{public_identifier}) {        } elsif (defined $token->{pubid}) {
3536          my $pubid = $token->{public_identifier};          my $pubid = $token->{pubid};
3537          $pubid =~ tr/a-z/A-z/;          $pubid =~ tr/a-z/A-z/;
3538          if ({          my $prefix = [
3539            "+//SILMARIL//DTD HTML PRO V0R11 19970101//EN" => 1,            "+//SILMARIL//DTD HTML PRO V0R11 19970101//",
3540            "-//ADVASOFT LTD//DTD HTML 3.0 ASWEDIT + EXTENSIONS//EN" => 1,            "-//ADVASOFT LTD//DTD HTML 3.0 ASWEDIT + EXTENSIONS//",
3541            "-//AS//DTD HTML 3.0 ASWEDIT + EXTENSIONS//EN" => 1,            "-//AS//DTD HTML 3.0 ASWEDIT + EXTENSIONS//",
3542            "-//IETF//DTD HTML 2.0 LEVEL 1//EN" => 1,            "-//IETF//DTD HTML 2.0 LEVEL 1//",
3543            "-//IETF//DTD HTML 2.0 LEVEL 2//EN" => 1,            "-//IETF//DTD HTML 2.0 LEVEL 2//",
3544            "-//IETF//DTD HTML 2.0 STRICT LEVEL 1//EN" => 1,            "-//IETF//DTD HTML 2.0 STRICT LEVEL 1//",
3545            "-//IETF//DTD HTML 2.0 STRICT LEVEL 2//EN" => 1,            "-//IETF//DTD HTML 2.0 STRICT LEVEL 2//",
3546            "-//IETF//DTD HTML 2.0 STRICT//EN" => 1,            "-//IETF//DTD HTML 2.0 STRICT//",
3547            "-//IETF//DTD HTML 2.0//EN" => 1,            "-//IETF//DTD HTML 2.0//",
3548            "-//IETF//DTD HTML 2.1E//EN" => 1,            "-//IETF//DTD HTML 2.1E//",
3549            "-//IETF//DTD HTML 3.0//EN" => 1,            "-//IETF//DTD HTML 3.0//",
3550            "-//IETF//DTD HTML 3.0//EN//" => 1,            "-//IETF//DTD HTML 3.2 FINAL//",
3551            "-//IETF//DTD HTML 3.2 FINAL//EN" => 1,            "-//IETF//DTD HTML 3.2//",
3552            "-//IETF//DTD HTML 3.2//EN" => 1,            "-//IETF//DTD HTML 3//",
3553            "-//IETF//DTD HTML 3//EN" => 1,            "-//IETF//DTD HTML LEVEL 0//",
3554            "-//IETF//DTD HTML LEVEL 0//EN" => 1,            "-//IETF//DTD HTML LEVEL 1//",
3555            "-//IETF//DTD HTML LEVEL 0//EN//2.0" => 1,            "-//IETF//DTD HTML LEVEL 2//",
3556            "-//IETF//DTD HTML LEVEL 1//EN" => 1,            "-//IETF//DTD HTML LEVEL 3//",
3557            "-//IETF//DTD HTML LEVEL 1//EN//2.0" => 1,            "-//IETF//DTD HTML STRICT LEVEL 0//",
3558            "-//IETF//DTD HTML LEVEL 2//EN" => 1,            "-//IETF//DTD HTML STRICT LEVEL 1//",
3559            "-//IETF//DTD HTML LEVEL 2//EN//2.0" => 1,            "-//IETF//DTD HTML STRICT LEVEL 2//",
3560            "-//IETF//DTD HTML LEVEL 3//EN" => 1,            "-//IETF//DTD HTML STRICT LEVEL 3//",
3561            "-//IETF//DTD HTML LEVEL 3//EN//3.0" => 1,            "-//IETF//DTD HTML STRICT//",
3562            "-//IETF//DTD HTML STRICT LEVEL 0//EN" => 1,            "-//IETF//DTD HTML//",
3563            "-//IETF//DTD HTML STRICT LEVEL 0//EN//2.0" => 1,            "-//METRIUS//DTD METRIUS PRESENTATIONAL//",
3564            "-//IETF//DTD HTML STRICT LEVEL 1//EN" => 1,            "-//MICROSOFT//DTD INTERNET EXPLORER 2.0 HTML STRICT//",
3565            "-//IETF//DTD HTML STRICT LEVEL 1//EN//2.0" => 1,            "-//MICROSOFT//DTD INTERNET EXPLORER 2.0 HTML//",
3566            "-//IETF//DTD HTML STRICT LEVEL 2//EN" => 1,            "-//MICROSOFT//DTD INTERNET EXPLORER 2.0 TABLES//",
3567            "-//IETF//DTD HTML STRICT LEVEL 2//EN//2.0" => 1,            "-//MICROSOFT//DTD INTERNET EXPLORER 3.0 HTML STRICT//",
3568            "-//IETF//DTD HTML STRICT LEVEL 3//EN" => 1,            "-//MICROSOFT//DTD INTERNET EXPLORER 3.0 HTML//",
3569            "-//IETF//DTD HTML STRICT LEVEL 3//EN//3.0" => 1,            "-//MICROSOFT//DTD INTERNET EXPLORER 3.0 TABLES//",
3570            "-//IETF//DTD HTML STRICT//EN" => 1,            "-//NETSCAPE COMM. CORP.//DTD HTML//",
3571            "-//IETF//DTD HTML STRICT//EN//2.0" => 1,            "-//NETSCAPE COMM. CORP.//DTD STRICT HTML//",
3572            "-//IETF//DTD HTML STRICT//EN//3.0" => 1,            "-//O'REILLY AND ASSOCIATES//DTD HTML 2.0//",
3573            "-//IETF//DTD HTML//EN" => 1,            "-//O'REILLY AND ASSOCIATES//DTD HTML EXTENDED 1.0//",
3574            "-//IETF//DTD HTML//EN//2.0" => 1,            "-//O'REILLY AND ASSOCIATES//DTD HTML EXTENDED RELAXED 1.0//",
3575            "-//IETF//DTD HTML//EN//3.0" => 1,            "-//SOFTQUAD SOFTWARE//DTD HOTMETAL PRO 6.0::19990601::EXTENSIONS TO HTML 4.0//",
3576            "-//METRIUS//DTD METRIUS PRESENTATIONAL//EN" => 1,            "-//SOFTQUAD//DTD HOTMETAL PRO 4.0::19971010::EXTENSIONS TO HTML 4.0//",
3577            "-//MICROSOFT//DTD INTERNET EXPLORER 2.0 HTML STRICT//EN" => 1,            "-//SPYGLASS//DTD HTML 2.0 EXTENDED//",
3578            "-//MICROSOFT//DTD INTERNET EXPLORER 2.0 HTML//EN" => 1,            "-//SQ//DTD HTML 2.0 HOTMETAL + EXTENSIONS//",
3579            "-//MICROSOFT//DTD INTERNET EXPLORER 2.0 TABLES//EN" => 1,            "-//SUN MICROSYSTEMS CORP.//DTD HOTJAVA HTML//",
3580            "-//MICROSOFT//DTD INTERNET EXPLORER 3.0 HTML STRICT//EN" => 1,            "-//SUN MICROSYSTEMS CORP.//DTD HOTJAVA STRICT HTML//",
3581            "-//MICROSOFT//DTD INTERNET EXPLORER 3.0 HTML//EN" => 1,            "-//W3C//DTD HTML 3 1995-03-24//",
3582            "-//MICROSOFT//DTD INTERNET EXPLORER 3.0 TABLES//EN" => 1,            "-//W3C//DTD HTML 3.2 DRAFT//",
3583            "-//NETSCAPE COMM. CORP.//DTD HTML//EN" => 1,            "-//W3C//DTD HTML 3.2 FINAL//",
3584            "-//NETSCAPE COMM. CORP.//DTD STRICT HTML//EN" => 1,            "-//W3C//DTD HTML 3.2//",
3585            "-//O'REILLY AND ASSOCIATES//DTD HTML 2.0//EN" => 1,            "-//W3C//DTD HTML 3.2S DRAFT//",
3586            "-//O'REILLY AND ASSOCIATES//DTD HTML EXTENDED 1.0//EN" => 1,            "-//W3C//DTD HTML 4.0 FRAMESET//",
3587            "-//SPYGLASS//DTD HTML 2.0 EXTENDED//EN" => 1,            "-//W3C//DTD HTML 4.0 TRANSITIONAL//",
3588            "-//SQ//DTD HTML 2.0 HOTMETAL + EXTENSIONS//EN" => 1,            "-//W3C//DTD HTML EXPERIMETNAL 19960712//",
3589            "-//SUN MICROSYSTEMS CORP.//DTD HOTJAVA HTML//EN" => 1,            "-//W3C//DTD HTML EXPERIMENTAL 970421//",
3590            "-//SUN MICROSYSTEMS CORP.//DTD HOTJAVA STRICT HTML//EN" => 1,            "-//W3C//DTD W3 HTML//",
3591            "-//W3C//DTD HTML 3 1995-03-24//EN" => 1,            "-//W3O//DTD W3 HTML 3.0//",
3592            "-//W3C//DTD HTML 3.2 DRAFT//EN" => 1,            "-//WEBTECHS//DTD MOZILLA HTML 2.0//",
3593            "-//W3C//DTD HTML 3.2 FINAL//EN" => 1,            "-//WEBTECHS//DTD MOZILLA HTML//",
3594            "-//W3C//DTD HTML 3.2//EN" => 1,          ]; # $prefix
3595            "-//W3C//DTD HTML 3.2S DRAFT//EN" => 1,          my $match;
3596            "-//W3C//DTD HTML 4.0 FRAMESET//EN" => 1,          for (@$prefix) {
3597            "-//W3C//DTD HTML 4.0 TRANSITIONAL//EN" => 1,            if (substr ($prefix, 0, length $_) eq $_) {
3598            "-//W3C//DTD HTML EXPERIMETNAL 19960712//EN" => 1,              $match = 1;
3599            "-//W3C//DTD HTML EXPERIMENTAL 970421//EN" => 1,              last;
3600            "-//W3C//DTD W3 HTML//EN" => 1,            }
3601            "-//W3O//DTD W3 HTML 3.0//EN" => 1,          }
3602            "-//W3O//DTD W3 HTML 3.0//EN//" => 1,          if ($match or
3603            "-//W3O//DTD W3 HTML STRICT 3.0//EN//" => 1,              $pubid eq "-//W3O//DTD W3 HTML STRICT 3.0//EN//" or
3604            "-//WEBTECHS//DTD MOZILLA HTML 2.0//EN" => 1,              $pubid eq "-/W3C/DTD HTML 4.0 TRANSITIONAL/EN" or
3605            "-//WEBTECHS//DTD MOZILLA HTML//EN" => 1,              $pubid eq "HTML") {
3606            "-/W3C/DTD HTML 4.0 TRANSITIONAL/EN" => 1,            !!!cp ('t5');
           "HTML" => 1,  
         }->{$pubid}) {  
3607            $self->{document}->manakai_compat_mode ('quirks');            $self->{document}->manakai_compat_mode ('quirks');
3608          } elsif ($pubid eq "-//W3C//DTD HTML 4.01 FRAMESET//EN" or          } elsif ($pubid =~ m[^-//W3C//DTD HTML 4.01 FRAMESET//] or
3609                   $pubid eq "-//W3C//DTD HTML 4.01 TRANSITIONAL//EN") {                   $pubid =~ m[^-//W3C//DTD HTML 4.01 TRANSITIONAL//]) {
3610            if (defined $token->{system_identifier}) {            if (defined $token->{sysid}) {
3611                !!!cp ('t6');
3612              $self->{document}->manakai_compat_mode ('quirks');              $self->{document}->manakai_compat_mode ('quirks');
3613            } else {            } else {
3614                !!!cp ('t7');
3615              $self->{document}->manakai_compat_mode ('limited quirks');              $self->{document}->manakai_compat_mode ('limited quirks');
3616            }            }
3617          } elsif ($pubid eq "-//W3C//DTD XHTML 1.0 Frameset//EN" or          } elsif ($pubid =~ m[^-//W3C//DTD XHTML 1.0 FRAMESET//] or
3618                   $pubid eq "-//W3C//DTD XHTML 1.0 Transitional//EN") {                   $pubid =~ m[^-//W3C//DTD XHTML 1.0 TRANSITIONAL//]) {
3619              !!!cp ('t8');
3620            $self->{document}->manakai_compat_mode ('limited quirks');            $self->{document}->manakai_compat_mode ('limited quirks');
3621            } else {
3622              !!!cp ('t9');
3623          }          }
3624          } else {
3625            !!!cp ('t10');
3626        }        }
3627        if (defined $token->{system_identifier}) {        if (defined $token->{sysid}) {
3628          my $sysid = $token->{system_identifier};          my $sysid = $token->{sysid};
3629          $sysid =~ tr/A-Z/a-z/;          $sysid =~ tr/A-Z/a-z/;
3630          if ($sysid eq "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd") {          if ($sysid eq "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd") {
3631              ## NOTE: Ensure that |PUBLIC "(limited quirks)" "(quirks)"| is
3632              ## marked as quirks.
3633            $self->{document}->manakai_compat_mode ('quirks');            $self->{document}->manakai_compat_mode ('quirks');
3634              !!!cp ('t11');
3635            } else {
3636              !!!cp ('t12');
3637          }          }
3638          } else {
3639            !!!cp ('t13');
3640        }        }
3641                
3642        ## Go to the root element phase.        ## Go to the "before html" insertion mode.
3643        !!!next-token;        !!!next-token;
3644        return;        return;
3645      } elsif ({      } elsif ({
3646                'start tag' => 1,                START_TAG_TOKEN, 1,
3647                'end tag' => 1,                END_TAG_TOKEN, 1,
3648                'end-of-file' => 1,                END_OF_FILE_TOKEN, 1,
3649               }->{$token->{type}}) {               }->{$token->{type}}) {
3650        !!!parse-error (type => 'no DOCTYPE');        !!!cp ('t14');
3651          !!!parse-error (type => 'no DOCTYPE', token => $token);
3652        $self->{document}->manakai_compat_mode ('quirks');        $self->{document}->manakai_compat_mode ('quirks');
3653        ## Go to the root element phase        ## Go to the "before html" insertion mode.
3654        ## reprocess        ## reprocess
3655          !!!ack-later;
3656        return;        return;
3657      } elsif ($token->{type} eq 'character') {      } elsif ($token->{type} == CHARACTER_TOKEN) {
3658        if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) { # \x0D        if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
3659          ## Ignore the token          ## Ignore the token
3660    
3661          unless (length $token->{data}) {          unless (length $token->{data}) {
3662            ## Stay in the phase            !!!cp ('t15');
3663              ## Stay in the insertion mode.
3664            !!!next-token;            !!!next-token;
3665            redo INITIAL;            redo INITIAL;
3666            } else {
3667              !!!cp ('t16');
3668          }          }
3669          } else {
3670            !!!cp ('t17');
3671        }        }
3672    
3673        !!!parse-error (type => 'no DOCTYPE');        !!!parse-error (type => 'no DOCTYPE', token => $token);
3674        $self->{document}->manakai_compat_mode ('quirks');        $self->{document}->manakai_compat_mode ('quirks');
3675        ## Go to the root element phase        ## Go to the "before html" insertion mode.
3676        ## reprocess        ## reprocess
3677        return;        return;
3678      } elsif ($token->{type} eq 'comment') {      } elsif ($token->{type} == COMMENT_TOKEN) {
3679          !!!cp ('t18');
3680        my $comment = $self->{document}->create_comment ($token->{data});        my $comment = $self->{document}->create_comment ($token->{data});
3681        $self->{document}->append_child ($comment);        $self->{document}->append_child ($comment);
3682                
3683        ## Stay in the phase.        ## Stay in the insertion mode.
3684        !!!next-token;        !!!next-token;
3685        redo INITIAL;        redo INITIAL;
3686      } else {      } else {
3687        die "$0: $token->{type}: Unknown token";        die "$0: $token->{type}: Unknown token type";
3688      }      }
3689    } # INITIAL    } # INITIAL
3690    
3691      die "$0: _tree_construction_initial: This should be never reached";
3692  } # _tree_construction_initial  } # _tree_construction_initial
3693    
3694  sub _tree_construction_root_element ($) {  sub _tree_construction_root_element ($) {
3695    my $self = shift;    my $self = shift;
3696    
3697      ## NOTE: "before html" insertion mode.
3698        
3699    B: {    B: {
3700        if ($token->{type} eq 'DOCTYPE') {        if ($token->{type} == DOCTYPE_TOKEN) {
3701          !!!parse-error (type => 'in html:#DOCTYPE');          !!!cp ('t19');
3702            !!!parse-error (type => 'in html:#DOCTYPE', token => $token);
3703          ## Ignore the token          ## Ignore the token
3704          ## Stay in the phase          ## Stay in the insertion mode.
3705          !!!next-token;          !!!next-token;
3706          redo B;          redo B;
3707        } elsif ($token->{type} eq 'comment') {        } elsif ($token->{type} == COMMENT_TOKEN) {
3708            !!!cp ('t20');
3709          my $comment = $self->{document}->create_comment ($token->{data});          my $comment = $self->{document}->create_comment ($token->{data});
3710          $self->{document}->append_child ($comment);          $self->{document}->append_child ($comment);
3711          ## Stay in the phase          ## Stay in the insertion mode.
3712          !!!next-token;          !!!next-token;
3713          redo B;          redo B;
3714        } elsif ($token->{type} eq 'character') {        } elsif ($token->{type} == CHARACTER_TOKEN) {
3715          if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) { # \x0D          if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
3716            ## Ignore the token.            ## Ignore the token.
3717    
3718            unless (length $token->{data}) {            unless (length $token->{data}) {
3719              ## Stay in the phase              !!!cp ('t21');
3720                ## Stay in the insertion mode.
3721              !!!next-token;              !!!next-token;
3722              redo B;              redo B;
3723              } else {
3724                !!!cp ('t22');
3725            }            }
3726            } else {
3727              !!!cp ('t23');
3728          }          }
3729    
3730            $self->{application_cache_selection}->(undef);
3731    
3732          #          #
3733          } elsif ($token->{type} == START_TAG_TOKEN) {
3734            if ($token->{tag_name} eq 'html') {
3735              my $root_element;
3736              !!!create-element ($root_element, $HTML_NS, $token->{tag_name}, $token->{attributes}, $token);
3737              $self->{document}->append_child ($root_element);
3738              push @{$self->{open_elements}},
3739                  [$root_element, $el_category->{html}];
3740    
3741              if ($token->{attributes}->{manifest}) {
3742                !!!cp ('t24');
3743                $self->{application_cache_selection}
3744                    ->($token->{attributes}->{manifest}->{value});
3745                ## ISSUE: Spec is unclear on relative references.
3746                ## According to Hixie (#whatwg 2008-03-19), it should be
3747                ## resolved against the base URI of the document in HTML
3748                ## or xml:base of the element in XHTML.
3749              } else {
3750                !!!cp ('t25');
3751                $self->{application_cache_selection}->(undef);
3752              }
3753    
3754              !!!nack ('t25c');
3755    
3756              !!!next-token;
3757              return; ## Go to the "before head" insertion mode.
3758            } else {
3759              !!!cp ('t25.1');
3760              #
3761            }
3762        } elsif ({        } elsif ({
3763                  'start tag' => 1,                  END_TAG_TOKEN, 1,
3764                  'end tag' => 1,                  END_OF_FILE_TOKEN, 1,
                 'end-of-file' => 1,  
3765                 }->{$token->{type}}) {                 }->{$token->{type}}) {
3766          ## ISSUE: There is an issue in the spec          !!!cp ('t26');
3767          #          #
3768        } else {        } else {
3769          die "$0: $token->{type}: Unknown token";          die "$0: $token->{type}: Unknown token type";
3770        }        }
3771        my $root_element; !!!create-element ($root_element, 'html');  
3772        $self->{document}->append_child ($root_element);      my $root_element;
3773        push @{$self->{open_elements}}, [$root_element, 'html'];      !!!create-element ($root_element, $HTML_NS, 'html',, $token);
3774        ## reprocess      $self->{document}->append_child ($root_element);
3775        #redo B;      push @{$self->{open_elements}}, [$root_element, $el_category->{html}];
3776        return; ## Go to the main phase.  
3777        $self->{application_cache_selection}->(undef);
3778    
3779        ## NOTE: Reprocess the token.
3780        !!!ack-later;
3781        return; ## Go to the "before head" insertion mode.
3782    } # B    } # B
3783    
3784      die "$0: _tree_construction_root_element: This should never be reached";
3785  } # _tree_construction_root_element  } # _tree_construction_root_element
3786    
3787  sub _reset_insertion_mode ($) {  sub _reset_insertion_mode ($) {
# Line 2043  sub _reset_insertion_mode ($) { Line 3796  sub _reset_insertion_mode ($) {
3796            
3797      ## Step 3      ## Step 3
3798      S3: {      S3: {
       ## ISSUE: Oops! "If node is the first node in the stack of open  
       ## elements, then set last to true. If the context element of the  
       ## HTML fragment parsing algorithm is neither a td element nor a  
       ## th element, then set node to the context element. (fragment case)":  
       ## The second "if" is in the scope of the first "if"!?  
3799        if ($self->{open_elements}->[0]->[0] eq $node->[0]) {        if ($self->{open_elements}->[0]->[0] eq $node->[0]) {
3800          $last = 1;          $last = 1;
3801          if (defined $self->{inner_html_node}) {          if (defined $self->{inner_html_node}) {
3802            if ($self->{inner_html_node}->[1] eq 'td' or            !!!cp ('t28');
3803                $self->{inner_html_node}->[1] eq 'th') {            $node = $self->{inner_html_node};
3804              #          } else {
3805            } else {            die "_reset_insertion_mode: t27";
             $node = $self->{inner_html_node};  
           }  
3806          }          }
3807        }        }
3808              
3809        ## Step 4..13        ## Step 4..14
3810        my $new_mode = {        my $new_mode;
3811                        select => 'in select',        if ($node->[1] & FOREIGN_EL) {
3812                        td => 'in cell',          !!!cp ('t28.1');
3813                        th => 'in cell',          ## NOTE: Strictly spaking, the line below only applies to MathML and
3814                        tr => 'in row',          ## SVG elements.  Currently the HTML syntax supports only MathML and
3815                        tbody => 'in table body',          ## SVG elements as foreigners.
3816                        thead => 'in table body',          $new_mode = IN_BODY_IM | IN_FOREIGN_CONTENT_IM;
3817                        tfoot => 'in table body',        } elsif ($node->[1] & TABLE_CELL_EL) {
3818                        caption => 'in caption',          if ($last) {
3819                        colgroup => 'in column group',            !!!cp ('t28.2');
3820                        table => 'in table',            #
3821                        head => 'in body', # not in head!          } else {
3822                        body => 'in body',            !!!cp ('t28.3');
3823                        frameset => 'in frameset',            $new_mode = IN_CELL_IM;
3824                       }->{$node->[1]};          }
3825          } else {
3826            !!!cp ('t28.4');
3827            $new_mode = {
3828                          select => IN_SELECT_IM,
3829                          ## NOTE: |option| and |optgroup| do not set
3830                          ## insertion mode to "in select" by themselves.
3831                          tr => IN_ROW_IM,
3832                          tbody => IN_TABLE_BODY_IM,
3833                          thead => IN_TABLE_BODY_IM,
3834                          tfoot => IN_TABLE_BODY_IM,
3835                          caption => IN_CAPTION_IM,
3836                          colgroup => IN_COLUMN_GROUP_IM,
3837                          table => IN_TABLE_IM,
3838                          head => IN_BODY_IM, # not in head!
3839                          body => IN_BODY_IM,
3840                          frameset => IN_FRAMESET_IM,
3841                         }->{$node->[0]->manakai_local_name};
3842          }
3843        $self->{insertion_mode} = $new_mode and return if defined $new_mode;        $self->{insertion_mode} = $new_mode and return if defined $new_mode;
3844                
3845        ## Step 14        ## Step 15
3846        if ($node->[1] eq 'html') {        if ($node->[1] & HTML_EL) {
3847          unless (defined $self->{head_element}) {          unless (defined $self->{head_element}) {
3848            $self->{insertion_mode} = 'before head';            !!!cp ('t29');
3849              $self->{insertion_mode} = BEFORE_HEAD_IM;
3850          } else {          } else {
3851            $self->{insertion_mode} = 'after head';            ## ISSUE: Can this state be reached?
3852              !!!cp ('t30');
3853              $self->{insertion_mode} = AFTER_HEAD_IM;
3854          }          }
3855          return;          return;
3856          } else {
3857            !!!cp ('t31');
3858        }        }
3859                
       ## Step 15  
       $self->{insertion_mode} = 'in body' and return if $last;  
         
3860        ## Step 16        ## Step 16
3861          $self->{insertion_mode} = IN_BODY_IM and return if $last;
3862          
3863          ## Step 17
3864        $i--;        $i--;
3865        $node = $self->{open_elements}->[$i];        $node = $self->{open_elements}->[$i];
3866                
3867        ## Step 17        ## Step 18
3868        redo S3;        redo S3;
3869      } # S3      } # S3
3870    
3871      die "$0: _reset_insertion_mode: This line should never be reached";
3872  } # _reset_insertion_mode  } # _reset_insertion_mode
3873    
3874  sub _tree_construction_main ($) {  sub _tree_construction_main ($) {
3875    my $self = shift;    my $self = shift;
3876    
   my $previous_insertion_mode;  
   
3877    my $active_formatting_elements = [];    my $active_formatting_elements = [];
3878    
3879    my $reconstruct_active_formatting_elements = sub { # MUST    my $reconstruct_active_formatting_elements = sub { # MUST
# Line 2121  sub _tree_construction_main ($) { Line 3890  sub _tree_construction_main ($) {
3890      return if $entry->[0] eq '#marker';      return if $entry->[0] eq '#marker';
3891      for (@{$self->{open_elements}}) {      for (@{$self->{open_elements}}) {
3892        if ($entry->[0] eq $_->[0]) {        if ($entry->[0] eq $_->[0]) {
3893            !!!cp ('t32');
3894          return;          return;
3895        }        }
3896      }      }
# Line 2135  sub _tree_construction_main ($) { Line 3905  sub _tree_construction_main ($) {
3905    
3906        ## Step 6        ## Step 6
3907        if ($entry->[0] eq '#marker') {        if ($entry->[0] eq '#marker') {
3908            !!!cp ('t33_1');
3909          #          #
3910        } else {        } else {
3911          my $in_open_elements;          my $in_open_elements;
3912          OE: for (@{$self->{open_elements}}) {          OE: for (@{$self->{open_elements}}) {
3913            if ($entry->[0] eq $_->[0]) {            if ($entry->[0] eq $_->[0]) {
3914                !!!cp ('t33');
3915              $in_open_elements = 1;              $in_open_elements = 1;
3916              last OE;              last OE;
3917            }            }
3918          }          }
3919          if ($in_open_elements) {          if ($in_open_elements) {
3920              !!!cp ('t34');
3921            #            #
3922          } else {          } else {
3923              ## NOTE: <!DOCTYPE HTML><p><b><i><u></p> <p>X
3924              !!!cp ('t35');
3925            redo S4;            redo S4;
3926          }          }
3927        }        }
# Line 2169  sub _tree_construction_main ($) { Line 3944  sub _tree_construction_main ($) {
3944    
3945        ## Step 11        ## Step 11
3946        unless ($clone->[0] eq $active_formatting_elements->[-1]->[0]) {        unless ($clone->[0] eq $active_formatting_elements->[-1]->[0]) {
3947            !!!cp ('t36');
3948          ## Step 7'          ## Step 7'
3949          $i++;          $i++;
3950          $entry = $active_formatting_elements->[$i];          $entry = $active_formatting_elements->[$i];
3951                    
3952          redo S7;          redo S7;
3953        }        }
3954    
3955          !!!cp ('t37');
3956      } # S7      } # S7
3957    }; # $reconstruct_active_formatting_elements    }; # $reconstruct_active_formatting_elements
3958    
3959    my $clear_up_to_marker = sub {    my $clear_up_to_marker = sub {
3960      for (reverse 0..$#$active_formatting_elements) {      for (reverse 0..$#$active_formatting_elements) {
3961        if ($active_formatting_elements->[$_]->[0] eq '#marker') {        if ($active_formatting_elements->[$_]->[0] eq '#marker') {
3962            !!!cp ('t38');
3963          splice @$active_formatting_elements, $_;          splice @$active_formatting_elements, $_;
3964          return;          return;
3965        }        }
3966      }      }
3967    
3968        !!!cp ('t39');
3969    }; # $clear_up_to_marker    }; # $clear_up_to_marker
3970    
3971    my $parse_rcdata = sub ($$) {    my $insert;
3972      my ($content_model_flag, $insert) = @_;  
3973      my $parse_rcdata = sub ($) {
3974        my ($content_model_flag) = @_;
3975    
3976      ## Step 1      ## Step 1
3977      my $start_tag_name = $token->{tag_name};      my $start_tag_name = $token->{tag_name};
3978      my $el;      my $el;
3979      !!!create-element ($el, $start_tag_name, $token->{attributes});      !!!create-element ($el, $HTML_NS, $start_tag_name, $token->{attributes}, $token);
3980    
3981      ## Step 2      ## Step 2
3982      $insert->($el); # /context node/->append_child ($el)      $insert->($el);
3983    
3984      ## Step 3      ## Step 3
3985      $self->{content_model} = $content_model_flag; # CDATA or RCDATA      $self->{content_model} = $content_model_flag; # CDATA or RCDATA
# Line 2204  sub _tree_construction_main ($) { Line 3987  sub _tree_construction_main ($) {
3987    
3988      ## Step 4      ## Step 4
3989      my $text = '';      my $text = '';
3990        !!!nack ('t40.1');
3991      !!!next-token;      !!!next-token;
3992      while ($token->{type} eq 'character') { # or until stop tokenizing      while ($token->{type} == CHARACTER_TOKEN) { # or until stop tokenizing
3993          !!!cp ('t40');
3994        $text .= $token->{data};        $text .= $token->{data};
3995        !!!next-token;        !!!next-token;
3996      }      }
3997    
3998      ## Step 5      ## Step 5
3999      if (length $text) {      if (length $text) {
4000          !!!cp ('t41');
4001        my $text = $self->{document}->create_text_node ($text);        my $text = $self->{document}->create_text_node ($text);
4002        $el->append_child ($text);        $el->append_child ($text);
4003      }      }
# Line 2220  sub _tree_construction_main ($) { Line 4006  sub _tree_construction_main ($) {
4006      $self->{content_model} = PCDATA_CONTENT_MODEL;      $self->{content_model} = PCDATA_CONTENT_MODEL;
4007    
4008      ## Step 7      ## Step 7
4009      if ($token->{type} eq 'end tag' and $token->{tag_name} eq $start_tag_name) {      if ($token->{type} == END_TAG_TOKEN and
4010            $token->{tag_name} eq $start_tag_name) {
4011          !!!cp ('t42');
4012        ## Ignore the token        ## Ignore the token
     } elsif ($content_model_flag == CDATA_CONTENT_MODEL) {  
       !!!parse-error (type => 'in CDATA:#'.$token->{type});  
     } elsif ($content_model_flag == RCDATA_CONTENT_MODEL) {  
       !!!parse-error (type => 'in RCDATA:#'.$token->{type});  
4013      } else {      } else {
4014        die "$0: $content_model_flag in parse_rcdata";        ## NOTE: An end-of-file token.
4015          if ($content_model_flag == CDATA_CONTENT_MODEL) {
4016            !!!cp ('t43');
4017            !!!parse-error (type => 'in CDATA:#eof', token => $token);
4018          } elsif ($content_model_flag == RCDATA_CONTENT_MODEL) {
4019            !!!cp ('t44');
4020            !!!parse-error (type => 'in RCDATA:#eof', token => $token);
4021          } else {
4022            die "$0: $content_model_flag in parse_rcdata";
4023          }
4024      }      }
4025      !!!next-token;      !!!next-token;
4026    }; # $parse_rcdata    }; # $parse_rcdata
4027    
4028    my $script_start_tag = sub ($) {    my $script_start_tag = sub () {
     my $insert = $_[0];  
4029      my $script_el;      my $script_el;
4030      !!!create-element ($script_el, 'script', $token->{attributes});      !!!create-element ($script_el, $HTML_NS, 'script', $token->{attributes}, $token);
4031      ## TODO: mark as "parser-inserted"      ## TODO: mark as "parser-inserted"
4032    
4033      $self->{content_model} = CDATA_CONTENT_MODEL;      $self->{content_model} = CDATA_CONTENT_MODEL;
4034      delete $self->{escape}; # MUST      delete $self->{escape}; # MUST
4035            
4036      my $text = '';      my $text = '';
4037        !!!nack ('t45.1');
4038      !!!next-token;      !!!next-token;
4039      while ($token->{type} eq 'character') {      while ($token->{type} == CHARACTER_TOKEN) {
4040          !!!cp ('t45');
4041        $text .= $token->{data};        $text .= $token->{data};
4042        !!!next-token;        !!!next-token;
4043      } # stop if non-character token or tokenizer stops tokenising      } # stop if non-character token or tokenizer stops tokenising
4044      if (length $text) {      if (length $text) {
4045          !!!cp ('t46');
4046        $script_el->manakai_append_text ($text);        $script_el->manakai_append_text ($text);
4047      }      }
4048                                
4049      $self->{content_model} = PCDATA_CONTENT_MODEL;      $self->{content_model} = PCDATA_CONTENT_MODEL;
4050    
4051      if ($token->{type} eq 'end tag' and      if ($token->{type} == END_TAG_TOKEN and
4052          $token->{tag_name} eq 'script') {          $token->{tag_name} eq 'script') {
4053          !!!cp ('t47');
4054        ## Ignore the token        ## Ignore the token
4055      } else {      } else {
4056        !!!parse-error (type => 'in CDATA:#'.$token->{type});        !!!cp ('t48');
4057          !!!parse-error (type => 'in CDATA:#eof', token => $token);
4058        ## ISSUE: And ignore?        ## ISSUE: And ignore?
4059        ## TODO: mark as "already executed"        ## TODO: mark as "already executed"
4060      }      }
4061            
4062      if (defined $self->{inner_html_node}) {      if (defined $self->{inner_html_node}) {
4063          !!!cp ('t49');
4064        ## TODO: mark as "already executed"        ## TODO: mark as "already executed"
4065      } else {      } else {
4066          !!!cp ('t50');
4067        ## TODO: $old_insertion_point = current insertion point        ## TODO: $old_insertion_point = current insertion point
4068        ## TODO: insertion point = just before the next input character        ## TODO: insertion point = just before the next input character
4069    
# Line 2278  sub _tree_construction_main ($) { Line 4077  sub _tree_construction_main ($) {
4077      !!!next-token;      !!!next-token;
4078    }; # $script_start_tag    }; # $script_start_tag
4079    
4080      ## NOTE: $open_tables->[-1]->[0] is the "current table" element node.
4081      ## NOTE: $open_tables->[-1]->[1] is the "tainted" flag.
4082      ## NOTE: $open_tables->[-1]->[2] is set false when non-Text node inserted.
4083      my $open_tables = [[$self->{open_elements}->[0]->[0]]];
4084    
4085    my $formatting_end_tag = sub {    my $formatting_end_tag = sub {
4086      my $tag_name = shift;      my $end_tag_token = shift;
4087        my $tag_name = $end_tag_token->{tag_name};
4088    
4089        ## NOTE: The adoption agency algorithm (AAA).
4090    
4091      FET: {      FET: {
4092        ## Step 1        ## Step 1
4093        my $formatting_element;        my $formatting_element;
4094        my $formatting_element_i_in_active;        my $formatting_element_i_in_active;
4095        AFE: for (reverse 0..$#$active_formatting_elements) {        AFE: for (reverse 0..$#$active_formatting_elements) {
4096          if ($active_formatting_elements->[$_]->[1] eq $tag_name) {          if ($active_formatting_elements->[$_]->[0] eq '#marker') {
4097              !!!cp ('t52');
4098              last AFE;
4099            } elsif ($active_formatting_elements->[$_]->[0]->manakai_local_name
4100                         eq $tag_name) {
4101              !!!cp ('t51');
4102            $formatting_element = $active_formatting_elements->[$_];            $formatting_element = $active_formatting_elements->[$_];
4103            $formatting_element_i_in_active = $_;            $formatting_element_i_in_active = $_;
4104            last AFE;            last AFE;
         } elsif ($active_formatting_elements->[$_]->[0] eq '#marker') {  
           last AFE;  
4105          }          }
4106        } # AFE        } # AFE
4107        unless (defined $formatting_element) {        unless (defined $formatting_element) {
4108          !!!parse-error (type => 'unmatched end tag:'.$tag_name);          !!!cp ('t53');
4109            !!!parse-error (type => 'unmatched end tag', text => $tag_name, token => $end_tag_token);
4110          ## Ignore the token          ## Ignore the token
4111          !!!next-token;          !!!next-token;
4112          return;          return;
# Line 2307  sub _tree_construction_main ($) { Line 4118  sub _tree_construction_main ($) {
4118          my $node = $self->{open_elements}->[$_];          my $node = $self->{open_elements}->[$_];
4119          if ($node->[0] eq $formatting_element->[0]) {          if ($node->[0] eq $formatting_element->[0]) {
4120            if ($in_scope) {            if ($in_scope) {
4121                !!!cp ('t54');
4122              $formatting_element_i_in_open = $_;              $formatting_element_i_in_open = $_;
4123              last INSCOPE;              last INSCOPE;
4124            } else { # in open elements but not in scope            } else { # in open elements but not in scope
4125              !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});              !!!cp ('t55');
4126                !!!parse-error (type => 'unmatched end tag',
4127                                text => $token->{tag_name},
4128                                token => $end_tag_token);
4129              ## Ignore the token              ## Ignore the token
4130              !!!next-token;              !!!next-token;
4131              return;              return;
4132            }            }
4133          } elsif ({          } elsif ($node->[1] & SCOPING_EL) {
4134                    table => 1, caption => 1, td => 1, th => 1,            !!!cp ('t56');
                   button => 1, marquee => 1, object => 1, html => 1,  
                  }->{$node->[1]}) {  
4135            $in_scope = 0;            $in_scope = 0;
4136          }          }
4137        } # INSCOPE        } # INSCOPE
4138        unless (defined $formatting_element_i_in_open) {        unless (defined $formatting_element_i_in_open) {
4139          !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});          !!!cp ('t57');
4140            !!!parse-error (type => 'unmatched end tag',
4141                            text => $token->{tag_name},
4142                            token => $end_tag_token);
4143          pop @$active_formatting_elements; # $formatting_element          pop @$active_formatting_elements; # $formatting_element
4144          !!!next-token; ## TODO: ok?          !!!next-token; ## TODO: ok?
4145          return;          return;
4146        }        }
4147        if (not $self->{open_elements}->[-1]->[0] eq $formatting_element->[0]) {        if (not $self->{open_elements}->[-1]->[0] eq $formatting_element->[0]) {
4148          !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);          !!!cp ('t58');
4149            !!!parse-error (type => 'not closed',
4150                            text => $self->{open_elements}->[-1]->[0]
4151                                ->manakai_local_name,
4152                            token => $end_tag_token);
4153        }        }
4154                
4155        ## Step 2        ## Step 2
# Line 2337  sub _tree_construction_main ($) { Line 4157  sub _tree_construction_main ($) {
4157        my $furthest_block_i_in_open;        my $furthest_block_i_in_open;
4158        OE: for (reverse 0..$#{$self->{open_elements}}) {        OE: for (reverse 0..$#{$self->{open_elements}}) {
4159          my $node = $self->{open_elements}->[$_];          my $node = $self->{open_elements}->[$_];
4160          if (not $formatting_category->{$node->[1]} and          if (not ($node->[1] & FORMATTING_EL) and
4161              #not $phrasing_category->{$node->[1]} and              #not $phrasing_category->{$node->[1]} and
4162              ($special_category->{$node->[1]} or              ($node->[1] & SPECIAL_EL or
4163               $scoping_category->{$node->[1]})) {               $node->[1] & SCOPING_EL)) { ## Scoping is redundant, maybe
4164              !!!cp ('t59');
4165            $furthest_block = $node;            $furthest_block = $node;
4166            $furthest_block_i_in_open = $_;            $furthest_block_i_in_open = $_;
4167              ## NOTE: The topmost (eldest) node.
4168          } elsif ($node->[0] eq $formatting_element->[0]) {          } elsif ($node->[0] eq $formatting_element->[0]) {
4169              !!!cp ('t60');
4170            last OE;            last OE;
4171          }          }
4172        } # OE        } # OE
4173                
4174        ## Step 3        ## Step 3
4175        unless (defined $furthest_block) { # MUST        unless (defined $furthest_block) { # MUST
4176            !!!cp ('t61');
4177          splice @{$self->{open_elements}}, $formatting_element_i_in_open;          splice @{$self->{open_elements}}, $formatting_element_i_in_open;
4178          splice @$active_formatting_elements, $formatting_element_i_in_active, 1;          splice @$active_formatting_elements, $formatting_element_i_in_active, 1;
4179          !!!next-token;          !!!next-token;
# Line 2362  sub _tree_construction_main ($) { Line 4186  sub _tree_construction_main ($) {
4186        ## Step 5        ## Step 5
4187        my $furthest_block_parent = $furthest_block->[0]->parent_node;        my $furthest_block_parent = $furthest_block->[0]->parent_node;
4188        if (defined $furthest_block_parent) {        if (defined $furthest_block_parent) {
4189            !!!cp ('t62');
4190          $furthest_block_parent->remove_child ($furthest_block->[0]);          $furthest_block_parent->remove_child ($furthest_block->[0]);
4191        }        }
4192                
# Line 2384  sub _tree_construction_main ($) { Line 4209  sub _tree_construction_main ($) {
4209          S7S2: {          S7S2: {
4210            for (reverse 0..$#$active_formatting_elements) {            for (reverse 0..$#$active_formatting_elements) {
4211              if ($active_formatting_elements->[$_]->[0] eq $node->[0]) {              if ($active_formatting_elements->[$_]->[0] eq $node->[0]) {
4212                  !!!cp ('t63');
4213                $node_i_in_active = $_;                $node_i_in_active = $_;
4214                last S7S2;                last S7S2;
4215              }              }
# Line 2397  sub _tree_construction_main ($) { Line 4223  sub _tree_construction_main ($) {
4223                    
4224          ## Step 4          ## Step 4
4225          if ($last_node->[0] eq $furthest_block->[0]) {          if ($last_node->[0] eq $furthest_block->[0]) {
4226              !!!cp ('t64');
4227            $bookmark_prev_el = $node->[0];            $bookmark_prev_el = $node->[0];
4228          }          }
4229                    
4230          ## Step 5          ## Step 5
4231          if ($node->[0]->has_child_nodes ()) {          if ($node->[0]->has_child_nodes ()) {
4232              !!!cp ('t65');
4233            my $clone = [$node->[0]->clone_node (0), $node->[1]];            my $clone = [$node->[0]->clone_node (0), $node->[1]];
4234            $active_formatting_elements->[$node_i_in_active] = $clone;            $active_formatting_elements->[$node_i_in_active] = $clone;
4235            $self->{open_elements}->[$node_i_in_open] = $clone;            $self->{open_elements}->[$node_i_in_open] = $clone;
# Line 2419  sub _tree_construction_main ($) { Line 4247  sub _tree_construction_main ($) {
4247        } # S7          } # S7  
4248                
4249        ## Step 8        ## Step 8
4250        $common_ancestor_node->[0]->append_child ($last_node->[0]);        if ($common_ancestor_node->[1] & TABLE_ROWS_EL) {
4251            my $foster_parent_element;
4252            my $next_sibling;
4253            OE: for (reverse 0..$#{$self->{open_elements}}) {
4254              if ($self->{open_elements}->[$_]->[1] & TABLE_EL) {
4255                                 my $parent = $self->{open_elements}->[$_]->[0]->parent_node;
4256                                 if (defined $parent and $parent->node_type == 1) {
4257                                   !!!cp ('t65.1');
4258                                   $foster_parent_element = $parent;
4259                                   $next_sibling = $self->{open_elements}->[$_]->[0];
4260                                 } else {
4261                                   !!!cp ('t65.2');
4262                                   $foster_parent_element
4263                                     = $self->{open_elements}->[$_ - 1]->[0];
4264                                 }
4265                                 last OE;
4266                               }
4267                             } # OE
4268                             $foster_parent_element = $self->{open_elements}->[0]->[0]
4269                               unless defined $foster_parent_element;
4270            $foster_parent_element->insert_before ($last_node->[0], $next_sibling);
4271            $open_tables->[-1]->[1] = 1; # tainted
4272          } else {
4273            !!!cp ('t65.3');
4274            $common_ancestor_node->[0]->append_child ($last_node->[0]);
4275          }
4276                
4277        ## Step 9        ## Step 9
4278        my $clone = [$formatting_element->[0]->clone_node (0),        my $clone = [$formatting_element->[0]->clone_node (0),
# Line 2436  sub _tree_construction_main ($) { Line 4289  sub _tree_construction_main ($) {
4289        my $i;        my $i;
4290        AFE: for (reverse 0..$#$active_formatting_elements) {        AFE: for (reverse 0..$#$active_formatting_elements) {
4291          if ($active_formatting_elements->[$_]->[0] eq $formatting_element->[0]) {          if ($active_formatting_elements->[$_]->[0] eq $formatting_element->[0]) {
4292              !!!cp ('t66');
4293            splice @$active_formatting_elements, $_, 1;            splice @$active_formatting_elements, $_, 1;
4294            $i-- and last AFE if defined $i;            $i-- and last AFE if defined $i;
4295          } elsif ($active_formatting_elements->[$_]->[0] eq $bookmark_prev_el) {          } elsif ($active_formatting_elements->[$_]->[0] eq $bookmark_prev_el) {
4296              !!!cp ('t67');
4297            $i = $_;            $i = $_;
4298          }          }
4299        } # AFE        } # AFE
# Line 2448  sub _tree_construction_main ($) { Line 4303  sub _tree_construction_main ($) {
4303        undef $i;        undef $i;
4304        OE: for (reverse 0..$#{$self->{open_elements}}) {        OE: for (reverse 0..$#{$self->{open_elements}}) {
4305          if ($self->{open_elements}->[$_]->[0] eq $formatting_element->[0]) {          if ($self->{open_elements}->[$_]->[0] eq $formatting_element->[0]) {
4306              !!!cp ('t68');
4307            splice @{$self->{open_elements}}, $_, 1;            splice @{$self->{open_elements}}, $_, 1;
4308            $i-- and last OE if defined $i;            $i-- and last OE if defined $i;
4309          } elsif ($self->{open_elements}->[$_]->[0] eq $furthest_block->[0]) {          } elsif ($self->{open_elements}->[$_]->[0] eq $furthest_block->[0]) {
4310              !!!cp ('t69');
4311            $i = $_;            $i = $_;
4312          }          }
4313        } # OE        } # OE
4314        splice @{$self->{open_elements}}, $i + 1, 1, $clone;        splice @{$self->{open_elements}}, $i + 1, 0, $clone;
4315                
4316        ## Step 14        ## Step 14
4317        redo FET;        redo FET;
4318      } # FET      } # FET
4319    }; # $formatting_end_tag    }; # $formatting_end_tag
4320    
4321    my $insert_to_current = sub {    $insert = my $insert_to_current = sub {
4322      $self->{open_elements}->[-1]->[0]->append_child ($_[0]);      $self->{open_elements}->[-1]->[0]->append_child ($_[0]);
4323    }; # $insert_to_current    }; # $insert_to_current
4324    
4325    my $insert_to_foster = sub {    my $insert_to_foster = sub {
4326                         my $child = shift;      my $child = shift;
4327                         if ({      if ($self->{open_elements}->[-1]->[1] & TABLE_ROWS_EL) {
4328                              table => 1, tbody => 1, tfoot => 1,        # MUST
4329                              thead => 1, tr => 1,        my $foster_parent_element;
4330                             }->{$self->{open_elements}->[-1]->[1]}) {        my $next_sibling;
4331                           # MUST        OE: for (reverse 0..$#{$self->{open_elements}}) {
4332                           my $foster_parent_element;          if ($self->{open_elements}->[$_]->[1] & TABLE_EL) {
                          my $next_sibling;  
                          OE: for (reverse 0..$#{$self->{open_elements}}) {  
                            if ($self->{open_elements}->[$_]->[1] eq 'table') {  
4333                               my $parent = $self->{open_elements}->[$_]->[0]->parent_node;                               my $parent = $self->{open_elements}->[$_]->[0]->parent_node;
4334                               if (defined $parent and $parent->node_type == 1) {                               if (defined $parent and $parent->node_type == 1) {
4335                                   !!!cp ('t70');
4336                                 $foster_parent_element = $parent;                                 $foster_parent_element = $parent;
4337                                 $next_sibling = $self->{open_elements}->[$_]->[0];                                 $next_sibling = $self->{open_elements}->[$_]->[0];
4338                               } else {                               } else {
4339                                   !!!cp ('t71');
4340                                 $foster_parent_element                                 $foster_parent_element
4341                                   = $self->{open_elements}->[$_ - 1]->[0];                                   = $self->{open_elements}->[$_ - 1]->[0];
4342                               }                               }
# Line 2491  sub _tree_construction_main ($) { Line 4347  sub _tree_construction_main ($) {
4347                             unless defined $foster_parent_element;                             unless defined $foster_parent_element;
4348                           $foster_parent_element->insert_before                           $foster_parent_element->insert_before
4349                             ($child, $next_sibling);                             ($child, $next_sibling);
4350                         } else {        $open_tables->[-1]->[1] = 1; # tainted
4351                           $self->{open_elements}->[-1]->[0]->append_child ($child);      } else {
4352                         }        !!!cp ('t72');
4353          $self->{open_elements}->[-1]->[0]->append_child ($child);
4354        }
4355    }; # $insert_to_foster    }; # $insert_to_foster
4356    
4357    my $in_body = sub {    ## NOTE: Insert a character (MUST): When a character is inserted, if
4358      my $insert = shift;    ## the last node that was inserted by the parser is a Text node and
4359      if ($token->{type} eq 'start tag') {    ## the character has to be inserted after that node, then the
4360        if ($token->{tag_name} eq 'script') {    ## character is appended to the Text node.  However, if any other
4361          ## NOTE: This is an "as if in head" code clone    ## node is inserted by the parser, then a new Text node is created
4362          $script_start_tag->($insert);    ## and the character is appended as that Text node.  If I'm not
4363          return;    ## wrong, for a parser with scripting disabled, there are only two
4364        } elsif ($token->{tag_name} eq 'style') {    ## cases where this occurs.  One is the case where an element node
4365          ## NOTE: This is an "as if in head" code clone    ## is inserted to the |head| element.  This is covered by using the
4366          $parse_rcdata->(CDATA_CONTENT_MODEL, $insert);    ## |$self->{head_element_inserted}| flag.  Another is the case where
4367          return;    ## an element or comment is inserted into the |table| subtree while
4368        } elsif ({    ## foster parenting happens.  This is covered by using the [2] flag
4369                  base => 1, link => 1,    ## of the |$open_tables| structure.  All other cases are handled
4370                 }->{$token->{tag_name}}) {    ## simply by calling |manakai_append_text| method.
4371          ## NOTE: This is an "as if in head" code clone, only "-t" differs  
4372          !!!insert-element-t ($token->{tag_name}, $token->{attributes});    ## TODO: |<body><script>document.write("a<br>");
4373          pop @{$self->{open_elements}}; ## ISSUE: This step is missing in the spec.    ## document.body.removeChild (document.body.lastChild);
4374          !!!next-token;    ## document.write ("b")</script>|
4375          return;  
4376        } elsif ($token->{tag_name} eq 'meta') {    B: while (1) {
4377          ## NOTE: This is an "as if in head" code clone, only "-t" differs      if ($token->{type} == DOCTYPE_TOKEN) {
4378          !!!insert-element-t ($token->{tag_name}, $token->{attributes});        !!!cp ('t73');
4379          pop @{$self->{open_elements}}; ## ISSUE: This step is missing in the spec.        !!!parse-error (type => 'in html:#DOCTYPE', token => $token);
4380          ## Ignore the token
4381          unless ($self->{confident}) {        ## Stay in the phase
4382            my $charset;        !!!next-token;
4383            if ($token->{attributes}->{charset}) { ## TODO: And if supported        next B;
4384              $charset = $token->{attributes}->{charset}->{value};      } elsif ($token->{type} == START_TAG_TOKEN and
4385            }               $token->{tag_name} eq 'html') {
4386            if ($token->{attributes}->{'http-equiv'}) {        if ($self->{insertion_mode} == AFTER_HTML_BODY_IM) {
4387              ## ISSUE: Algorithm name in the spec was incorrect so that not linked to the definition.          !!!cp ('t79');
4388              if ($token->{attributes}->{'http-equiv'}->{value}          !!!parse-error (type => 'after html', text => 'html', token => $token);
4389                  =~ /\A[^;]*;[\x09-\x0D\x20]*charset[\x09-\x0D\x20]*=          $self->{insertion_mode} = AFTER_BODY_IM;
4390                      [\x09-\x0D\x20]*(?>"([^"]*)"|'([^']*)'|        } elsif ($self->{insertion_mode} == AFTER_HTML_FRAMESET_IM) {
4391                      ([^"'\x09-\x0D\x20][^\x09-\x0D\x20]*))/x) {          !!!cp ('t80');
4392                $charset = defined $1 ? $1 : defined $2 ? $2 : $3;          !!!parse-error (type => 'after html', text => 'html', token => $token);
4393              } ## TODO: And if supported          $self->{insertion_mode} = AFTER_FRAMESET_IM;
4394            }        } else {
4395            ## TODO: Change the encoding          !!!cp ('t81');
4396          }        }
4397    
4398          !!!next-token;        !!!cp ('t82');
4399          return;        !!!parse-error (type => 'not first start tag', token => $token);
4400        } elsif ($token->{tag_name} eq 'title') {        my $top_el = $self->{open_elements}->[0]->[0];
4401          !!!parse-error (type => 'in body:title');        for my $attr_name (keys %{$token->{attributes}}) {
4402          ## NOTE: This is an "as if in head" code clone          unless ($top_el->has_attribute_ns (undef, $attr_name)) {
4403          $parse_rcdata->(RCDATA_CONTENT_MODEL, sub {            !!!cp ('t84');
4404            if (defined $self->{head_element}) {            $top_el->set_attribute_ns
4405              $self->{head_element}->append_child ($_[0]);              (undef, [undef, $attr_name],
4406            } else {               $token->{attributes}->{$attr_name}->{value});
             $insert->($_[0]);  
           }  
         });  
         return;  
       } elsif ($token->{tag_name} eq 'body') {  
         !!!parse-error (type => 'in body:body');  
                 
         if (@{$self->{open_elements}} == 1 or  
             $self->{open_elements}->[1]->[1] ne 'body') {  
           ## Ignore the token  
         } else {  
           my $body_el = $self->{open_elements}->[1]->[0];  
           for my $attr_name (keys %{$token->{attributes}}) {  
             unless ($body_el->has_attribute_ns (undef, $attr_name)) {  
               $body_el->set_attribute_ns  
                 (undef, [undef, $attr_name],  
                  $token->{attributes}->{$attr_name}->{value});  
             }  
           }  
4407          }          }
4408          }
4409          !!!nack ('t84.1');
4410          !!!next-token;
4411          next B;
4412        } elsif ($token->{type} == COMMENT_TOKEN) {
4413          my $comment = $self->{document}->create_comment ($token->{data});
4414          if ($self->{insertion_mode} & AFTER_HTML_IMS) {
4415            !!!cp ('t85');
4416            $self->{document}->append_child ($comment);
4417          } elsif ($self->{insertion_mode} == AFTER_BODY_IM) {
4418            !!!cp ('t86');
4419            $self->{open_elements}->[0]->[0]->append_child ($comment);
4420          } else {
4421            !!!cp ('t87');
4422            $self->{open_elements}->[-1]->[0]->append_child ($comment);
4423            $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
4424          }
4425          !!!next-token;
4426          next B;
4427        } elsif ($self->{insertion_mode} & IN_FOREIGN_CONTENT_IM) {
4428          if ($token->{type} == CHARACTER_TOKEN) {
4429            !!!cp ('t87.1');
4430            $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
4431          !!!next-token;          !!!next-token;
4432          return;          next B;
4433        } elsif ({        } elsif ($token->{type} == START_TAG_TOKEN) {
4434                  address => 1, blockquote => 1, center => 1, dir => 1,          if ((not {mglyph => 1, malignmark => 1}->{$token->{tag_name}} and
4435                  div => 1, dl => 1, fieldset => 1, listing => 1,               $self->{open_elements}->[-1]->[1] & FOREIGN_FLOW_CONTENT_EL) or
4436                  menu => 1, ol => 1, p => 1, ul => 1,              not ($self->{open_elements}->[-1]->[1] & FOREIGN_EL) or
4437                  pre => 1,              ($token->{tag_name} eq 'svg' and
4438                 }->{$token->{tag_name}}) {               $self->{open_elements}->[-1]->[1] & MML_AXML_EL)) {
4439          ## has a p element in scope            ## NOTE: "using the rules for secondary insertion mode"then"continue"
4440          INSCOPE: for (reverse @{$self->{open_elements}}) {            !!!cp ('t87.2');
4441            if ($_->[1] eq 'p') {            #
4442              !!!back-token;          } elsif ({
4443              $token = {type => 'end tag', tag_name => 'p'};                    b => 1, big => 1, blockquote => 1, body => 1, br => 1,
4444              return;                    center => 1, code => 1, dd => 1, div => 1, dl => 1, dt => 1,
4445            } elsif ({                    em => 1, embed => 1, font => 1, h1 => 1, h2 => 1, h3 => 1,
4446                      table => 1, caption => 1, td => 1, th => 1,                    h4 => 1, h5 => 1, h6 => 1, head => 1, hr => 1, i => 1,
4447                      button => 1, marquee => 1, object => 1, html => 1,                    img => 1, li => 1, listing => 1, menu => 1, meta => 1,
4448                     }->{$_->[1]}) {                    nobr => 1, ol => 1, p => 1, pre => 1, ruby => 1, s => 1,
4449              last INSCOPE;                    small => 1, span => 1, strong => 1, strike => 1, sub => 1,
4450                      sup => 1, table => 1, tt => 1, u => 1, ul => 1, var => 1,
4451                     }->{$token->{tag_name}}) {
4452              !!!cp ('t87.2');
4453              !!!parse-error (type => 'not closed',
4454                              text => $self->{open_elements}->[-1]->[0]
4455                                  ->manakai_local_name,
4456                              token => $token);
4457    
4458              pop @{$self->{open_elements}}
4459                  while $self->{open_elements}->[-1]->[1] & FOREIGN_EL;
4460    
4461              $self->{insertion_mode} &= ~ IN_FOREIGN_CONTENT_IM;
4462              ## Reprocess.
4463              next B;
4464            } else {
4465              my $nsuri = $self->{open_elements}->[-1]->[0]->namespace_uri;
4466              my $tag_name = $token->{tag_name};
4467              if ($nsuri eq $SVG_NS) {
4468                $tag_name = {
4469                   altglyph => 'altGlyph',
4470                   altglyphdef => 'altGlyphDef',
4471                   altglyphitem => 'altGlyphItem',
4472                   animatecolor => 'animateColor',
4473                   animatemotion => 'animateMotion',
4474                   animatetransform => 'animateTransform',
4475                   clippath => 'clipPath',
4476                   feblend => 'feBlend',
4477                   fecolormatrix => 'feColorMatrix',
4478                   fecomponenttransfer => 'feComponentTransfer',
4479                   fecomposite => 'feComposite',
4480                   feconvolvematrix => 'feConvolveMatrix',
4481                   fediffuselighting => 'feDiffuseLighting',
4482                   fedisplacementmap => 'feDisplacementMap',
4483                   fedistantlight => 'feDistantLight',
4484                   feflood => 'feFlood',
4485                   fefunca => 'feFuncA',
4486                   fefuncb => 'feFuncB',
4487                   fefuncg => 'feFuncG',
4488                   fefuncr => 'feFuncR',
4489                   fegaussianblur => 'feGaussianBlur',
4490                   feimage => 'feImage',
4491                   femerge => 'feMerge',
4492                   femergenode => 'feMergeNode',
4493                   femorphology => 'feMorphology',
4494                   feoffset => 'feOffset',
4495                   fepointlight => 'fePointLight',
4496                   fespecularlighting => 'feSpecularLighting',
4497                   fespotlight => 'feSpotLight',
4498                   fetile => 'feTile',
4499                   feturbulence => 'feTurbulence',
4500                   foreignobject => 'foreignObject',
4501                   glyphref => 'glyphRef',
4502                   lineargradient => 'linearGradient',
4503                   radialgradient => 'radialGradient',
4504                   #solidcolor => 'solidColor', ## NOTE: Commented in spec (SVG1.2)
4505                   textpath => 'textPath',  
4506                }->{$tag_name} || $tag_name;
4507            }            }
4508          } # INSCOPE  
4509                        ## "adjust SVG attributes" (SVG only) - done in insert-element-f
4510          !!!insert-element-t ($token->{tag_name}, $token->{attributes});  
4511          if ($token->{tag_name} eq 'pre') {            ## "adjust foreign attributes" - done in insert-element-f
4512            !!!next-token;  
4513            if ($token->{type} eq 'character') {            !!!insert-element-f ($nsuri, $tag_name, $token->{attributes}, $token);
4514              $token->{data} =~ s/^\x0A//;  
4515              unless (length $token->{data}) {            if ($self->{self_closing}) {
4516                !!!next-token;              pop @{$self->{open_elements}};
4517              }              !!!ack ('t87.3');
4518              } else {
4519                !!!cp ('t87.4');
4520            }            }
4521          } else {  
           !!!next-token;  
         }  
         return;  
       } elsif ($token->{tag_name} eq 'form') {  
         if (defined $self->{form_element}) {  
           !!!parse-error (type => 'in form:form');  
           ## Ignore the token  
           !!!next-token;  
           return;  
         } else {  
           ## has a p element in scope  
           INSCOPE: for (reverse @{$self->{open_elements}}) {  
             if ($_->[1] eq 'p') {  
               !!!back-token;  
               $token = {type => 'end tag', tag_name => 'p'};  
               return;  
             } elsif ({  
                       table => 1, caption => 1, td => 1, th => 1,  
                       button => 1, marquee => 1, object => 1, html => 1,  
                      }->{$_->[1]}) {  
               last INSCOPE;  
             }  
           } # INSCOPE  
               
           !!!insert-element-t ($token->{tag_name}, $token->{attributes});  
           $self->{form_element} = $self->{open_elements}->[-1]->[0];  
4522            !!!next-token;            !!!next-token;
4523            return;            next B;
4524          }          }
4525        } elsif ($token->{tag_name} eq 'li') {        } elsif ($token->{type} == END_TAG_TOKEN) {
4526          ## has a p element in scope          ## NOTE: "using the rules for secondary insertion mode" then "continue"
4527          INSCOPE: for (reverse @{$self->{open_elements}}) {          !!!cp ('t87.5');
4528            if ($_->[1] eq 'p') {          #
4529              !!!back-token;        } elsif ($token->{type} == END_OF_FILE_TOKEN) {
4530              $token = {type => 'end tag', tag_name => 'p'};          !!!cp ('t87.6');
4531              return;          !!!parse-error (type => 'not closed',
4532            } elsif ({                          text => $self->{open_elements}->[-1]->[0]
4533                      table => 1, caption => 1, td => 1, th => 1,                              ->manakai_local_name,
4534                      button => 1, marquee => 1, object => 1, html => 1,                          token => $token);
4535                     }->{$_->[1]}) {  
4536              last INSCOPE;          pop @{$self->{open_elements}}
4537            }              while $self->{open_elements}->[-1]->[1] & FOREIGN_EL;
4538          } # INSCOPE  
4539                      ## NOTE: |<span><svg>| ... two parse errors, |<svg>| ... a parse error.
4540          ## Step 1  
4541          my $i = -1;          $self->{insertion_mode} &= ~ IN_FOREIGN_CONTENT_IM;
4542          my $node = $self->{open_elements}->[$i];          ## Reprocess.
4543          LI: {          next B;
4544            ## Step 2        } else {
4545            if ($node->[1] eq 'li') {          die "$0: $token->{type}: Unknown token type";        
4546              if ($i != -1) {        }
4547                !!!parse-error (type => 'end tag missing:'.      }
4548                                $self->{open_elements}->[-1]->[1]);  
4549              }      if ($self->{insertion_mode} & HEAD_IMS) {
4550              splice @{$self->{open_elements}}, $i;        if ($token->{type} == CHARACTER_TOKEN) {
4551              last LI;          if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
4552            }            unless ($self->{insertion_mode} == BEFORE_HEAD_IM) {
4553                          if ($self->{head_element_inserted}) {
4554            ## Step 3                !!!cp ('t88.3');
4555            if (not $formatting_category->{$node->[1]} and                $self->{open_elements}->[-1]->[0]->append_child
4556                #not $phrasing_category->{$node->[1]} and                  ($self->{document}->create_text_node ($1));
4557                ($special_category->{$node->[1]} or                delete $self->{head_element_inserted};
4558                 $scoping_category->{$node->[1]}) and                ## NOTE: |</head> <link> |
4559                $node->[1] ne 'address' and $node->[1] ne 'div') {                #
4560              last LI;              } else {
4561            }                !!!cp ('t88.2');
4562                            $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
4563            ## Step 4                ## NOTE: |</head> &#x20;|
4564            $i--;                #
           $node = $self->{open_elements}->[$i];  
           redo LI;  
         } # LI  
             
         !!!insert-element-t ($token->{tag_name}, $token->{attributes});  
         !!!next-token;  
         return;  
       } elsif ($token->{tag_name} eq 'dd' or $token->{tag_name} eq 'dt') {  
         ## has a p element in scope  
         INSCOPE: for (reverse @{$self->{open_elements}}) {  
           if ($_->[1] eq 'p') {  
             !!!back-token;  
             $token = {type => 'end tag', tag_name => 'p'};  
             return;  
           } elsif ({  
                     table => 1, caption => 1, td => 1, th => 1,  
                     button => 1, marquee => 1, object => 1, html => 1,  
                    }->{$_->[1]}) {  
             last INSCOPE;  
           }  
         } # INSCOPE  
             
         ## Step 1  
         my $i = -1;  
         my $node = $self->{open_elements}->[$i];  
         LI: {  
           ## Step 2  
           if ($node->[1] eq 'dt' or $node->[1] eq 'dd') {  
             if ($i != -1) {  
               !!!parse-error (type => 'end tag missing:'.  
                               $self->{open_elements}->[-1]->[1]);  
4565              }              }
4566              splice @{$self->{open_elements}}, $i;            } else {
4567              last LI;              !!!cp ('t88.1');
4568            }              ## Ignore the token.
4569                          #
           ## Step 3  
           if (not $formatting_category->{$node->[1]} and  
               #not $phrasing_category->{$node->[1]} and  
               ($special_category->{$node->[1]} or  
                $scoping_category->{$node->[1]}) and  
               $node->[1] ne 'address' and $node->[1] ne 'div') {  
             last LI;  
           }  
             
           ## Step 4  
           $i--;  
           $node = $self->{open_elements}->[$i];  
           redo LI;  
         } # LI  
             
         !!!insert-element-t ($token->{tag_name}, $token->{attributes});  
         !!!next-token;  
         return;  
       } elsif ($token->{tag_name} eq 'plaintext') {  
         ## has a p element in scope  
         INSCOPE: for (reverse @{$self->{open_elements}}) {  
           if ($_->[1] eq 'p') {  
             !!!back-token;  
             $token = {type => 'end tag', tag_name => 'p'};  
             return;  
           } elsif ({  
                     table => 1, caption => 1, td => 1, th => 1,  
                     button => 1, marquee => 1, object => 1, html => 1,  
                    }->{$_->[1]}) {  
             last INSCOPE;  
           }  
         } # INSCOPE  
             
         !!!insert-element-t ($token->{tag_name}, $token->{attributes});  
             
         $self->{content_model} = PLAINTEXT_CONTENT_MODEL;  
             
         !!!next-token;  
         return;  
       } elsif ({  
                 h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,  
                }->{$token->{tag_name}}) {  
         ## has a p element in scope  
         INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {  
           my $node = $self->{open_elements}->[$_];  
           if ($node->[1] eq 'p') {  
             !!!back-token;  
             $token = {type => 'end tag', tag_name => 'p'};  
             return;  
           } elsif ({  
                     table => 1, caption => 1, td => 1, th => 1,  
                     button => 1, marquee => 1, object => 1, html => 1,  
                    }->{$node->[1]}) {  
             last INSCOPE;  
4570            }            }
4571          } # INSCOPE            unless (length $token->{data}) {
4572                          !!!cp ('t88');
4573          ## NOTE: See <http://html5.org/tools/web-apps-tracker?from=925&to=926>              !!!next-token;
4574          ## has an element in scope              next B;
         #my $i;  
         #INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {  
         #  my $node = $self->{open_elements}->[$_];  
         #  if ({  
         #       h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,  
         #      }->{$node->[1]}) {  
         #    $i = $_;  
         #    last INSCOPE;  
         #  } elsif ({  
         #            table => 1, caption => 1, td => 1, th => 1,  
         #            button => 1, marquee => 1, object => 1, html => 1,  
         #           }->{$node->[1]}) {  
         #    last INSCOPE;  
         #  }  
         #} # INSCOPE  
         #    
         #if (defined $i) {  
         #  !!! parse-error (type => 'in hn:hn');  
         #  splice @{$self->{open_elements}}, $i;  
         #}  
             
         !!!insert-element-t ($token->{tag_name}, $token->{attributes});  
             
         !!!next-token;  
         return;  
       } elsif ($token->{tag_name} eq 'a') {  
         AFE: for my $i (reverse 0..$#$active_formatting_elements) {  
           my $node = $active_formatting_elements->[$i];  
           if ($node->[1] eq 'a') {  
             !!!parse-error (type => 'in a:a');  
               
             !!!back-token;  
             $token = {type => 'end tag', tag_name => 'a'};  
             $formatting_end_tag->($token->{tag_name});  
               
             AFE2: for (reverse 0..$#$active_formatting_elements) {  
               if ($active_formatting_elements->[$_]->[0] eq $node->[0]) {  
                 splice @$active_formatting_elements, $_, 1;  
                 last AFE2;  
               }  
             } # AFE2  
             OE: for (reverse 0..$#{$self->{open_elements}}) {  
               if ($self->{open_elements}->[$_]->[0] eq $node->[0]) {  
                 splice @{$self->{open_elements}}, $_, 1;  
                 last OE;  
               }  
             } # OE  
             last AFE;  
           } elsif ($node->[0] eq '#marker') {  
             last AFE;  
4575            }            }
4576          } # AFE  ## TODO: set $token->{column} appropriately
4577                      }
         $reconstruct_active_formatting_elements->($insert_to_current);  
4578    
4579          !!!insert-element-t ($token->{tag_name}, $token->{attributes});          if ($self->{insertion_mode} == BEFORE_HEAD_IM) {
4580          push @$active_formatting_elements, $self->{open_elements}->[-1];            !!!cp ('t89');
4581              ## As if <head>
4582              !!!create-element ($self->{head_element}, $HTML_NS, 'head',, $token);
4583              $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
4584              push @{$self->{open_elements}},
4585                  [$self->{head_element}, $el_category->{head}];
4586    
4587          !!!next-token;            ## Reprocess in the "in head" insertion mode...
4588          return;            pop @{$self->{open_elements}};
       } elsif ({  
                 b => 1, big => 1, em => 1, font => 1, i => 1,  
                 s => 1, small => 1, strile => 1,  
                 strong => 1, tt => 1, u => 1,  
                }->{$token->{tag_name}}) {  
         $reconstruct_active_formatting_elements->($insert_to_current);  
           
         !!!insert-element-t ($token->{tag_name}, $token->{attributes});  
         push @$active_formatting_elements, $self->{open_elements}->[-1];  
           
         !!!next-token;  
         return;  
       } elsif ($token->{tag_name} eq 'nobr') {  
         $reconstruct_active_formatting_elements->($insert_to_current);  
4589    
4590          ## has a |nobr| element in scope            ## Reprocess in the "after head" insertion mode...
4591          INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {          } elsif ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
4592            my $node = $self->{open_elements}->[$_];            !!!cp ('t90');
4593            if ($node->[1] eq 'nobr') {            ## As if </noscript>
4594              !!!parse-error (type => 'not closed:nobr');            pop @{$self->{open_elements}};
4595              !!!back-token;            !!!parse-error (type => 'in noscript:#text', token => $token);
             $token = {type => 'end tag', tag_name => 'nobr'};  
             return;  
           } elsif ({  
                     table => 1, caption => 1, td => 1, th => 1,  
                     button => 1, marquee => 1, object => 1, html => 1,  
                    }->{$node->[1]}) {  
             last INSCOPE;  
           }  
         } # INSCOPE  
           
         !!!insert-element-t ($token->{tag_name}, $token->{attributes});  
         push @$active_formatting_elements, $self->{open_elements}->[-1];  
           
         !!!next-token;  
         return;  
       } elsif ($token->{tag_name} eq 'button') {  
         ## has a button element in scope  
         INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {  
           my $node = $self->{open_elements}->[$_];  
           if ($node->[1] eq 'button') {  
             !!!parse-error (type => 'in button:button');  
             !!!back-token;  
             $token = {type => 'end tag', tag_name => 'button'};  
             return;  
           } elsif ({  
                     table => 1, caption => 1, td => 1, th => 1,  
                     button => 1, marquee => 1, object => 1, html => 1,  
                    }->{$node->[1]}) {  
             last INSCOPE;  
           }  
         } # INSCOPE  
             
         $reconstruct_active_formatting_elements->($insert_to_current);  
4596                        
4597          !!!insert-element-t ($token->{tag_name}, $token->{attributes});            ## Reprocess in the "in head" insertion mode...
4598          push @$active_formatting_elements, ['#marker', ''];            ## As if </head>
4599              pop @{$self->{open_elements}};
4600    
4601          !!!next-token;            ## Reprocess in the "after head" insertion mode...
4602          return;          } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
4603        } elsif ($token->{tag_name} eq 'marquee' or            !!!cp ('t91');
4604                 $token->{tag_name} eq 'object') {            pop @{$self->{open_elements}};
         $reconstruct_active_formatting_elements->($insert_to_current);  
           
         !!!insert-element-t ($token->{tag_name}, $token->{attributes});  
         push @$active_formatting_elements, ['#marker', ''];  
           
         !!!next-token;  
         return;  
       } elsif ($token->{tag_name} eq 'xmp') {  
         $reconstruct_active_formatting_elements->($insert_to_current);  
         $parse_rcdata->(CDATA_CONTENT_MODEL, $insert);  
         return;  
       } elsif ($token->{tag_name} eq 'table') {  
         ## has a p element in scope  
         INSCOPE: for (reverse @{$self->{open_elements}}) {  
           if ($_->[1] eq 'p') {  
             !!!back-token;  
             $token = {type => 'end tag', tag_name => 'p'};  
             return;  
           } elsif ({  
                     table => 1, caption => 1, td => 1, th => 1,  
                     button => 1, marquee => 1, object => 1, html => 1,  
                    }->{$_->[1]}) {  
             last INSCOPE;  
           }  
         } # INSCOPE  
             
         !!!insert-element-t ($token->{tag_name}, $token->{attributes});  
             
         $self->{insertion_mode} = 'in table';  
             
         !!!next-token;  
         return;  
       } elsif ({  
                 area => 1, basefont => 1, bgsound => 1, br => 1,  
                 embed => 1, img => 1, param => 1, spacer => 1, wbr => 1,  
                 image => 1,  
                }->{$token->{tag_name}}) {  
         if ($token->{tag_name} eq 'image') {  
           !!!parse-error (type => 'image');  
           $token->{tag_name} = 'img';  
         }  
4605    
4606          ## NOTE: There is an "as if <br>" code clone.            ## Reprocess in the "after head" insertion mode...
         $reconstruct_active_formatting_elements->($insert_to_current);  
           
         !!!insert-element-t ($token->{tag_name}, $token->{attributes});  
         pop @{$self->{open_elements}};  
           
         !!!next-token;  
         return;  
       } elsif ($token->{tag_name} eq 'hr') {  
         ## has a p element in scope  
         INSCOPE: for (reverse @{$self->{open_elements}}) {  
           if ($_->[1] eq 'p') {  
             !!!back-token;  
             $token = {type => 'end tag', tag_name => 'p'};  
             return;  
           } elsif ({  
                     table => 1, caption => 1, td => 1, th => 1,  
                     button => 1, marquee => 1, object => 1, html => 1,  
                    }->{$_->[1]}) {  
             last INSCOPE;  
           }  
         } # INSCOPE  
             
         !!!insert-element-t ($token->{tag_name}, $token->{attributes});  
         pop @{$self->{open_elements}};  
             
         !!!next-token;  
         return;  
       } elsif ($token->{tag_name} eq 'input') {  
         $reconstruct_active_formatting_elements->($insert_to_current);  
           
         !!!insert-element-t ($token->{tag_name}, $token->{attributes});  
         ## TODO: associate with $self->{form_element} if defined  
         pop @{$self->{open_elements}};  
           
         !!!next-token;  
         return;  
       } elsif ($token->{tag_name} eq 'isindex') {  
         !!!parse-error (type => 'isindex');  
           
         if (defined $self->{form_element}) {  
           ## Ignore the token  
           !!!next-token;  
           return;  
4607          } else {          } else {
4608            my $at = $token->{attributes};            !!!cp ('t92');
           my $form_attrs;  
           $form_attrs->{action} = $at->{action} if $at->{action};  
           my $prompt_attr = $at->{prompt};  
           $at->{name} = {name => 'name', value => 'isindex'};  
           delete $at->{action};  
           delete $at->{prompt};  
           my @tokens = (  
                         {type => 'start tag', tag_name => 'form',  
                          attributes => $form_attrs},  
                         {type => 'start tag', tag_name => 'hr'},  
                         {type => 'start tag', tag_name => 'p'},  
                         {type => 'start tag', tag_name => 'label'},  
                        );  
           if ($prompt_attr) {  
             push @tokens, {type => 'character', data => $prompt_attr->{value}};  
           } else {  
             push @tokens, {type => 'character',  
                            data => 'This is a searchable index. Insert your search keywords here: '}; # SHOULD  
             ## TODO: make this configurable  
           }  
           push @tokens,  
                         {type => 'start tag', tag_name => 'input', attributes => $at},  
                         #{type => 'character', data => ''}, # SHOULD  
                         {type => 'end tag', tag_name => 'label'},  
                         {type => 'end tag', tag_name => 'p'},  
                         {type => 'start tag', tag_name => 'hr'},  
                         {type => 'end tag', tag_name => 'form'};  
           $token = shift @tokens;  
           !!!back-token (@tokens);  
           return;  
4609          }          }
4610        } elsif ($token->{tag_name} eq 'textarea') {  
4611          my $tag_name = $token->{tag_name};          ## "after head" insertion mode
4612          my $el;          ## As if <body>
4613          !!!create-element ($el, $token->{tag_name}, $token->{attributes});          !!!insert-element ('body',, $token);
4614                    $self->{insertion_mode} = IN_BODY_IM;
4615          ## TODO: $self->{form_element} if defined          ## reprocess
4616          $self->{content_model} = RCDATA_CONTENT_MODEL;          next B;
4617          delete $self->{escape}; # MUST        } elsif ($token->{type} == START_TAG_TOKEN) {
4618                    if ($token->{tag_name} eq 'head') {
4619          $insert->($el);            if ($self->{insertion_mode} == BEFORE_HEAD_IM) {
4620                        !!!cp ('t93');
4621          my $text = '';              !!!create-element ($self->{head_element}, $HTML_NS, $token->{tag_name}, $token->{attributes}, $token);
4622          !!!next-token;              $self->{open_elements}->[-1]->[0]->append_child
4623          if ($token->{type} eq 'character') {                  ($self->{head_element});
4624            $token->{data} =~ s/^\x0A//;              push @{$self->{open_elements}},
4625            unless (length $token->{data}) {                  [$self->{head_element}, $el_category->{head}];
4626                $self->{insertion_mode} = IN_HEAD_IM;
4627                !!!nack ('t93.1');
4628              !!!next-token;              !!!next-token;
4629                next B;
4630              } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
4631                !!!cp ('t93.2');
4632                !!!parse-error (type => 'after head', text => 'head',
4633                                token => $token);
4634                ## Ignore the token
4635                !!!nack ('t93.3');
4636                !!!next-token;
4637                next B;
4638              } else {
4639                !!!cp ('t95');
4640                !!!parse-error (type => 'in head:head',
4641                                token => $token); # or in head noscript
4642                ## Ignore the token
4643                !!!nack ('t95.1');
4644                !!!next-token;
4645                next B;
4646            }            }
4647          }          } elsif ($self->{insertion_mode} == BEFORE_HEAD_IM) {
4648          while ($token->{type} eq 'character') {            !!!cp ('t96');
4649            $text .= $token->{data};            ## As if <head>
4650            !!!next-token;            !!!create-element ($self->{head_element}, $HTML_NS, 'head',, $token);
4651          }            $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
4652          if (length $text) {            push @{$self->{open_elements}},
4653            $el->manakai_append_text ($text);                [$self->{head_element}, $el_category->{head}];
4654          }  
4655                      $self->{insertion_mode} = IN_HEAD_IM;
4656          $self->{content_model} = PCDATA_CONTENT_MODEL;            ## Reprocess in the "in head" insertion mode...
4657                    } else {
4658          if ($token->{type} eq 'end tag' and            !!!cp ('t97');
4659              $token->{tag_name} eq $tag_name) {          }
4660            ## Ignore the token  
4661          } else {          if ($token->{tag_name} eq 'base') {
4662            !!!parse-error (type => 'in RCDATA:#'.$token->{type});            if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
4663          }              !!!cp ('t98');
4664          !!!next-token;              ## As if </noscript>
4665          return;              pop @{$self->{open_elements}};
4666        } elsif ({              !!!parse-error (type => 'in noscript', text => 'base',
4667                  iframe => 1,                              token => $token);
4668                  noembed => 1,            
4669                  noframes => 1,              $self->{insertion_mode} = IN_HEAD_IM;
4670                  noscript => 0, ## TODO: 1 if scripting is enabled              ## Reprocess in the "in head" insertion mode...
4671                 }->{$token->{tag_name}}) {            } else {
4672          ## NOTE: There are two "as if in body" code clones.              !!!cp ('t99');
         $parse_rcdata->(CDATA_CONTENT_MODEL, $insert);  
         return;  
       } elsif ($token->{tag_name} eq 'select') {  
         $reconstruct_active_formatting_elements->($insert_to_current);  
           
         !!!insert-element-t ($token->{tag_name}, $token->{attributes});  
           
         $self->{insertion_mode} = 'in select';  
         !!!next-token;  
         return;  
       } elsif ({  
                 caption => 1, col => 1, colgroup => 1, frame => 1,  
                 frameset => 1, head => 1, option => 1, optgroup => 1,  
                 tbody => 1, td => 1, tfoot => 1, th => 1,  
                 thead => 1, tr => 1,  
                }->{$token->{tag_name}}) {  
         !!!parse-error (type => 'in body:'.$token->{tag_name});  
         ## Ignore the token  
         !!!next-token;  
         return;  
           
         ## ISSUE: An issue on HTML5 new elements in the spec.  
       } else {  
         $reconstruct_active_formatting_elements->($insert_to_current);  
           
         !!!insert-element-t ($token->{tag_name}, $token->{attributes});  
           
         !!!next-token;  
         return;  
       }  
     } elsif ($token->{type} eq 'end tag') {  
       if ($token->{tag_name} eq 'body') {  
         if (@{$self->{open_elements}} > 1 and  
             $self->{open_elements}->[1]->[1] eq 'body') {  
           for (@{$self->{open_elements}}) {  
             unless ({  
                        dd => 1, dt => 1, li => 1, p => 1, td => 1,  
                        th => 1, tr => 1, body => 1, html => 1,  
                      tbody => 1, tfoot => 1, thead => 1,  
                     }->{$_->[1]}) {  
               !!!parse-error (type => 'not closed:'.$_->[1]);  
             }  
4673            }            }
4674    
4675            $self->{insertion_mode} = 'after body';            ## NOTE: There is a "as if in head" code clone.
4676            !!!next-token;            if ($self->{insertion_mode} == AFTER_HEAD_IM) {
4677            return;              !!!cp ('t100');
4678          } else {              !!!parse-error (type => 'after head',
4679            !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});                              text => $token->{tag_name}, token => $token);
4680            ## Ignore the token              push @{$self->{open_elements}},
4681            !!!next-token;                  [$self->{head_element}, $el_category->{head}];
4682            return;              $self->{head_element_inserted} = 1;
4683          }            } else {
4684        } elsif ($token->{tag_name} eq 'html') {              !!!cp ('t101');
         if (@{$self->{open_elements}} > 1 and $self->{open_elements}->[1]->[1] eq 'body') {  
           ## ISSUE: There is an issue in the spec.  
           if ($self->{open_elements}->[-1]->[1] ne 'body') {  
             !!!parse-error (type => 'not closed:'.$self->{open_elements}->[1]->[1]);  
4685            }            }
4686            $self->{insertion_mode} = 'after body';            !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
4687            ## reprocess            pop @{$self->{open_elements}};
4688            return;            pop @{$self->{open_elements}} # <head>
4689          } else {                if $self->{insertion_mode} == AFTER_HEAD_IM;
4690            !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});            !!!nack ('t101.1');
           ## Ignore the token  
4691            !!!next-token;            !!!next-token;
4692            return;            next B;
4693          }          } elsif ($token->{tag_name} eq 'link') {
4694        } elsif ({            ## NOTE: There is a "as if in head" code clone.
4695                  address => 1, blockquote => 1, center => 1, dir => 1,            if ($self->{insertion_mode} == AFTER_HEAD_IM) {
4696                  div => 1, dl => 1, fieldset => 1, listing => 1,              !!!cp ('t102');
4697                  menu => 1, ol => 1, pre => 1, ul => 1,              !!!parse-error (type => 'after head',
4698                  p => 1,                              text => $token->{tag_name}, token => $token);
4699                  dd => 1, dt => 1, li => 1,              push @{$self->{open_elements}},
4700                  button => 1, marquee => 1, object => 1,                  [$self->{head_element}, $el_category->{head}];
4701                 }->{$token->{tag_name}}) {              $self->{head_element_inserted} = 1;
         ## has an element in scope  
         my $i;  
         INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {  
           my $node = $self->{open_elements}->[$_];  
           if ($node->[1] eq $token->{tag_name}) {  
             ## generate implied end tags  
             if ({  
                  dd => ($token->{tag_name} ne 'dd'),  
                  dt => ($token->{tag_name} ne 'dt'),  
                  li => ($token->{tag_name} ne 'li'),  
                  p => ($token->{tag_name} ne 'p'),  
                  td => 1, th => 1, tr => 1,  
                  tbody => 1, tfoot=> 1, thead => 1,  
                 }->{$self->{open_elements}->[-1]->[1]}) {  
               !!!back-token;  
               $token = {type => 'end tag',  
                         tag_name => $self->{open_elements}->[-1]->[1]}; # MUST  
               return;  
             }  
             $i = $_;  
             last INSCOPE unless $token->{tag_name} eq 'p';  
           } elsif ({  
                     table => 1, caption => 1, td => 1, th => 1,  
                     button => 1, marquee => 1, object => 1, html => 1,  
                    }->{$node->[1]}) {  
             last INSCOPE;  
           }  
         } # INSCOPE  
           
         if ($self->{open_elements}->[-1]->[1] ne $token->{tag_name}) {  
           if (defined $i) {  
             !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);  
4702            } else {            } else {
4703              !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});              !!!cp ('t103');
           }  
         }  
           
         if (defined $i) {  
           splice @{$self->{open_elements}}, $i;  
         } elsif ($token->{tag_name} eq 'p') {  
           ## As if <p>, then reprocess the current token  
           my $el;  
           !!!create-element ($el, 'p');  
           $insert->($el);  
         }  
         $clear_up_to_marker->()  
           if {  
             button => 1, marquee => 1, object => 1,  
           }->{$token->{tag_name}};  
         !!!next-token;  
         return;  
       } elsif ($token->{tag_name} eq 'form') {  
         ## has an element in scope  
         INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {  
           my $node = $self->{open_elements}->[$_];  
           if ($node->[1] eq $token->{tag_name}) {  
             ## generate implied end tags  
             if ({  
                  dd => 1, dt => 1, li => 1, p => 1,  
                  td => 1, th => 1, tr => 1,  
                  tbody => 1, tfoot=> 1, thead => 1,  
                 }->{$self->{open_elements}->[-1]->[1]}) {  
               !!!back-token;  
               $token = {type => 'end tag',  
                         tag_name => $self->{open_elements}->[-1]->[1]}; # MUST  
               return;  
             }  
             last INSCOPE;  
           } elsif ({  
                     table => 1, caption => 1, td => 1, th => 1,  
                     button => 1, marquee => 1, object => 1, html => 1,  
                    }->{$node->[1]}) {  
             last INSCOPE;  
4704            }            }
4705          } # INSCOPE            !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
           
         if ($self->{open_elements}->[-1]->[1] eq $token->{tag_name}) {  
4706            pop @{$self->{open_elements}};            pop @{$self->{open_elements}};
4707          } else {            pop @{$self->{open_elements}} # <head>
4708            !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);                if $self->{insertion_mode} == AFTER_HEAD_IM;
4709          }            !!!ack ('t103.1');
4710              !!!next-token;
4711              next B;
4712            } elsif ($token->{tag_name} eq 'command' or
4713                     $token->{tag_name} eq 'eventsource') {
4714              if ($self->{insertion_mode} == IN_HEAD_IM) {
4715                ## NOTE: If the insertion mode at the time of the emission
4716                ## of the token was "before head", $self->{insertion_mode}
4717                ## is already changed to |IN_HEAD_IM|.
4718    
4719          undef $self->{form_element};              ## NOTE: There is a "as if in head" code clone.
4720          !!!next-token;              !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
4721          return;              pop @{$self->{open_elements}};
4722        } elsif ({              pop @{$self->{open_elements}} # <head>
4723                  h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,                  if $self->{insertion_mode} == AFTER_HEAD_IM;
4724                 }->{$token->{tag_name}}) {              !!!ack ('t103.2');
4725          ## has an element in scope              !!!next-token;
4726          my $i;              next B;
4727          INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {            } else {
4728            my $node = $self->{open_elements}->[$_];              ## NOTE: "in head noscript" or "after head" insertion mode
4729            if ({              ## - in these cases, these tags are treated as same as
4730                 h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,              ## normal in-body tags.
4731                }->{$node->[1]}) {              !!!cp ('t103.3');
4732              ## generate implied end tags              #
             if ({  
                  dd => 1, dt => 1, li => 1, p => 1,  
                  td => 1, th => 1, tr => 1,  
                  tbody => 1, tfoot=> 1, thead => 1,  
                 }->{$self->{open_elements}->[-1]->[1]}) {  
               !!!back-token;  
               $token = {type => 'end tag',  
                         tag_name => $self->{open_elements}->[-1]->[1]}; # MUST  
               return;  
             }  
             $i = $_;  
             last INSCOPE;  
           } elsif ({  
                     table => 1, caption => 1, td => 1, th => 1,  
                     button => 1, marquee => 1, object => 1, html => 1,  
                    }->{$node->[1]}) {  
             last INSCOPE;  
4733            }            }
4734          } # INSCOPE          } elsif ($token->{tag_name} eq 'meta') {
4735                      ## NOTE: There is a "as if in head" code clone.
4736          if ($self->{open_elements}->[-1]->[1] ne $token->{tag_name}) {            if ($self->{insertion_mode} == AFTER_HEAD_IM) {
4737            !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);              !!!cp ('t104');
4738          }              !!!parse-error (type => 'after head',
4739                                        text => $token->{tag_name}, token => $token);
4740          splice @{$self->{open_elements}}, $i if defined $i;              push @{$self->{open_elements}},
4741          !!!next-token;                  [$self->{head_element}, $el_category->{head}];
4742          return;              $self->{head_element_inserted} = 1;
4743        } elsif ({            } else {
4744                  a => 1,              !!!cp ('t105');
4745                  b => 1, big => 1, em => 1, font => 1, i => 1,            }
4746                  nobr => 1, s => 1, small => 1, strile => 1,            !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
4747                  strong => 1, tt => 1, u => 1,            my $meta_el = pop @{$self->{open_elements}};
                }->{$token->{tag_name}}) {  
         $formatting_end_tag->($token->{tag_name});  
         return;  
       } elsif ($token->{tag_name} eq 'br') {  
         !!!parse-error (type => 'unmatched end tag:br');  
4748    
4749          ## As if <br>                unless ($self->{confident}) {
4750          $reconstruct_active_formatting_elements->($insert_to_current);                  if ($token->{attributes}->{charset}) {
4751                              !!!cp ('t106');
4752          my $el;                    ## NOTE: Whether the encoding is supported or not is handled
4753          !!!create-element ($el, 'br');                    ## in the {change_encoding} callback.
4754          $insert->($el);                    $self->{change_encoding}
4755                                  ->($self, $token->{attributes}->{charset}->{value},
4756          ## Ignore the token.                           $token);
4757          !!!next-token;                    
4758          return;                    $meta_el->[0]->get_attribute_node_ns (undef, 'charset')
4759        } elsif ({                        ->set_user_data (manakai_has_reference =>
4760                  caption => 1, col => 1, colgroup => 1, frame => 1,                                             $token->{attributes}->{charset}
4761                  frameset => 1, head => 1, option => 1, optgroup => 1,                                                 ->{has_reference});
4762                  tbody => 1, td => 1, tfoot => 1, th => 1,                  } elsif ($token->{attributes}->{content}) {
4763                  thead => 1, tr => 1,                    if ($token->{attributes}->{content}->{value}
4764                  area => 1, basefont => 1, bgsound => 1,                        =~ /[Cc][Hh][Aa][Rr][Ss][Ee][Tt]
4765                  embed => 1, hr => 1, iframe => 1, image => 1,                            [\x09\x0A\x0C\x0D\x20]*=
4766                  img => 1, input => 1, isindex => 1, noembed => 1,                            [\x09\x0A\x0C\x0D\x20]*(?>"([^"]*)"|'([^']*)'|
4767                  noframes => 1, param => 1, select => 1, spacer => 1,                            ([^"'\x09\x0A\x0C\x0D\x20]
4768                  table => 1, textarea => 1, wbr => 1,                             [^\x09\x0A\x0C\x0D\x20\x3B]*))/x) {
4769                  noscript => 0, ## TODO: if scripting is enabled                      !!!cp ('t107');
4770                 }->{$token->{tag_name}}) {                      ## NOTE: Whether the encoding is supported or not is handled
4771          !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});                      ## in the {change_encoding} callback.
4772          ## Ignore the token                      $self->{change_encoding}
4773          !!!next-token;                          ->($self, defined $1 ? $1 : defined $2 ? $2 : $3,
4774          return;                             $token);
4775                                $meta_el->[0]->get_attribute_node_ns (undef, 'content')
4776          ## ISSUE: Issue on HTML5 new elements in spec                          ->set_user_data (manakai_has_reference =>
4777                                                         $token->{attributes}->{content}
4778        } else {                                                     ->{has_reference});
4779          ## Step 1                    } else {
4780          my $node_i = -1;                      !!!cp ('t108');
4781          my $node = $self->{open_elements}->[$node_i];                    }
4782                    }
4783                  } else {
4784                    if ($token->{attributes}->{charset}) {
4785                      !!!cp ('t109');
4786                      $meta_el->[0]->get_attribute_node_ns (undef, 'charset')
4787                          ->set_user_data (manakai_has_reference =>
4788                                               $token->{attributes}->{charset}
4789                                                   ->{has_reference});
4790                    }
4791                    if ($token->{attributes}->{content}) {
4792                      !!!cp ('t110');
4793                      $meta_el->[0]->get_attribute_node_ns (undef, 'content')
4794                          ->set_user_data (manakai_has_reference =>
4795                                               $token->{attributes}->{content}
4796                                                   ->{has_reference});
4797                    }
4798                  }
4799    
4800          ## Step 2                pop @{$self->{open_elements}} # <head>
4801          S2: {                    if $self->{insertion_mode} == AFTER_HEAD_IM;
4802            if ($node->[1] eq $token->{tag_name}) {                !!!ack ('t110.1');
4803              ## Step 1                !!!next-token;
4804              ## generate implied end tags                next B;
4805              if ({          } elsif ($token->{tag_name} eq 'title') {
4806                   dd => 1, dt => 1, li => 1, p => 1,            if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
4807                   td => 1, th => 1, tr => 1,              !!!cp ('t111');
4808                   tbody => 1, tfoot=> 1, thead => 1,              ## As if </noscript>
4809                  }->{$self->{open_elements}->[-1]->[1]}) {              pop @{$self->{open_elements}};
4810                !!!back-token;              !!!parse-error (type => 'in noscript', text => 'title',
4811                $token = {type => 'end tag',                              token => $token);
4812                          tag_name => $self->{open_elements}->[-1]->[1]}; # MUST            
4813                return;              $self->{insertion_mode} = IN_HEAD_IM;
4814              }              ## Reprocess in the "in head" insertion mode...
4815                      } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
4816              ## Step 2              !!!cp ('t112');
4817              if ($token->{tag_name} ne $self->{open_elements}->[-1]->[1]) {              !!!parse-error (type => 'after head',
4818                !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);                              text => $token->{tag_name}, token => $token);
4819              }              push @{$self->{open_elements}},
4820                                [$self->{head_element}, $el_category->{head}];
4821              ## Step 3              $self->{head_element_inserted} = 1;
4822              splice @{$self->{open_elements}}, $node_i;            } else {
4823                !!!cp ('t113');
4824              }
4825    
4826              !!!next-token;            ## NOTE: There is a "as if in head" code clone.
4827              last S2;            $parse_rcdata->(RCDATA_CONTENT_MODEL);
4828              pop @{$self->{open_elements}} # <head>
4829                  if $self->{insertion_mode} == AFTER_HEAD_IM;
4830              next B;
4831            } elsif ($token->{tag_name} eq 'style' or
4832                     $token->{tag_name} eq 'noframes') {
4833              ## NOTE: Or (scripting is enabled and tag_name eq 'noscript' and
4834              ## insertion mode IN_HEAD_IM)
4835              ## NOTE: There is a "as if in head" code clone.
4836              if ($self->{insertion_mode} == AFTER_HEAD_IM) {
4837                !!!cp ('t114');
4838                !!!parse-error (type => 'after head',
4839                                text => $token->{tag_name}, token => $token);
4840                push @{$self->{open_elements}},
4841                    [$self->{head_element}, $el_category->{head}];
4842                $self->{head_element_inserted} = 1;
4843            } else {            } else {
4844              ## Step 3              !!!cp ('t115');
             if (not $formatting_category->{$node->[1]} and  
                 #not $phrasing_category->{$node->[1]} and  
                 ($special_category->{$node->[1]} or  
                  $scoping_category->{$node->[1]})) {  
               !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});  
               ## Ignore the token  
               !!!next-token;  
               last S2;  
             }  
4845            }            }
4846              $parse_rcdata->(CDATA_CONTENT_MODEL);
4847              pop @{$self->{open_elements}} # <head>
4848                  if $self->{insertion_mode} == AFTER_HEAD_IM;
4849              next B;
4850                } elsif ($token->{tag_name} eq 'noscript') {
4851                  if ($self->{insertion_mode} == IN_HEAD_IM) {
4852                    !!!cp ('t116');
4853                    ## NOTE: and scripting is disalbed
4854                    !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
4855                    $self->{insertion_mode} = IN_HEAD_NOSCRIPT_IM;
4856                    !!!nack ('t116.1');
4857                    !!!next-token;
4858                    next B;
4859                  } elsif ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
4860                    !!!cp ('t117');
4861                    !!!parse-error (type => 'in noscript', text => 'noscript',
4862                                    token => $token);
4863                    ## Ignore the token
4864                    !!!nack ('t117.1');
4865                    !!!next-token;
4866                    next B;
4867                  } else {
4868                    !!!cp ('t118');
4869                    #
4870                  }
4871            } elsif ($token->{tag_name} eq 'script') {
4872              if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
4873                !!!cp ('t119');
4874                ## As if </noscript>
4875                pop @{$self->{open_elements}};
4876                !!!parse-error (type => 'in noscript', text => 'script',
4877                                token => $token);
4878                        
4879            ## Step 4              $self->{insertion_mode} = IN_HEAD_IM;
4880            $node_i--;              ## Reprocess in the "in head" insertion mode...
4881            $node = $self->{open_elements}->[$node_i];            } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
4882                          !!!cp ('t120');
4883            ## Step 5;              !!!parse-error (type => 'after head',
4884            redo S2;                              text => $token->{tag_name}, token => $token);
4885          } # S2              push @{$self->{open_elements}},
4886          return;                  [$self->{head_element}, $el_category->{head}];
4887        }              $self->{head_element_inserted} = 1;
4888      }            } else {
4889    }; # $in_body              !!!cp ('t121');
4890              }
   B: {  
     if ($token->{type} eq 'DOCTYPE') {  
       !!!parse-error (type => 'DOCTYPE in the middle');  
       ## Ignore the token  
       ## Stay in the phase  
       !!!next-token;  
       redo B;  
     } elsif ($token->{type} eq 'end-of-file') {  
       if ($token->{insertion_mode} ne 'trailing end') {  
         ## Generate implied end tags  
         if ({  
              dd => 1, dt => 1, li => 1, p => 1, td => 1, th => 1, tr => 1,  
              tbody => 1, tfoot=> 1, thead => 1,  
             }->{$self->{open_elements}->[-1]->[1]}) {  
           !!!back-token;  
           $token = {type => 'end tag', tag_name => $self->{open_elements}->[-1]->[1]};  
           redo B;  
         }  
           
         if (@{$self->{open_elements}} > 2 or  
             (@{$self->{open_elements}} == 2 and $self->{open_elements}->[1]->[1] ne 'body')) {  
           !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);  
         } elsif (defined $self->{inner_html_node} and  
                  @{$self->{open_elements}} > 1 and  
                  $self->{open_elements}->[1]->[1] ne 'body') {  
           !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);  
         }  
   
         ## ISSUE: There is an issue in the spec.  
       }  
4891    
4892        ## Stop parsing            ## NOTE: There is a "as if in head" code clone.
4893        last B;            $script_start_tag->();
4894      } elsif ($token->{type} eq 'start tag' and            pop @{$self->{open_elements}} # <head>
4895               $token->{tag_name} eq 'html') {                if $self->{insertion_mode} == AFTER_HEAD_IM;
4896        if ($self->{insertion_mode} eq 'trailing end') {            next B;
4897          ## Turn into the main phase          } elsif ($token->{tag_name} eq 'body' or
4898          !!!parse-error (type => 'after html:html');                   $token->{tag_name} eq 'frameset') {
4899          $self->{insertion_mode} = $previous_insertion_mode;                if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
4900        }                  !!!cp ('t122');
4901                    ## As if </noscript>
4902                    pop @{$self->{open_elements}};
4903                    !!!parse-error (type => 'in noscript',
4904                                    text => $token->{tag_name}, token => $token);
4905                    
4906                    ## Reprocess in the "in head" insertion mode...
4907                    ## As if </head>
4908                    pop @{$self->{open_elements}};
4909                    
4910                    ## Reprocess in the "after head" insertion mode...
4911                  } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
4912                    !!!cp ('t124');
4913                    pop @{$self->{open_elements}};
4914                    
4915                    ## Reprocess in the "after head" insertion mode...
4916                  } else {
4917                    !!!cp ('t125');
4918                  }
4919    
4920  ## ISSUE: "aa<html>" is not a parse error.                ## "after head" insertion mode
4921  ## ISSUE: "<html>" in fragment is not a parse error.                !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
4922        unless ($token->{first_start_tag}) {                if ($token->{tag_name} eq 'body') {
4923          !!!parse-error (type => 'not first start tag');                  !!!cp ('t126');
4924        }                  $self->{insertion_mode} = IN_BODY_IM;
4925        my $top_el = $self->{open_elements}->[0]->[0];                } elsif ($token->{tag_name} eq 'frameset') {
4926        for my $attr_name (keys %{$token->{attributes}}) {                  !!!cp ('t127');
4927          unless ($top_el->has_attribute_ns (undef, $attr_name)) {                  $self->{insertion_mode} = IN_FRAMESET_IM;
4928            $top_el->set_attribute_ns                } else {
4929              (undef, [undef, $attr_name],                  die "$0: tag name: $self->{tag_name}";
              $token->{attributes}->{$attr_name}->{value});  
         }  
       }  
       !!!next-token;  
       redo B;  
     } elsif ($token->{type} eq 'comment') {  
       my $comment = $self->{document}->create_comment ($token->{data});  
       if ($self->{insertion_mode} eq 'trailing end') {  
         $self->{document}->append_child ($comment);  
       } elsif ($self->{insertion_mode} eq 'after body') {  
         $self->{open_elements}->[0]->[0]->append_child ($comment);  
       } else {  
         $self->{open_elements}->[-1]->[0]->append_child ($comment);  
       }  
       !!!next-token;  
       redo B;  
     } elsif ($self->{insertion_mode} eq 'before head') {  
           if ($token->{type} eq 'character') {  
             if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {  
               $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);  
               unless (length $token->{data}) {  
                 !!!next-token;  
                 redo B;  
4930                }                }
4931              }                !!!nack ('t127.1');
             ## As if <head>  
             !!!create-element ($self->{head_element}, 'head');  
             $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});  
             push @{$self->{open_elements}}, [$self->{head_element}, 'head'];  
             $self->{insertion_mode} = 'in head';  
             ## reprocess  
             redo B;  
           } elsif ($token->{type} eq 'start tag') {  
             my $attr = $token->{tag_name} eq 'head' ? $token->{attributes} : {};  
             !!!create-element ($self->{head_element}, 'head', $attr);  
             $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});  
             push @{$self->{open_elements}}, [$self->{head_element}, 'head'];  
             $self->{insertion_mode} = 'in head';  
             if ($token->{tag_name} eq 'head') {  
4932                !!!next-token;                !!!next-token;
4933              #} elsif ({                next B;
             #          base => 1, link => 1, meta => 1,  
             #          script => 1, style => 1, title => 1,  
             #         }->{$token->{tag_name}}) {  
             #  ## reprocess  
4934              } else {              } else {
4935                ## reprocess                !!!cp ('t128');
4936                  #
4937              }              }
4938              redo B;  
4939            } elsif ($token->{type} eq 'end tag') {              if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
4940              if ({                !!!cp ('t129');
4941                   head => 1, body => 1, html => 1,                ## As if </noscript>
4942                   p => 1, br => 1,                pop @{$self->{open_elements}};
4943                  }->{$token->{tag_name}}) {                !!!parse-error (type => 'in noscript:/',
4944                ## As if <head>                                text => $token->{tag_name}, token => $token);
4945                !!!create-element ($self->{head_element}, 'head');                
4946                $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});                ## Reprocess in the "in head" insertion mode...
4947                push @{$self->{open_elements}}, [$self->{head_element}, 'head'];                ## As if </head>
4948                $self->{insertion_mode} = 'in head';                pop @{$self->{open_elements}};
4949                ## reprocess  
4950                redo B;                ## Reprocess in the "after head" insertion mode...
4951                } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
4952                  !!!cp ('t130');
4953                  ## As if </head>
4954                  pop @{$self->{open_elements}};
4955    
4956                  ## Reprocess in the "after head" insertion mode...
4957              } else {              } else {
4958                !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});                !!!cp ('t131');
               ## Ignore the token ## ISSUE: An issue in the spec.  
               !!!next-token;  
               redo B;  
             }  
           } else {  
             die "$0: $token->{type}: Unknown type";  
           }  
         } elsif ($self->{insertion_mode} eq 'in head' or  
                  $self->{insertion_mode} eq 'in head noscript' or  
                  $self->{insertion_mode} eq 'after head') {  
           if ($token->{type} eq 'character') {  
             if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {  
               $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);  
               unless (length $token->{data}) {  
                 !!!next-token;  
                 redo B;  
               }  
4959              }              }
               
             #  
           } elsif ($token->{type} eq 'start tag') {  
             if ({base => ($self->{insertion_mode} eq 'in head' or  
                           $self->{insertion_mode} eq 'after head'),  
                  link => 1}->{$token->{tag_name}}) {  
               ## NOTE: There is a "as if in head" code clone.  
               if ($self->{insertion_mode} eq 'after head') {  
                 !!!parse-error (type => 'after head:'.$token->{tag_name});  
                 push @{$self->{open_elements}}, [$self->{head_element}, 'head'];  
               }  
               !!!insert-element ($token->{tag_name}, $token->{attributes});  
               pop @{$self->{open_elements}}; ## ISSUE: This step is missing in the spec.  
               pop @{$self->{open_elements}}  
                   if $self->{insertion_mode} eq 'after head';  
               !!!next-token;  
               redo B;  
             } elsif ($token->{tag_name} eq 'meta') {  
               ## NOTE: There is a "as if in head" code clone.  
               if ($self->{insertion_mode} eq 'after head') {  
                 !!!parse-error (type => 'after head:'.$token->{tag_name});  
                 push @{$self->{open_elements}}, [$self->{head_element}, 'head'];  
               }  
               !!!insert-element ($token->{tag_name}, $token->{attributes});  
               pop @{$self->{open_elements}}; ## ISSUE: This step is missing in the spec.  
4960    
4961                unless ($self->{confident}) {              ## "after head" insertion mode
4962                  my $charset;              ## As if <body>
4963                  if ($token->{attributes}->{charset}) { ## TODO: And if supported              !!!insert-element ('body',, $token);
4964                    $charset = $token->{attributes}->{charset}->{value};              $self->{insertion_mode} = IN_BODY_IM;
4965                  }              ## reprocess
4966                  if ($token->{attributes}->{'http-equiv'}) {              !!!ack-later;
4967                    ## ISSUE: Algorithm name in the spec was incorrect so that not linked to the definition.              next B;
4968                    if ($token->{attributes}->{'http-equiv'}->{value}            } elsif ($token->{type} == END_TAG_TOKEN) {
4969                        =~ /\A[^;]*;[\x09-\x0D\x20]*charset[\x09-\x0D\x20]*=              if ($token->{tag_name} eq 'head') {
4970                            [\x09-\x0D\x20]*(?>"([^"]*)"|'([^']*)'|                if ($self->{insertion_mode} == BEFORE_HEAD_IM) {
4971                            ([^"'\x09-\x0D\x20][^\x09-\x0D\x20]*))/x) {                  !!!cp ('t132');
4972                      $charset = defined $1 ? $1 : defined $2 ? $2 : $3;                  ## As if <head>
4973                    } ## TODO: And if supported                  !!!create-element ($self->{head_element}, $HTML_NS, 'head',, $token);
4974                  }                  $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
4975                  ## TODO: Change the encoding                  push @{$self->{open_elements}},
4976                }                      [$self->{head_element}, $el_category->{head}];
4977    
4978                ## TODO: Extracting |charset| from |meta|.                  ## Reprocess in the "in head" insertion mode...
4979                pop @{$self->{open_elements}}                  pop @{$self->{open_elements}};
4980                    if $self->{insertion_mode} eq 'after head';                  $self->{insertion_mode} = AFTER_HEAD_IM;
4981                !!!next-token;                  !!!next-token;
4982                redo B;                  next B;
4983              } elsif ($token->{tag_name} eq 'title' and                } elsif ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
4984                       $self->{insertion_mode} eq 'in head') {                  !!!cp ('t133');
4985                ## NOTE: There is a "as if in head" code clone.                  ## As if </noscript>
4986                if ($self->{insertion_mode} eq 'after head') {                  pop @{$self->{open_elements}};
4987                  !!!parse-error (type => 'after head:'.$token->{tag_name});                  !!!parse-error (type => 'in noscript:/',
4988                  push @{$self->{open_elements}}, [$self->{head_element}, 'head'];                                  text => 'head', token => $token);
4989                }                  
4990                my $parent = defined $self->{head_element} ? $self->{head_element}                  ## Reprocess in the "in head" insertion mode...
4991                    : $self->{open_elements}->[-1]->[0];                  pop @{$self->{open_elements}};
4992                $parse_rcdata->(RCDATA_CONTENT_MODEL,                  $self->{insertion_mode} = AFTER_HEAD_IM;
4993                                sub { $parent->append_child ($_[0]) });                  !!!next-token;
4994                pop @{$self->{open_elements}}                  next B;
4995                    if $self->{insertion_mode} eq 'after head';                } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
4996                redo B;                  !!!cp ('t134');
4997              } elsif ($token->{tag_name} eq 'style') {                  pop @{$self->{open_elements}};
4998                ## NOTE: Or (scripting is enabled and tag_name eq 'noscript' and                  $self->{insertion_mode} = AFTER_HEAD_IM;
4999                ## insertion mode 'in head')                  !!!next-token;
5000                ## NOTE: There is a "as if in head" code clone.                  next B;
5001                if ($self->{insertion_mode} eq 'after head') {                } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
5002                  !!!parse-error (type => 'after head:'.$token->{tag_name});                  !!!cp ('t134.1');
5003                  push @{$self->{open_elements}}, [$self->{head_element}, 'head'];                  !!!parse-error (type => 'unmatched end tag', text => 'head',
5004                                    token => $token);
5005                    ## Ignore the token
5006                    !!!next-token;
5007                    next B;
5008                  } else {
5009                    die "$0: $self->{insertion_mode}: Unknown insertion mode";
5010                }                }
               $parse_rcdata->(CDATA_CONTENT_MODEL, $insert_to_current);  
               pop @{$self->{open_elements}}  
                   if $self->{insertion_mode} eq 'after head';  
               redo B;  
5011              } elsif ($token->{tag_name} eq 'noscript') {              } elsif ($token->{tag_name} eq 'noscript') {
5012                if ($self->{insertion_mode} eq 'in head') {                if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
5013                  ## NOTE: and scripting is disalbed                  !!!cp ('t136');
5014                  !!!insert-element ($token->{tag_name}, $token->{attributes});                  pop @{$self->{open_elements}};
5015                  $self->{insertion_mode} = 'in head noscript';                  $self->{insertion_mode} = IN_HEAD_IM;
5016                  !!!next-token;                  !!!next-token;
5017                  redo B;                  next B;
5018                } elsif ($self->{insertion_mode} eq 'in head noscript') {                } elsif ($self->{insertion_mode} == BEFORE_HEAD_IM or
5019                  !!!parse-error (type => 'in noscript:noscript');                         $self->{insertion_mode} == AFTER_HEAD_IM) {
5020                  ## Ignore the token                  !!!cp ('t137');
5021                    !!!parse-error (type => 'unmatched end tag',
5022                                    text => 'noscript', token => $token);
5023                    ## Ignore the token ## ISSUE: An issue in the spec.
5024                  !!!next-token;                  !!!next-token;
5025                  redo B;                  next B;
5026                } else {                } else {
5027                    !!!cp ('t138');
5028                  #                  #
5029                }                }
5030              } elsif ($token->{tag_name} eq 'head' and              } elsif ({
5031                       $self->{insertion_mode} ne 'after head') {                        body => 1, html => 1,
5032                !!!parse-error (type => 'in head:head'); # or in head noscript                       }->{$token->{tag_name}}) {
5033                  ## TODO: This branch is entirely redundant.
5034                  if ($self->{insertion_mode} == BEFORE_HEAD_IM or
5035                      $self->{insertion_mode} == IN_HEAD_IM or
5036                      $self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
5037                    !!!cp ('t140');
5038                    !!!parse-error (type => 'unmatched end tag',
5039                                    text => $token->{tag_name}, token => $token);
5040                    ## Ignore the token
5041                    !!!next-token;
5042                    next B;
5043                  } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
5044                    !!!cp ('t140.1');
5045                    !!!parse-error (type => 'unmatched end tag',
5046                                    text => $token->{tag_name}, token => $token);
5047                    ## Ignore the token
5048                    !!!next-token;
5049                    next B;
5050                  } else {
5051                    die "$0: $self->{insertion_mode}: Unknown insertion mode";
5052                  }
5053                } elsif ($token->{tag_name} eq 'p') {
5054                  !!!cp ('t142');
5055                  !!!parse-error (type => 'unmatched end tag',
5056                                  text => $token->{tag_name}, token => $token);
5057                ## Ignore the token                ## Ignore the token
5058                !!!next-token;                !!!next-token;
5059                redo B;                next B;
5060              } elsif ($self->{insertion_mode} ne 'in head noscript' and              } elsif ($token->{tag_name} eq 'br') {
5061                       $token->{tag_name} eq 'script') {                if ($self->{insertion_mode} == BEFORE_HEAD_IM) {
5062                if ($self->{insertion_mode} eq 'after head') {                  !!!cp ('t142.2');
5063                  !!!parse-error (type => 'after head:'.$token->{tag_name});                  ## (before head) as if <head>, (in head) as if </head>
5064                  push @{$self->{open_elements}}, [$self->{head_element}, 'head'];                  !!!create-element ($self->{head_element}, $HTML_NS, 'head',, $token);
5065                    $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
5066                    $self->{insertion_mode} = AFTER_HEAD_IM;
5067      
5068                    ## Reprocess in the "after head" insertion mode...
5069                  } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
5070                    !!!cp ('t143.2');
5071                    ## As if </head>
5072                    pop @{$self->{open_elements}};
5073                    $self->{insertion_mode} = AFTER_HEAD_IM;
5074      
5075                    ## Reprocess in the "after head" insertion mode...
5076                  } elsif ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
5077                    !!!cp ('t143.3');
5078                    ## ISSUE: Two parse errors for <head><noscript></br>
5079                    !!!parse-error (type => 'unmatched end tag',
5080                                    text => 'br', token => $token);
5081                    ## As if </noscript>
5082                    pop @{$self->{open_elements}};
5083                    $self->{insertion_mode} = IN_HEAD_IM;
5084    
5085                    ## Reprocess in the "in head" insertion mode...
5086                    ## As if </head>
5087                    pop @{$self->{open_elements}};
5088                    $self->{insertion_mode} = AFTER_HEAD_IM;
5089    
5090                    ## Reprocess in the "after head" insertion mode...
5091                  } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
5092                    !!!cp ('t143.4');
5093                    #
5094                  } else {
5095                    die "$0: $self->{insertion_mode}: Unknown insertion mode";
5096                }                }
5097                ## NOTE: There is a "as if in head" code clone.  
5098                $script_start_tag->($insert_to_current);                ## ISSUE: does not agree with IE7 - it doesn't ignore </br>.
5099                pop @{$self->{open_elements}}                !!!parse-error (type => 'unmatched end tag',
5100                    if $self->{insertion_mode} eq 'after head';                                text => 'br', token => $token);
5101                redo B;                ## Ignore the token
             } elsif ($self->{insertion_mode} eq 'after head' and  
                      $token->{tag_name} eq 'body') {  
               !!!insert-element ('body', $token->{attributes});  
               $self->{insertion_mode} = 'in body';  
               !!!next-token;  
               redo B;  
             } elsif ($self->{insertion_mode} eq 'after head' and  
                      $token->{tag_name} eq 'frameset') {  
               !!!insert-element ('frameset', $token->{attributes});  
               $self->{insertion_mode} = 'in frameset';  
5102                !!!next-token;                !!!next-token;
5103                redo B;                next B;
5104              } else {              } else {
5105                #                !!!cp ('t145');
5106                  !!!parse-error (type => 'unmatched end tag',
5107                                  text => $token->{tag_name}, token => $token);
5108                  ## Ignore the token
5109                  !!!next-token;
5110                  next B;
5111              }              }
5112            } elsif ($token->{type} eq 'end tag') {  
5113              if ($self->{insertion_mode} eq 'in head' and              if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
5114                  $token->{tag_name} eq 'head') {                !!!cp ('t146');
5115                  ## As if </noscript>
5116                pop @{$self->{open_elements}};                pop @{$self->{open_elements}};
5117                $self->{insertion_mode} = 'after head';                !!!parse-error (type => 'in noscript:/',
5118                !!!next-token;                                text => $token->{tag_name}, token => $token);
5119                redo B;                
5120              } elsif ($self->{insertion_mode} eq 'in head noscript' and                ## Reprocess in the "in head" insertion mode...
5121                  $token->{tag_name} eq 'noscript') {                ## As if </head>
5122                pop @{$self->{open_elements}};                pop @{$self->{open_elements}};
5123                $self->{insertion_mode} = 'in head';  
5124                !!!next-token;                ## Reprocess in the "after head" insertion mode...
5125                redo B;              } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
5126              } elsif ($self->{insertion_mode} eq 'in head' and                !!!cp ('t147');
5127                       {                ## As if </head>
5128                        body => 1, html => 1,                pop @{$self->{open_elements}};
5129                        p => 1, br => 1,  
5130                       }->{$token->{tag_name}}) {                ## Reprocess in the "after head" insertion mode...
5131                #              } elsif ($self->{insertion_mode} == BEFORE_HEAD_IM) {
5132              } elsif ($self->{insertion_mode} eq 'in head noscript' and  ## ISSUE: This case cannot be reached?
5133                       {                !!!cp ('t148');
5134                        p => 1, br => 1,                !!!parse-error (type => 'unmatched end tag',
5135                       }->{$token->{tag_name}}) {                                text => $token->{tag_name}, token => $token);
5136                #                ## Ignore the token ## ISSUE: An issue in the spec.
             } elsif ($self->{insertion_mode} ne 'after head') {  
               !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});  
               ## Ignore the token  
5137                !!!next-token;                !!!next-token;
5138                redo B;                next B;
5139              } else {              } else {
5140                #                !!!cp ('t149');
5141              }              }
           } else {  
             #  
           }  
5142    
5143            ## As if </head> or </noscript> or <body>              ## "after head" insertion mode
5144            if ($self->{insertion_mode} eq 'in head') {              ## As if <body>
5145              pop @{$self->{open_elements}};              !!!insert-element ('body',, $token);
5146              $self->{insertion_mode} = 'after head';              $self->{insertion_mode} = IN_BODY_IM;
5147            } elsif ($self->{insertion_mode} eq 'in head noscript') {              ## reprocess
5148              pop @{$self->{open_elements}};              next B;
5149              !!!parse-error (type => 'in noscript:'.(defined $token->{tag_name} ? ($token->{type} eq 'end tag' ? '/' : '') . $token->{tag_name} : '#' . $token->{type}));        } elsif ($token->{type} == END_OF_FILE_TOKEN) {
5150              $self->{insertion_mode} = 'in head';          if ($self->{insertion_mode} == BEFORE_HEAD_IM) {
5151            } else { # 'after head'            !!!cp ('t149.1');
5152              !!!insert-element ('body');  
5153              $self->{insertion_mode} = 'in body';            ## NOTE: As if <head>
5154            }            !!!create-element ($self->{head_element}, $HTML_NS, 'head',, $token);
5155            ## reprocess            $self->{open_elements}->[-1]->[0]->append_child
5156            redo B;                ($self->{head_element});
5157              #push @{$self->{open_elements}},
5158              #    [$self->{head_element}, $el_category->{head}];
5159              #$self->{insertion_mode} = IN_HEAD_IM;
5160              ## NOTE: Reprocess.
5161    
5162              ## NOTE: As if </head>
5163              #pop @{$self->{open_elements}};
5164              #$self->{insertion_mode} = IN_AFTER_HEAD_IM;
5165              ## NOTE: Reprocess.
5166              
5167              #
5168            } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
5169              !!!cp ('t149.2');
5170    
5171              ## NOTE: As if </head>
5172              pop @{$self->{open_elements}};
5173              #$self->{insertion_mode} = IN_AFTER_HEAD_IM;
5174              ## NOTE: Reprocess.
5175    
5176              #
5177            } elsif ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
5178              !!!cp ('t149.3');
5179    
5180              !!!parse-error (type => 'in noscript:#eof', token => $token);
5181    
5182              ## As if </noscript>
5183              pop @{$self->{open_elements}};
5184              #$self->{insertion_mode} = IN_HEAD_IM;
5185              ## NOTE: Reprocess.
5186    
5187            ## ISSUE: An issue in the spec.            ## NOTE: As if </head>
5188          } elsif ($self->{insertion_mode} eq 'in body' or            pop @{$self->{open_elements}};
5189                   $self->{insertion_mode} eq 'in cell' or            #$self->{insertion_mode} = IN_AFTER_HEAD_IM;
5190                   $self->{insertion_mode} eq 'in caption') {            ## NOTE: Reprocess.
5191            if ($token->{type} eq 'character') {  
5192              #
5193            } else {
5194              !!!cp ('t149.4');
5195              #
5196            }
5197    
5198            ## NOTE: As if <body>
5199            !!!insert-element ('body',, $token);
5200            $self->{insertion_mode} = IN_BODY_IM;
5201            ## NOTE: Reprocess.
5202            next B;
5203          } else {
5204            die "$0: $token->{type}: Unknown token type";
5205          }
5206        } elsif ($self->{insertion_mode} & BODY_IMS) {
5207              if ($token->{type} == CHARACTER_TOKEN) {
5208                !!!cp ('t150');
5209              ## NOTE: There is a code clone of "character in body".              ## NOTE: There is a code clone of "character in body".
5210              $reconstruct_active_formatting_elements->($insert_to_current);              $reconstruct_active_formatting_elements->($insert_to_current);
5211                            
5212              $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});              $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
5213    
5214              !!!next-token;              !!!next-token;
5215              redo B;              next B;
5216            } elsif ($token->{type} eq 'start tag') {            } elsif ($token->{type} == START_TAG_TOKEN) {
5217              if ({              if ({
5218                   caption => 1, col => 1, colgroup => 1, tbody => 1,                   caption => 1, col => 1, colgroup => 1, tbody => 1,
5219                   td => 1, tfoot => 1, th => 1, thead => 1, tr => 1,                   td => 1, tfoot => 1, th => 1, thead => 1, tr => 1,
5220                  }->{$token->{tag_name}}) {                  }->{$token->{tag_name}}) {
5221                if ($self->{insertion_mode} eq 'in cell') {                if ($self->{insertion_mode} == IN_CELL_IM) {
5222                  ## have an element in table scope                  ## have an element in table scope
5223                  my $tn;                  for (reverse 0..$#{$self->{open_elements}}) {
                 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {  
5224                    my $node = $self->{open_elements}->[$_];                    my $node = $self->{open_elements}->[$_];
5225                    if ($node->[1] eq 'td' or $node->[1] eq 'th') {                    if ($node->[1] & TABLE_CELL_EL) {
5226                      $tn = $node->[1];                      !!!cp ('t151');
5227                      last INSCOPE;  
5228                    } elsif ({                      ## Close the cell
5229                              table => 1, html => 1,                      !!!back-token; # <x>
5230                             }->{$node->[1]}) {                      $token = {type => END_TAG_TOKEN,
5231                      last INSCOPE;                                tag_name => $node->[0]->manakai_local_name,
5232                    }                                line => $token->{line},
5233                  } # INSCOPE                                column => $token->{column}};
5234                    unless (defined $tn) {                      next B;
5235                      !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});                    } elsif ($node->[1] & TABLE_SCOPING_EL) {
5236                      ## Ignore the token                      !!!cp ('t152');
5237                      !!!next-token;                      ## ISSUE: This case can never be reached, maybe.
5238                      redo B;                      last;
5239                    }                    }
5240                    }
5241    
5242                    !!!cp ('t153');
5243                    !!!parse-error (type => 'start tag not allowed',
5244                        text => $token->{tag_name}, token => $token);
5245                    ## Ignore the token
5246                    !!!nack ('t153.1');
5247                    !!!next-token;
5248                    next B;
5249                  } elsif ($self->{insertion_mode} == IN_CAPTION_IM) {
5250                    !!!parse-error (type => 'not closed', text => 'caption',
5251                                    token => $token);
5252                                    
5253                  ## Close the cell                  ## NOTE: As if </caption>.
                 !!!back-token; # <?>  
                 $token = {type => 'end tag', tag_name => $tn};  
                 redo B;  
               } elsif ($self->{insertion_mode} eq 'in caption') {  
                 !!!parse-error (type => 'not closed:caption');  
                   
                 ## As if </caption>  
5254                  ## have a table element in table scope                  ## have a table element in table scope
5255                  my $i;                  my $i;
5256                  INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {                  INSCOPE: {
5257                    my $node = $self->{open_elements}->[$_];                    for (reverse 0..$#{$self->{open_elements}}) {
5258                    if ($node->[1] eq 'caption') {                      my $node = $self->{open_elements}->[$_];
5259                      $i = $_;                      if ($node->[1] & CAPTION_EL) {
5260                      last INSCOPE;                        !!!cp ('t155');
5261                    } elsif ({                        $i = $_;
5262                              table => 1, html => 1,                        last INSCOPE;
5263                             }->{$node->[1]}) {                      } elsif ($node->[1] & TABLE_SCOPING_EL) {
5264                      last INSCOPE;                        !!!cp ('t156');
5265                          last;
5266                        }
5267                    }                    }
5268    
5269                      !!!cp ('t157');
5270                      !!!parse-error (type => 'start tag not allowed',
5271                                      text => $token->{tag_name}, token => $token);
5272                      ## Ignore the token
5273                      !!!nack ('t157.1');
5274                      !!!next-token;
5275                      next B;
5276                  } # INSCOPE                  } # INSCOPE
                   unless (defined $i) {  
                     !!!parse-error (type => 'unmatched end tag:caption');  
                     ## Ignore the token  
                     !!!next-token;  
                     redo B;  
                   }  
5277                                    
5278                  ## generate implied end tags                  ## generate implied end tags
5279                  if ({                  while ($self->{open_elements}->[-1]->[1]
5280                       dd => 1, dt => 1, li => 1, p => 1,                             & END_TAG_OPTIONAL_EL) {
5281                       td => 1, th => 1, tr => 1,                    !!!cp ('t158');
5282                       tbody => 1, tfoot=> 1, thead => 1,                    pop @{$self->{open_elements}};
                     }->{$self->{open_elements}->[-1]->[1]}) {  
                   !!!back-token; # <?>  
                   $token = {type => 'end tag', tag_name => 'caption'};  
                   !!!back-token;  
                   $token = {type => 'end tag',  
                             tag_name => $self->{open_elements}->[-1]->[1]}; # MUST  
                   redo B;  
5283                  }                  }
5284    
5285                  if ($self->{open_elements}->[-1]->[1] ne 'caption') {                  unless ($self->{open_elements}->[-1]->[1] & CAPTION_EL) {
5286                    !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);                    !!!cp ('t159');
5287                      !!!parse-error (type => 'not closed',
5288                                      text => $self->{open_elements}->[-1]->[0]
5289                                          ->manakai_local_name,
5290                                      token => $token);
5291                    } else {
5292                      !!!cp ('t160');
5293                  }                  }
5294                                    
5295                  splice @{$self->{open_elements}}, $i;                  splice @{$self->{open_elements}}, $i;
5296                                    
5297                  $clear_up_to_marker->();                  $clear_up_to_marker->();
5298                                    
5299                  $self->{insertion_mode} = 'in table';                  $self->{insertion_mode} = IN_TABLE_IM;
5300                                    
5301                  ## reprocess                  ## reprocess
5302                  redo B;                  !!!ack-later;
5303                    next B;
5304                } else {                } else {
5305                    !!!cp ('t161');
5306                  #                  #
5307                }                }
5308              } else {              } else {
5309                  !!!cp ('t162');
5310                #                #
5311              }              }
5312            } elsif ($token->{type} eq 'end tag') {            } elsif ($token->{type} == END_TAG_TOKEN) {
5313              if ($token->{tag_name} eq 'td' or $token->{tag_name} eq 'th') {              if ($token->{tag_name} eq 'td' or $token->{tag_name} eq 'th') {
5314                if ($self->{insertion_mode} eq 'in cell') {                if ($self->{insertion_mode} == IN_CELL_IM) {
5315                  ## have an element in table scope                  ## have an element in table scope
5316                  my $i;                  my $i;
5317                  INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {                  INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5318                    my $node = $self->{open_elements}->[$_];                    my $node = $self->{open_elements}->[$_];
5319                    if ($node->[1] eq $token->{tag_name}) {                    if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
5320                        !!!cp ('t163');
5321                      $i = $_;                      $i = $_;
5322                      last INSCOPE;                      last INSCOPE;
5323                    } elsif ({                    } elsif ($node->[1] & TABLE_SCOPING_EL) {
5324                              table => 1, html => 1,                      !!!cp ('t164');
                            }->{$node->[1]}) {  
5325                      last INSCOPE;                      last INSCOPE;
5326                    }                    }
5327                  } # INSCOPE                  } # INSCOPE
5328                    unless (defined $i) {                    unless (defined $i) {
5329                      !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});                      !!!cp ('t165');
5330                        !!!parse-error (type => 'unmatched end tag',
5331                                        text => $token->{tag_name},
5332                                        token => $token);
5333                      ## Ignore the token                      ## Ignore the token
5334                      !!!next-token;                      !!!next-token;
5335                      redo B;                      next B;
5336                    }                    }
5337                                    
5338                  ## generate implied end tags                  ## generate implied end tags
5339                  if ({                  while ($self->{open_elements}->[-1]->[1]
5340                       dd => 1, dt => 1, li => 1, p => 1,                             & END_TAG_OPTIONAL_EL) {
5341                       td => ($token->{tag_name} eq 'th'),                    !!!cp ('t166');
5342                       th => ($token->{tag_name} eq 'td'),                    pop @{$self->{open_elements}};
                      tr => 1,  
                      tbody => 1, tfoot=> 1, thead => 1,  
                     }->{$self->{open_elements}->[-1]->[1]}) {  
                   !!!back-token;  
                   $token = {type => 'end tag',  
                             tag_name => $self->{open_elements}->[-1]->[1]}; # MUST  
                   redo B;  
5343                  }                  }
5344                    
5345                  if ($self->{open_elements}->[-1]->[1] ne $token->{tag_name}) {                  if ($self->{open_elements}->[-1]->[0]->manakai_local_name
5346                    !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);                          ne $token->{tag_name}) {
5347                      !!!cp ('t167');
5348                      !!!parse-error (type => 'not closed',
5349                                      text => $self->{open_elements}->[-1]->[0]
5350                                          ->manakai_local_name,
5351                                      token => $token);
5352                    } else {
5353                      !!!cp ('t168');
5354                  }                  }
5355                                    
5356                  splice @{$self->{open_elements}}, $i;                  splice @{$self->{open_elements}}, $i;
5357                                    
5358                  $clear_up_to_marker->();                  $clear_up_to_marker->();
5359                                    
5360                  $self->{insertion_mode} = 'in row';                  $self->{insertion_mode} = IN_ROW_IM;
5361                                    
5362                  !!!next-token;                  !!!next-token;
5363                  redo B;                  next B;
5364                } elsif ($self->{insertion_mode} eq 'in caption') {                } elsif ($self->{insertion_mode} == IN_CAPTION_IM) {
5365                  !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});                  !!!cp ('t169');
5366                    !!!parse-error (type => 'unmatched end tag',
5367                                    text => $token->{tag_name}, token => $token);
5368                  ## Ignore the token                  ## Ignore the token
5369                  !!!next-token;                  !!!next-token;
5370                  redo B;                  next B;
5371                } else {                } else {
5372                    !!!cp ('t170');
5373                  #                  #
5374                }                }
5375              } elsif ($token->{tag_name} eq 'caption') {              } elsif ($token->{tag_name} eq 'caption') {
5376                if ($self->{insertion_mode} eq 'in caption') {                if ($self->{insertion_mode} == IN_CAPTION_IM) {
5377                  ## have a table element in table scope                  ## have a table element in table scope
5378                  my $i;                  my $i;
5379                  INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {                  INSCOPE: {
5380                    my $node = $self->{open_elements}->[$_];                    for (reverse 0..$#{$self->{open_elements}}) {
5381                    if ($node->[1] eq $token->{tag_name}) {                      my $node = $self->{open_elements}->[$_];
5382                      $i = $_;                      if ($node->[1] & CAPTION_EL) {
5383                      last INSCOPE;                        !!!cp ('t171');
5384                    } elsif ({                        $i = $_;
5385                              table => 1, html => 1,                        last INSCOPE;
5386                             }->{$node->[1]}) {                      } elsif ($node->[1] & TABLE_SCOPING_EL) {
5387                      last INSCOPE;                        !!!cp ('t172');
5388                          last;
5389                        }
5390                    }                    }
5391    
5392                      !!!cp ('t173');
5393                      !!!parse-error (type => 'unmatched end tag',
5394                                      text => $token->{tag_name}, token => $token);
5395                      ## Ignore the token
5396                      !!!next-token;
5397                      next B;
5398                  } # INSCOPE                  } # INSCOPE
                   unless (defined $i) {  
                     !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});  
                     ## Ignore the token  
                     !!!next-token;  
                     redo B;  
                   }  
5399                                    
5400                  ## generate implied end tags                  ## generate implied end tags
5401                  if ({                  while ($self->{open_elements}->[-1]->[1]
5402                       dd => 1, dt => 1, li => 1, p => 1,                             & END_TAG_OPTIONAL_EL) {
5403                       td => 1, th => 1, tr => 1,                    !!!cp ('t174');
5404                       tbody => 1, tfoot=> 1, thead => 1,                    pop @{$self->{open_elements}};
                     }->{$self->{open_elements}->[-1]->[1]}) {  
                   !!!back-token;  
                   $token = {type => 'end tag',  
                             tag_name => $self->{open_elements}->[-1]->[1]}; # MUST  
                   redo B;  
5405                  }                  }
5406                                    
5407                  if ($self->{open_elements}->[-1]->[1] ne 'caption') {                  unless ($self->{open_elements}->[-1]->[1] & CAPTION_EL) {
5408                    !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);                    !!!cp ('t175');
5409                      !!!parse-error (type => 'not closed',
5410                                      text => $self->{open_elements}->[-1]->[0]
5411                                          ->manakai_local_name,
5412                                      token => $token);
5413                    } else {
5414                      !!!cp ('t176');
5415                  }                  }
5416                                    
5417                  splice @{$self->{open_elements}}, $i;                  splice @{$self->{open_elements}}, $i;
5418                                    
5419                  $clear_up_to_marker->();                  $clear_up_to_marker->();
5420                                    
5421                  $self->{insertion_mode} = 'in table';                  $self->{insertion_mode} = IN_TABLE_IM;
5422                                    
5423                  !!!next-token;                  !!!next-token;
5424                  redo B;                  next B;
5425                } elsif ($self->{insertion_mode} eq 'in cell') {                } elsif ($self->{insertion_mode} == IN_CELL_IM) {
5426                  !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});                  !!!cp ('t177');
5427                    !!!parse-error (type => 'unmatched end tag',
5428                                    text => $token->{tag_name}, token => $token);
5429                  ## Ignore the token                  ## Ignore the token
5430                  !!!next-token;                  !!!next-token;
5431                  redo B;                  next B;
5432                } else {                } else {
5433                    !!!cp ('t178');
5434                  #                  #
5435                }                }
5436              } elsif ({              } elsif ({
5437                        table => 1, tbody => 1, tfoot => 1,                        table => 1, tbody => 1, tfoot => 1,
5438                        thead => 1, tr => 1,                        thead => 1, tr => 1,
5439                       }->{$token->{tag_name}} and                       }->{$token->{tag_name}} and
5440                       $self->{insertion_mode} eq 'in cell') {                       $self->{insertion_mode} == IN_CELL_IM) {
5441                ## have an element in table scope                ## have an element in table scope
5442                my $i;                my $i;
5443                my $tn;                my $tn;
5444                INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {                INSCOPE: {
5445                  my $node = $self->{open_elements}->[$_];                  for (reverse 0..$#{$self->{open_elements}}) {
5446                  if ($node->[1] eq $token->{tag_name}) {                    my $node = $self->{open_elements}->[$_];
5447                    $i = $_;                    if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
5448                    last INSCOPE;                      !!!cp ('t179');
5449                  } elsif ($node->[1] eq 'td' or $node->[1] eq 'th') {                      $i = $_;
5450                    $tn = $node->[1];  
5451                    ## NOTE: There is exactly one |td| or |th| element                      ## Close the cell
5452                    ## in scope in the stack of open elements by definition.                      !!!back-token; # </x>
5453                  } elsif ({                      $token = {type => END_TAG_TOKEN, tag_name => $tn,
5454                            table => 1, html => 1,                                line => $token->{line},
5455                           }->{$node->[1]}) {                                column => $token->{column}};
5456                    last INSCOPE;                      next B;
5457                      } elsif ($node->[1] & TABLE_CELL_EL) {
5458                        !!!cp ('t180');
5459                        $tn = $node->[0]->manakai_local_name;
5460                        ## NOTE: There is exactly one |td| or |th| element
5461                        ## in scope in the stack of open elements by definition.
5462                      } elsif ($node->[1] & TABLE_SCOPING_EL) {
5463                        ## ISSUE: Can this be reached?
5464                        !!!cp ('t181');
5465                        last;
5466                      }
5467                  }                  }
5468                } # INSCOPE  
5469                unless (defined $i) {                  !!!cp ('t182');
5470                  !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});                  !!!parse-error (type => 'unmatched end tag',
5471                        text => $token->{tag_name}, token => $token);
5472                  ## Ignore the token                  ## Ignore the token
5473                  !!!next-token;                  !!!next-token;
5474                  redo B;                  next B;
5475                }                } # INSCOPE
   
               ## Close the cell  
               !!!back-token; # </?>  
               $token = {type => 'end tag', tag_name => $tn};  
               redo B;  
5476              } elsif ($token->{tag_name} eq 'table' and              } elsif ($token->{tag_name} eq 'table' and
5477                       $self->{insertion_mode} eq 'in caption') {                       $self->{insertion_mode} == IN_CAPTION_IM) {
5478                !!!parse-error (type => 'not closed:caption');                !!!parse-error (type => 'not closed', text => 'caption',
5479                                  token => $token);
5480    
5481                ## As if </caption>                ## As if </caption>
5482                ## have a table element in table scope                ## have a table element in table scope
5483                my $i;                my $i;
5484                INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {                INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5485                  my $node = $self->{open_elements}->[$_];                  my $node = $self->{open_elements}->[$_];
5486                  if ($node->[1] eq 'caption') {                  if ($node->[1] & CAPTION_EL) {
5487                      !!!cp ('t184');
5488                    $i = $_;                    $i = $_;
5489                    last INSCOPE;                    last INSCOPE;
5490                  } elsif ({                  } elsif ($node->[1] & TABLE_SCOPING_EL) {
5491                            table => 1, html => 1,                    !!!cp ('t185');
                          }->{$node->[1]}) {  
5492                    last INSCOPE;                    last INSCOPE;
5493                  }                  }
5494                } # INSCOPE                } # INSCOPE
5495                unless (defined $i) {                unless (defined $i) {
5496                  !!!parse-error (type => 'unmatched end tag:caption');                  !!!cp ('t186');
5497                    !!!parse-error (type => 'unmatched end tag',
5498                                    text => 'caption', token => $token);
5499                  ## Ignore the token                  ## Ignore the token
5500                  !!!next-token;                  !!!next-token;
5501                  redo B;                  next B;
5502                }                }
5503                                
5504                ## generate implied end tags                ## generate implied end tags
5505                if ({                while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
5506                     dd => 1, dt => 1, li => 1, p => 1,                  !!!cp ('t187');
5507                     td => 1, th => 1, tr => 1,                  pop @{$self->{open_elements}};
                    tbody => 1, tfoot=> 1, thead => 1,  
                   }->{$self->{open_elements}->[-1]->[1]}) {  
                 !!!back-token; # </table>  
                 $token = {type => 'end tag', tag_name => 'caption'};  
                 !!!back-token;  
                 $token = {type => 'end tag',  
                           tag_name => $self->{open_elements}->[-1]->[1]}; # MUST  
                 redo B;  
5508                }                }
5509    
5510                if ($self->{open_elements}->[-1]->[1] ne 'caption') {                unless ($self->{open_elements}->[-1]->[1] & CAPTION_EL) {
5511                  !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);                  !!!cp ('t188');
5512                    !!!parse-error (type => 'not closed',
5513                                    text => $self->{open_elements}->[-1]->[0]
5514                                        ->manakai_local_name,
5515                                    token => $token);
5516                  } else {
5517                    !!!cp ('t189');
5518                }                }
5519    
5520                splice @{$self->{open_elements}}, $i;                splice @{$self->{open_elements}}, $i;
5521    
5522                $clear_up_to_marker->();                $clear_up_to_marker->();
5523    
5524                $self->{insertion_mode} = 'in table';                $self->{insertion_mode} = IN_TABLE_IM;
5525    
5526                ## reprocess                ## reprocess
5527                redo B;                next B;
5528              } elsif ({              } elsif ({
5529                        body => 1, col => 1, colgroup => 1, html => 1,                        body => 1, col => 1, colgroup => 1, html => 1,
5530                       }->{$token->{tag_name}}) {                       }->{$token->{tag_name}}) {
5531                if ($self->{insertion_mode} eq 'in cell' or                if ($self->{insertion_mode} & BODY_TABLE_IMS) {
5532                    $self->{insertion_mode} eq 'in caption') {                  !!!cp ('t190');
5533                  !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});                  !!!parse-error (type => 'unmatched end tag',
5534                                    text => $token->{tag_name}, token => $token);
5535                  ## Ignore the token                  ## Ignore the token
5536                  !!!next-token;                  !!!next-token;
5537                  redo B;                  next B;
5538                } else {                } else {
5539                    !!!cp ('t191');
5540                  #                  #
5541                }                }
5542              } elsif ({              } elsif ({
5543                        tbody => 1, tfoot => 1,                        tbody => 1, tfoot => 1,
5544                        thead => 1, tr => 1,                        thead => 1, tr => 1,
5545                       }->{$token->{tag_name}} and                       }->{$token->{tag_name}} and
5546                       $self->{insertion_mode} eq 'in caption') {                       $self->{insertion_mode} == IN_CAPTION_IM) {
5547                !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});                !!!cp ('t192');
5548                  !!!parse-error (type => 'unmatched end tag',
5549                                  text => $token->{tag_name}, token => $token);
5550                ## Ignore the token                ## Ignore the token
5551                !!!next-token;                !!!next-token;
5552                redo B;                next B;
5553              } else {              } else {
5554                  !!!cp ('t193');
5555                #                #
5556              }              }
5557            } else {        } elsif ($token->{type} == END_OF_FILE_TOKEN) {
5558              #          for my $entry (@{$self->{open_elements}}) {
5559              unless ($entry->[1] & ALL_END_TAG_OPTIONAL_EL) {
5560                !!!cp ('t75');
5561                !!!parse-error (type => 'in body:#eof', token => $token);
5562                last;
5563            }            }
5564                      }
5565            $in_body->($insert_to_current);  
5566            redo B;          ## Stop parsing.
5567          } elsif ($self->{insertion_mode} eq 'in row' or          last B;
5568                   $self->{insertion_mode} eq 'in table body' or        } else {
5569                   $self->{insertion_mode} eq 'in table') {          die "$0: $token->{type}: Unknown token type";
5570            if ($token->{type} eq 'character') {        }
5571              ## NOTE: There are "character in table" code clones.  
5572              if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {        $insert = $insert_to_current;
5573                $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);        #
5574        } elsif ($self->{insertion_mode} & TABLE_IMS) {
5575          if ($token->{type} == CHARACTER_TOKEN) {
5576            if (not $open_tables->[-1]->[1] and # tainted
5577                $token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
5578              $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
5579                                
5580                unless (length $token->{data}) {            unless (length $token->{data}) {
5581                  !!!next-token;              !!!cp ('t194');
5582                  redo B;              !!!next-token;
5583                }              next B;
5584              }            } else {
5585                !!!cp ('t195');
5586              }
5587            }
5588    
5589              !!!parse-error (type => 'in table:#character');          !!!parse-error (type => 'in table:#text', token => $token);
5590    
5591              ## As if in body, but insert into foster parent element          ## NOTE: As if in body, but insert into the foster parent element.
5592              ## ISSUE: Spec says that "whenever a node would be inserted          $reconstruct_active_formatting_elements->($insert_to_foster);
             ## into the current node" while characters might not be  
             ## result in a new Text node.  
             $reconstruct_active_formatting_elements->($insert_to_foster);  
5593                            
5594              if ({          if ($self->{open_elements}->[-1]->[1] & TABLE_ROWS_EL) {
5595                   table => 1, tbody => 1, tfoot => 1,            # MUST
5596                   thead => 1, tr => 1,            my $foster_parent_element;
5597                  }->{$self->{open_elements}->[-1]->[1]}) {            my $next_sibling;
5598                # MUST            my $prev_sibling;
5599                my $foster_parent_element;            OE: for (reverse 0..$#{$self->{open_elements}}) {
5600                my $next_sibling;              if ($self->{open_elements}->[$_]->[1] & TABLE_EL) {
5601                my $prev_sibling;                my $parent = $self->{open_elements}->[$_]->[0]->parent_node;
5602                OE: for (reverse 0..$#{$self->{open_elements}}) {                if (defined $parent and $parent->node_type == 1) {
5603                  if ($self->{open_elements}->[$_]->[1] eq 'table') {                  $foster_parent_element = $parent;
5604                    my $parent = $self->{open_elements}->[$_]->[0]->parent_node;                  !!!cp ('t196');
5605                    if (defined $parent and $parent->node_type == 1) {                  $next_sibling = $self->{open_elements}->[$_]->[0];
5606                      $foster_parent_element = $parent;                  $prev_sibling = $next_sibling->previous_sibling;
5607                      $next_sibling = $self->{open_elements}->[$_]->[0];                  #
                     $prev_sibling = $next_sibling->previous_sibling;  
                   } else {  
                     $foster_parent_element = $self->{open_elements}->[$_ - 1]->[0];  
                     $prev_sibling = $foster_parent_element->last_child;  
                   }  
                   last OE;  
                 }  
               } # OE  
               $foster_parent_element = $self->{open_elements}->[0]->[0] and  
               $prev_sibling = $foster_parent_element->last_child  
                 unless defined $foster_parent_element;  
               if (defined $prev_sibling and  
                   $prev_sibling->node_type == 3) {  
                 $prev_sibling->manakai_append_text ($token->{data});  
5608                } else {                } else {
5609                  $foster_parent_element->insert_before                  !!!cp ('t197');
5610                    ($self->{document}->create_text_node ($token->{data}),                  $foster_parent_element = $self->{open_elements}->[$_ - 1]->[0];
5611                     $next_sibling);                  $prev_sibling = $foster_parent_element->last_child;
5612                    #
5613                }                }
5614              } else {                last OE;
               $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});  
5615              }              }
5616              } # OE
5617              $foster_parent_element = $self->{open_elements}->[0]->[0] and
5618              $prev_sibling = $foster_parent_element->last_child
5619                  unless defined $foster_parent_element;
5620              undef $prev_sibling unless $open_tables->[-1]->[2]; # ~node inserted
5621              if (defined $prev_sibling and
5622                  $prev_sibling->node_type == 3) {
5623                !!!cp ('t198');
5624                $prev_sibling->manakai_append_text ($token->{data});
5625              } else {
5626                !!!cp ('t199');
5627                $foster_parent_element->insert_before
5628                    ($self->{document}->create_text_node ($token->{data}),
5629                     $next_sibling);
5630              }
5631              $open_tables->[-1]->[1] = 1; # tainted
5632              $open_tables->[-1]->[2] = 1; # ~node inserted
5633            } else {
5634              ## NOTE: Fragment case or in a foster parent'ed element
5635              ## (e.g. |<table><span>a|).  In fragment case, whether the
5636              ## character is appended to existing node or a new node is
5637              ## created is irrelevant, since the foster parent'ed nodes
5638              ## are discarded and fragment parsing does not invoke any
5639              ## script.
5640              !!!cp ('t200');
5641              $self->{open_elements}->[-1]->[0]->manakai_append_text
5642                  ($token->{data});
5643            }
5644                            
5645              !!!next-token;          !!!next-token;
5646              redo B;          next B;
5647            } elsif ($token->{type} eq 'start tag') {        } elsif ($token->{type} == START_TAG_TOKEN) {
5648              if ({          if ({
5649                   tr => ($self->{insertion_mode} ne 'in row'),               tr => ($self->{insertion_mode} != IN_ROW_IM),
5650                   th => 1, td => 1,               th => 1, td => 1,
5651                  }->{$token->{tag_name}}) {              }->{$token->{tag_name}}) {
5652                if ($self->{insertion_mode} eq 'in table') {            if ($self->{insertion_mode} == IN_TABLE_IM) {
5653                  ## Clear back to table context              ## Clear back to table context
5654                  while ($self->{open_elements}->[-1]->[1] ne 'table' and              while (not ($self->{open_elements}->[-1]->[1]
5655                         $self->{open_elements}->[-1]->[1] ne 'html') {                              & TABLE_SCOPING_EL)) {
5656                    !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);                !!!cp ('t201');
5657                    pop @{$self->{open_elements}};                pop @{$self->{open_elements}};
5658                  }              }
5659                                
5660                  !!!insert-element ('tbody');              !!!insert-element ('tbody',, $token);
5661                  $self->{insertion_mode} = 'in table body';              $self->{insertion_mode} = IN_TABLE_BODY_IM;
5662                  ## reprocess in the "in table body" insertion mode...              ## reprocess in the "in table body" insertion mode...
5663                }            }
5664              
5665                if ($self->{insertion_mode} eq 'in table body') {            if ($self->{insertion_mode} == IN_TABLE_BODY_IM) {
5666                  unless ($token->{tag_name} eq 'tr') {              unless ($token->{tag_name} eq 'tr') {
5667                    !!!parse-error (type => 'missing start tag:tr');                !!!cp ('t202');
5668                  }                !!!parse-error (type => 'missing start tag:tr', token => $token);
5669                }
5670                                    
5671                  ## Clear back to table body context              ## Clear back to table body context
5672                  while (not {              while (not ($self->{open_elements}->[-1]->[1]
5673                    tbody => 1, tfoot => 1, thead => 1, html => 1,                              & TABLE_ROWS_SCOPING_EL)) {
5674                  }->{$self->{open_elements}->[-1]->[1]}) {                !!!cp ('t203');
5675                    !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);                ## ISSUE: Can this case be reached?
5676                    pop @{$self->{open_elements}};                pop @{$self->{open_elements}};
5677                  }              }
5678                                    
5679                  $self->{insertion_mode} = 'in row';              $self->{insertion_mode} = IN_ROW_IM;
5680                  if ($token->{tag_name} eq 'tr') {              if ($token->{tag_name} eq 'tr') {
5681                    !!!insert-element ($token->{tag_name}, $token->{attributes});                !!!cp ('t204');
5682                    !!!next-token;                !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
5683                    redo B;                $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
5684                  } else {                !!!nack ('t204');
5685                    !!!insert-element ('tr');                !!!next-token;
5686                    ## reprocess in the "in row" insertion mode                next B;
5687                  }              } else {
5688                }                !!!cp ('t205');
5689                  !!!insert-element ('tr',, $token);
5690                  ## reprocess in the "in row" insertion mode
5691                }
5692              } else {
5693                !!!cp ('t206');
5694              }
5695    
5696                ## Clear back to table row context                ## Clear back to table row context
5697                while (not {                while (not ($self->{open_elements}->[-1]->[1]
5698                  tr => 1, html => 1,                                & TABLE_ROW_SCOPING_EL)) {
5699                }->{$self->{open_elements}->[-1]->[1]}) {                  !!!cp ('t207');
                 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);  
5700                  pop @{$self->{open_elements}};                  pop @{$self->{open_elements}};
5701                }                }
5702                                
5703                !!!insert-element ($token->{tag_name}, $token->{attributes});            !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
5704                $self->{insertion_mode} = 'in cell';            $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
5705              $self->{insertion_mode} = IN_CELL_IM;
5706    
5707                push @$active_formatting_elements, ['#marker', ''];            push @$active_formatting_elements, ['#marker', ''];
5708                                
5709              !!!nack ('t207.1');
5710              !!!next-token;
5711              next B;
5712            } elsif ({
5713                      caption => 1, col => 1, colgroup => 1,
5714                      tbody => 1, tfoot => 1, thead => 1,
5715                      tr => 1, # $self->{insertion_mode} == IN_ROW_IM
5716                     }->{$token->{tag_name}}) {
5717              if ($self->{insertion_mode} == IN_ROW_IM) {
5718                ## As if </tr>
5719                ## have an element in table scope
5720                my $i;
5721                INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5722                  my $node = $self->{open_elements}->[$_];
5723                  if ($node->[1] & TABLE_ROW_EL) {
5724                    !!!cp ('t208');
5725                    $i = $_;
5726                    last INSCOPE;
5727                  } elsif ($node->[1] & TABLE_SCOPING_EL) {
5728                    !!!cp ('t209');
5729                    last INSCOPE;
5730                  }
5731                } # INSCOPE
5732                unless (defined $i) {
5733                  !!!cp ('t210');
5734                  ## TODO: This type is wrong.
5735                  !!!parse-error (type => 'unmacthed end tag',
5736                                  text => $token->{tag_name}, token => $token);
5737                  ## Ignore the token
5738                  !!!nack ('t210.1');
5739                !!!next-token;                !!!next-token;
5740                redo B;                next B;
5741              } elsif ({              }
                       caption => 1, col => 1, colgroup => 1,  
                       tbody => 1, tfoot => 1, thead => 1,  
                       tr => 1, # $self->{insertion_mode} eq 'in row'  
                      }->{$token->{tag_name}}) {  
               if ($self->{insertion_mode} eq 'in row') {  
                 ## As if </tr>  
                 ## have an element in table scope  
                 my $i;  
                 INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {  
                   my $node = $self->{open_elements}->[$_];  
                   if ($node->[1] eq 'tr') {  
                     $i = $_;  
                     last INSCOPE;  
                   } elsif ({  
                             table => 1, html => 1,  
                            }->{$node->[1]}) {  
                     last INSCOPE;  
                   }  
                 } # INSCOPE  
                 unless (defined $i) {  
                   !!!parse-error (type => 'unmacthed end tag:'.$token->{tag_name});  
                   ## Ignore the token  
                   !!!next-token;  
                   redo B;  
                 }  
5742                                    
5743                  ## Clear back to table row context                  ## Clear back to table row context
5744                  while (not {                  while (not ($self->{open_elements}->[-1]->[1]
5745                    tr => 1, html => 1,                                  & TABLE_ROW_SCOPING_EL)) {
5746                  }->{$self->{open_elements}->[-1]->[1]}) {                    !!!cp ('t211');
5747                    !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);                    ## ISSUE: Can this case be reached?
5748                    pop @{$self->{open_elements}};                    pop @{$self->{open_elements}};
5749                  }                  }
5750                                    
5751                  pop @{$self->{open_elements}}; # tr                  pop @{$self->{open_elements}}; # tr
5752                  $self->{insertion_mode} = 'in table body';                  $self->{insertion_mode} = IN_TABLE_BODY_IM;
5753                  if ($token->{tag_name} eq 'tr') {                  if ($token->{tag_name} eq 'tr') {
5754                      !!!cp ('t212');
5755                    ## reprocess                    ## reprocess
5756                    redo B;                    !!!ack-later;
5757                      next B;
5758                  } else {                  } else {
5759                      !!!cp ('t213');
5760                    ## reprocess in the "in table body" insertion mode...                    ## reprocess in the "in table body" insertion mode...
5761                  }                  }
5762                }                }
5763    
5764                if ($self->{insertion_mode} eq 'in table body') {                if ($self->{insertion_mode} == IN_TABLE_BODY_IM) {
5765                  ## have an element in table scope                  ## have an element in table scope
5766                  my $i;                  my $i;
5767                  INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {                  INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5768                    my $node = $self->{open_elements}->[$_];                    my $node = $self->{open_elements}->[$_];
5769                    if ({                    if ($node->[1] & TABLE_ROW_GROUP_EL) {
5770                         tbody => 1, thead => 1, tfoot => 1,                      !!!cp ('t214');
                       }->{$node->[1]}) {  
5771                      $i = $_;                      $i = $_;
5772                      last INSCOPE;                      last INSCOPE;
5773                    } elsif ({                    } elsif ($node->[1] & TABLE_SCOPING_EL) {
5774                              table => 1, html => 1,                      !!!cp ('t215');
                            }->{$node->[1]}) {  
5775                      last INSCOPE;                      last INSCOPE;
5776                    }                    }
5777                  } # INSCOPE                  } # INSCOPE
5778                  unless (defined $i) {                  unless (defined $i) {
5779                    !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});                    !!!cp ('t216');
5780    ## TODO: This erorr type is wrong.
5781                      !!!parse-error (type => 'unmatched end tag',
5782                                      text => $token->{tag_name}, token => $token);
5783                    ## Ignore the token                    ## Ignore the token
5784                      !!!nack ('t216.1');
5785                    !!!next-token;                    !!!next-token;
5786                    redo B;                    next B;
5787                  }                  }
5788    
5789                  ## Clear back to table body context                  ## Clear back to table body context
5790                  while (not {                  while (not ($self->{open_elements}->[-1]->[1]
5791                    tbody => 1, tfoot => 1, thead => 1, html => 1,                                  & TABLE_ROWS_SCOPING_EL)) {
5792                  }->{$self->{open_elements}->[-1]->[1]}) {                    !!!cp ('t217');
5793                    !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);                    ## ISSUE: Can this state be reached?
5794                    pop @{$self->{open_elements}};                    pop @{$self->{open_elements}};
5795                  }                  }
5796                                    
# Line 4172  sub _tree_construction_main ($) { Line 5802  sub _tree_construction_main ($) {
5802                  ## nop by definition                  ## nop by definition
5803                                    
5804                  pop @{$self->{open_elements}};                  pop @{$self->{open_elements}};
5805                  $self->{insertion_mode} = 'in table';                  $self->{insertion_mode} = IN_TABLE_IM;
5806                  ## reprocess in "in table" insertion mode...                  ## reprocess in "in table" insertion mode...
5807                  } else {
5808                    !!!cp ('t218');
5809                }                }
5810    
5811                if ($token->{tag_name} eq 'col') {            if ($token->{tag_name} eq 'col') {
5812                  ## Clear back to table context              ## Clear back to table context
5813                  while ($self->{open_elements}->[-1]->[1] ne 'table' and              while (not ($self->{open_elements}->[-1]->[1]
5814                         $self->{open_elements}->[-1]->[1] ne 'html') {                              & TABLE_SCOPING_EL)) {
5815                    !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);                !!!cp ('t219');
5816                    pop @{$self->{open_elements}};                ## ISSUE: Can this state be reached?
5817                  }                pop @{$self->{open_elements}};
5818                                }
5819                  !!!insert-element ('colgroup');              
5820                  $self->{insertion_mode} = 'in column group';              !!!insert-element ('colgroup',, $token);
5821                  ## reprocess              $self->{insertion_mode} = IN_COLUMN_GROUP_IM;
5822                  redo B;              ## reprocess
5823                } elsif ({              $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
5824                          caption => 1,              !!!ack-later;
5825                          colgroup => 1,              next B;
5826                          tbody => 1, tfoot => 1, thead => 1,            } elsif ({
5827                         }->{$token->{tag_name}}) {                      caption => 1,
5828                  ## Clear back to table context                      colgroup => 1,
5829                  while ($self->{open_elements}->[-1]->[1] ne 'table' and                      tbody => 1, tfoot => 1, thead => 1,
5830                         $self->{open_elements}->[-1]->[1] ne 'html') {                     }->{$token->{tag_name}}) {
5831                    !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);              ## Clear back to table context
5832                    while (not ($self->{open_elements}->[-1]->[1]
5833                                    & TABLE_SCOPING_EL)) {
5834                      !!!cp ('t220');
5835                      ## ISSUE: Can this state be reached?
5836                    pop @{$self->{open_elements}};                    pop @{$self->{open_elements}};
5837                  }                  }
5838                                    
5839                  push @$active_formatting_elements, ['#marker', '']              push @$active_formatting_elements, ['#marker', '']
5840                      if $token->{tag_name} eq 'caption';                  if $token->{tag_name} eq 'caption';
5841                                    
5842                  !!!insert-element ($token->{tag_name}, $token->{attributes});              !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
5843                  $self->{insertion_mode} = {              $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
5844                                             caption => 'in caption',              $self->{insertion_mode} = {
5845                                             colgroup => 'in column group',                                         caption => IN_CAPTION_IM,
5846                                             tbody => 'in table body',                                         colgroup => IN_COLUMN_GROUP_IM,
5847                                             tfoot => 'in table body',                                         tbody => IN_TABLE_BODY_IM,
5848                                             thead => 'in table body',                                         tfoot => IN_TABLE_BODY_IM,
5849                                            }->{$token->{tag_name}};                                         thead => IN_TABLE_BODY_IM,
5850                  !!!next-token;                                        }->{$token->{tag_name}};
5851                  redo B;              !!!next-token;
5852                } else {              !!!nack ('t220.1');
5853                  die "$0: in table: <>: $token->{tag_name}";              next B;
5854                }            } else {
5855                die "$0: in table: <>: $token->{tag_name}";
5856              }
5857              } elsif ($token->{tag_name} eq 'table') {              } elsif ($token->{tag_name} eq 'table') {
5858                ## NOTE: There are code clones for this "table in table"                !!!parse-error (type => 'not closed',
5859                !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);                                text => $self->{open_elements}->[-1]->[0]
5860                                      ->manakai_local_name,
5861                                  token => $token);
5862    
5863                ## As if </table>                ## As if </table>
5864                ## have a table element in table scope                ## have a table element in table scope
5865                my $i;                my $i;
5866                INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {                INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5867                  my $node = $self->{open_elements}->[$_];                  my $node = $self->{open_elements}->[$_];
5868                  if ($node->[1] eq 'table') {                  if ($node->[1] & TABLE_EL) {
5869                      !!!cp ('t221');
5870                    $i = $_;                    $i = $_;
5871                    last INSCOPE;                    last INSCOPE;
5872                  } elsif ({                  } elsif ($node->[1] & TABLE_SCOPING_EL) {
5873                            table => 1, html => 1,                    !!!cp ('t222');
                          }->{$node->[1]}) {  
5874                    last INSCOPE;                    last INSCOPE;
5875                  }                  }
5876                } # INSCOPE                } # INSCOPE
5877                unless (defined $i) {                unless (defined $i) {
5878                  !!!parse-error (type => 'unmatched end tag:table');                  !!!cp ('t223');
5879    ## TODO: The following is wrong, maybe.
5880                    !!!parse-error (type => 'unmatched end tag', text => 'table',
5881                                    token => $token);
5882                  ## Ignore tokens </table><table>                  ## Ignore tokens </table><table>
5883                    !!!nack ('t223.1');
5884                  !!!next-token;                  !!!next-token;
5885                  redo B;                  next B;
5886                }                }
5887                                
5888    ## TODO: Followings are removed from the latest spec.
5889                ## generate implied end tags                ## generate implied end tags
5890                if ({                while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
5891                     dd => 1, dt => 1, li => 1, p => 1,                  !!!cp ('t224');
5892                     td => 1, th => 1, tr => 1,                  pop @{$self->{open_elements}};
                    tbody => 1, tfoot=> 1, thead => 1,  
                   }->{$self->{open_elements}->[-1]->[1]}) {  
                 !!!back-token; # <table>  
                 $token = {type => 'end tag', tag_name => 'table'};  
                 !!!back-token;  
                 $token = {type => 'end tag',  
                           tag_name => $self->{open_elements}->[-1]->[1]}; # MUST  
                 redo B;  
5893                }                }
5894    
5895                if ($self->{open_elements}->[-1]->[1] ne 'table') {                unless ($self->{open_elements}->[-1]->[1] & TABLE_EL) {
5896                  !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);                  !!!cp ('t225');
5897                    ## NOTE: |<table><tr><table>|
5898                    !!!parse-error (type => 'not closed',
5899                                    text => $self->{open_elements}->[-1]->[0]
5900                                        ->manakai_local_name,
5901                                    token => $token);
5902                  } else {
5903                    !!!cp ('t226');
5904                }                }
5905    
5906                splice @{$self->{open_elements}}, $i;                splice @{$self->{open_elements}}, $i;
5907                  pop @{$open_tables};
5908    
5909                $self->_reset_insertion_mode;                $self->_reset_insertion_mode;
5910    
5911                ## reprocess            ## reprocess
5912                redo B;            !!!ack-later;
5913              next B;
5914            } elsif ($token->{tag_name} eq 'style') {
5915              if (not $open_tables->[-1]->[1]) { # tainted
5916                !!!cp ('t227.8');
5917                ## NOTE: This is a "as if in head" code clone.
5918                $parse_rcdata->(CDATA_CONTENT_MODEL);
5919                $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
5920                next B;
5921              } else {
5922                !!!cp ('t227.7');
5923                #
5924              }
5925            } elsif ($token->{tag_name} eq 'script') {
5926              if (not $open_tables->[-1]->[1]) { # tainted
5927                !!!cp ('t227.6');
5928                ## NOTE: This is a "as if in head" code clone.
5929                $script_start_tag->();
5930                $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
5931                next B;
5932              } else {
5933                !!!cp ('t227.5');
5934                #
5935              }
5936            } elsif ($token->{tag_name} eq 'input') {
5937              if (not $open_tables->[-1]->[1]) { # tainted
5938                if ($token->{attributes}->{type}) { ## TODO: case
5939                  my $type = lc $token->{attributes}->{type}->{value};
5940                  if ($type eq 'hidden') {
5941                    !!!cp ('t227.3');
5942                    !!!parse-error (type => 'in table',
5943                                    text => $token->{tag_name}, token => $token);
5944    
5945                    !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
5946                    $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
5947    
5948                    ## TODO: form element pointer
5949    
5950                    pop @{$self->{open_elements}};
5951    
5952                    !!!next-token;
5953                    !!!ack ('t227.2.1');
5954                    next B;
5955                  } else {
5956                    !!!cp ('t227.2');
5957                    #
5958                  }
5959              } else {              } else {
5960                  !!!cp ('t227.1');
5961                #                #
5962              }              }
5963            } elsif ($token->{type} eq 'end tag') {            } else {
5964                !!!cp ('t227.4');
5965                #
5966              }
5967            } else {
5968              !!!cp ('t227');
5969              #
5970            }
5971    
5972            !!!parse-error (type => 'in table', text => $token->{tag_name},
5973                            token => $token);
5974    
5975            $insert = $insert_to_foster;
5976            #
5977          } elsif ($token->{type} == END_TAG_TOKEN) {
5978              if ($token->{tag_name} eq 'tr' and              if ($token->{tag_name} eq 'tr' and
5979                  $self->{insertion_mode} eq 'in row') {                  $self->{insertion_mode} == IN_ROW_IM) {
5980                ## have an element in table scope                ## have an element in table scope
5981                my $i;                my $i;
5982                INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {                INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
5983                  my $node = $self->{open_elements}->[$_];                  my $node = $self->{open_elements}->[$_];
5984                  if ($node->[1] eq $token->{tag_name}) {                  if ($node->[1] & TABLE_ROW_EL) {
5985                      !!!cp ('t228');
5986                    $i = $_;                    $i = $_;
5987                    last INSCOPE;                    last INSCOPE;
5988                  } elsif ({                  } elsif ($node->[1] & TABLE_SCOPING_EL) {
5989                            table => 1, html => 1,                    !!!cp ('t229');
                          }->{$node->[1]}) {  
5990                    last INSCOPE;                    last INSCOPE;
5991                  }                  }
5992                } # INSCOPE                } # INSCOPE
5993                unless (defined $i) {                unless (defined $i) {
5994                  !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});                  !!!cp ('t230');
5995                    !!!parse-error (type => 'unmatched end tag',
5996                                    text => $token->{tag_name}, token => $token);
5997                  ## Ignore the token                  ## Ignore the token
5998                    !!!nack ('t230.1');
5999                  !!!next-token;                  !!!next-token;
6000                  redo B;                  next B;
6001                  } else {
6002                    !!!cp ('t232');
6003                }                }
6004    
6005                ## Clear back to table row context                ## Clear back to table row context
6006                while (not {                while (not ($self->{open_elements}->[-1]->[1]
6007                  tr => 1, html => 1,                                & TABLE_ROW_SCOPING_EL)) {
6008                }->{$self->{open_elements}->[-1]->[1]}) {                  !!!cp ('t231');
6009                  !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);  ## ISSUE: Can this state be reached?
6010                  pop @{$self->{open_elements}};                  pop @{$self->{open_elements}};
6011                }                }
6012    
6013                pop @{$self->{open_elements}}; # tr                pop @{$self->{open_elements}}; # tr
6014                $self->{insertion_mode} = 'in table body';                $self->{insertion_mode} = IN_TABLE_BODY_IM;
6015                !!!next-token;                !!!next-token;
6016                redo B;                !!!nack ('t231.1');
6017                  next B;
6018              } elsif ($token->{tag_name} eq 'table') {              } elsif ($token->{tag_name} eq 'table') {
6019                if ($self->{insertion_mode} eq 'in row') {                if ($self->{insertion_mode} == IN_ROW_IM) {
6020                  ## As if </tr>                  ## As if </tr>
6021                  ## have an element in table scope                  ## have an element in table scope
6022                  my $i;                  my $i;
6023                  INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {                  INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6024                    my $node = $self->{open_elements}->[$_];                    my $node = $self->{open_elements}->[$_];
6025                    if ($node->[1] eq 'tr') {                    if ($node->[1] & TABLE_ROW_EL) {
6026                        !!!cp ('t233');
6027                      $i = $_;                      $i = $_;
6028                      last INSCOPE;                      last INSCOPE;
6029                    } elsif ({                    } elsif ($node->[1] & TABLE_SCOPING_EL) {
6030                              table => 1, html => 1,                      !!!cp ('t234');
                            }->{$node->[1]}) {  
6031                      last INSCOPE;                      last INSCOPE;
6032                    }                    }
6033                  } # INSCOPE                  } # INSCOPE
6034                  unless (defined $i) {                  unless (defined $i) {
6035                    !!!parse-error (type => 'unmatched end tag:'.$token->{type});                    !!!cp ('t235');
6036    ## TODO: The following is wrong.
6037                      !!!parse-error (type => 'unmatched end tag',
6038                                      text => $token->{type}, token => $token);
6039                    ## Ignore the token                    ## Ignore the token
6040                      !!!nack ('t236.1');
6041                    !!!next-token;                    !!!next-token;
6042                    redo B;                    next B;
6043                  }                  }
6044                                    
6045                  ## Clear back to table row context                  ## Clear back to table row context
6046                  while (not {                  while (not ($self->{open_elements}->[-1]->[1]
6047                    tr => 1, html => 1,                                  & TABLE_ROW_SCOPING_EL)) {
6048                  }->{$self->{open_elements}->[-1]->[1]}) {                    !!!cp ('t236');
6049                    !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);  ## ISSUE: Can this state be reached?
6050                    pop @{$self->{open_elements}};                    pop @{$self->{open_elements}};
6051                  }                  }
6052                                    
6053                  pop @{$self->{open_elements}}; # tr                  pop @{$self->{open_elements}}; # tr
6054                  $self->{insertion_mode} = 'in table body';                  $self->{insertion_mode} = IN_TABLE_BODY_IM;
6055                  ## reprocess in the "in table body" insertion mode...                  ## reprocess in the "in table body" insertion mode...
6056                }                }
6057    
6058                if ($self->{insertion_mode} eq 'in table body') {                if ($self->{insertion_mode} == IN_TABLE_BODY_IM) {
6059                  ## have an element in table scope                  ## have an element in table scope
6060                  my $i;                  my $i;
6061                  INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {                  INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6062                    my $node = $self->{open_elements}->[$_];                    my $node = $self->{open_elements}->[$_];
6063                    if ({                    if ($node->[1] & TABLE_ROW_GROUP_EL) {
6064                         tbody => 1, thead => 1, tfoot => 1,                      !!!cp ('t237');
                       }->{$node->[1]}) {  
6065                      $i = $_;                      $i = $_;
6066                      last INSCOPE;                      last INSCOPE;
6067                    } elsif ({                    } elsif ($node->[1] & TABLE_SCOPING_EL) {
6068                              table => 1, html => 1,                      !!!cp ('t238');
                            }->{$node->[1]}) {  
6069                      last INSCOPE;                      last INSCOPE;
6070                    }                    }
6071                  } # INSCOPE                  } # INSCOPE
6072                  unless (defined $i) {                  unless (defined $i) {
6073                    !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});                    !!!cp ('t239');
6074                      !!!parse-error (type => 'unmatched end tag',
6075                                      text => $token->{tag_name}, token => $token);
6076                    ## Ignore the token                    ## Ignore the token
6077                      !!!nack ('t239.1');
6078                    !!!next-token;                    !!!next-token;
6079                    redo B;                    next B;
6080                  }                  }
6081                                    
6082                  ## Clear back to table body context                  ## Clear back to table body context
6083                  while (not {                  while (not ($self->{open_elements}->[-1]->[1]
6084                    tbody => 1, tfoot => 1, thead => 1, html => 1,                                  & TABLE_ROWS_SCOPING_EL)) {
6085                  }->{$self->{open_elements}->[-1]->[1]}) {                    !!!cp ('t240');
                   !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);  
6086                    pop @{$self->{open_elements}};                    pop @{$self->{open_elements}};
6087                  }                  }
6088                                    
# Line 4378  sub _tree_construction_main ($) { Line 6094  sub _tree_construction_main ($) {
6094                  ## nop by definition                  ## nop by definition
6095                                    
6096                  pop @{$self->{open_elements}};                  pop @{$self->{open_elements}};
6097                  $self->{insertion_mode} = 'in table';                  $self->{insertion_mode} = IN_TABLE_IM;
6098                  ## reprocess in the "in table" insertion mode...                  ## reprocess in the "in table" insertion mode...
6099                }                }
6100    
6101                  ## NOTE: </table> in the "in table" insertion mode.
6102                  ## When you edit the code fragment below, please ensure that
6103                  ## the code for <table> in the "in table" insertion mode
6104                  ## is synced with it.
6105    
6106                ## have a table element in table scope                ## have a table element in table scope
6107                my $i;                my $i;
6108                INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {                INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6109                  my $node = $self->{open_elements}->[$_];                  my $node = $self->{open_elements}->[$_];
6110                  if ($node->[1] eq $token->{tag_name}) {                  if ($node->[1] & TABLE_EL) {
6111                      !!!cp ('t241');
6112                    $i = $_;                    $i = $_;
6113                    last INSCOPE;                    last INSCOPE;
6114                  } elsif ({                  } elsif ($node->[1] & TABLE_SCOPING_EL) {
6115                            table => 1, html => 1,                    !!!cp ('t242');
                          }->{$node->[1]}) {  
6116                    last INSCOPE;                    last INSCOPE;
6117                  }                  }
6118                } # INSCOPE                } # INSCOPE
6119                unless (defined $i) {                unless (defined $i) {
6120                  !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});                  !!!cp ('t243');
6121                    !!!parse-error (type => 'unmatched end tag',
6122                                    text => $token->{tag_name}, token => $token);
6123                  ## Ignore the token                  ## Ignore the token
6124                    !!!nack ('t243.1');
6125                  !!!next-token;                  !!!next-token;
6126                  redo B;                  next B;
               }  
   
               ## generate implied end tags  
               if ({  
                    dd => 1, dt => 1, li => 1, p => 1,  
                    td => 1, th => 1, tr => 1,  
                    tbody => 1, tfoot=> 1, thead => 1,  
                   }->{$self->{open_elements}->[-1]->[1]}) {  
                 !!!back-token;  
                 $token = {type => 'end tag',  
                           tag_name => $self->{open_elements}->[-1]->[1]}; # MUST  
                 redo B;  
               }  
                 
               if ($self->{open_elements}->[-1]->[1] ne 'table') {  
                 !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);  
6127                }                }
6128                                    
6129                splice @{$self->{open_elements}}, $i;                splice @{$self->{open_elements}}, $i;
6130                  pop @{$open_tables};
6131                                
6132                $self->_reset_insertion_mode;                $self->_reset_insertion_mode;
6133                                
6134                !!!next-token;                !!!next-token;
6135                redo B;                next B;
6136              } elsif ({              } elsif ({
6137                        tbody => 1, tfoot => 1, thead => 1,                        tbody => 1, tfoot => 1, thead => 1,
6138                       }->{$token->{tag_name}} and                       }->{$token->{tag_name}} and
6139                       ($self->{insertion_mode} eq 'in row' or                       $self->{insertion_mode} & ROW_IMS) {
6140                        $self->{insertion_mode} eq 'in table body')) {                if ($self->{insertion_mode} == IN_ROW_IM) {
               if ($self->{insertion_mode} eq 'in row') {  
6141                  ## have an element in table scope                  ## have an element in table scope
6142                  my $i;                  my $i;
6143                  INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {                  INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6144                    my $node = $self->{open_elements}->[$_];                    my $node = $self->{open_elements}->[$_];
6145                    if ($node->[1] eq $token->{tag_name}) {                    if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
6146                        !!!cp ('t247');
6147                      $i = $_;                      $i = $_;
6148                      last INSCOPE;                      last INSCOPE;
6149                    } elsif ({                    } elsif ($node->[1] & TABLE_SCOPING_EL) {
6150                              table => 1, html => 1,                      !!!cp ('t248');
                            }->{$node->[1]}) {  
6151                      last INSCOPE;                      last INSCOPE;
6152                    }                    }
6153                  } # INSCOPE                  } # INSCOPE
6154                    unless (defined $i) {                    unless (defined $i) {
6155                      !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});                      !!!cp ('t249');
6156                        !!!parse-error (type => 'unmatched end tag',
6157                                        text => $token->{tag_name}, token => $token);
6158                      ## Ignore the token                      ## Ignore the token
6159                        !!!nack ('t249.1');
6160                      !!!next-token;                      !!!next-token;
6161                      redo B;                      next B;
6162                    }                    }
6163                                    
6164                  ## As if </tr>                  ## As if </tr>
# Line 4455  sub _tree_construction_main ($) { Line 6166  sub _tree_construction_main ($) {
6166                  my $i;                  my $i;
6167                  INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {                  INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6168                    my $node = $self->{open_elements}->[$_];                    my $node = $self->{open_elements}->[$_];
6169                    if ($node->[1] eq 'tr') {                    if ($node->[1] & TABLE_ROW_EL) {
6170                        !!!cp ('t250');
6171                      $i = $_;                      $i = $_;
6172                      last INSCOPE;                      last INSCOPE;
6173                    } elsif ({                    } elsif ($node->[1] & TABLE_SCOPING_EL) {
6174                              table => 1, html => 1,                      !!!cp ('t251');
                            }->{$node->[1]}) {  
6175                      last INSCOPE;                      last INSCOPE;
6176                    }                    }
6177                  } # INSCOPE                  } # INSCOPE
6178                    unless (defined $i) {                    unless (defined $i) {
6179                      !!!parse-error (type => 'unmatched end tag:tr');                      !!!cp ('t252');
6180                        !!!parse-error (type => 'unmatched end tag',
6181                                        text => 'tr', token => $token);
6182                      ## Ignore the token                      ## Ignore the token
6183                        !!!nack ('t252.1');
6184                      !!!next-token;                      !!!next-token;
6185                      redo B;                      next B;
6186                    }                    }
6187                                    
6188                  ## Clear back to table row context                  ## Clear back to table row context
6189                  while (not {                  while (not ($self->{open_elements}->[-1]->[1]
6190                    tr => 1, html => 1,                                  & TABLE_ROW_SCOPING_EL)) {
6191                  }->{$self->{open_elements}->[-1]->[1]}) {                    !!!cp ('t253');
6192                    !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);  ## ISSUE: Can this case be reached?
6193                    pop @{$self->{open_elements}};                    pop @{$self->{open_elements}};
6194                  }                  }
6195                                    
6196                  pop @{$self->{open_elements}}; # tr                  pop @{$self->{open_elements}}; # tr
6197                  $self->{insertion_mode} = 'in table body';                  $self->{insertion_mode} = IN_TABLE_BODY_IM;
6198                  ## reprocess in the "in table body" insertion mode...                  ## reprocess in the "in table body" insertion mode...
6199                }                }
6200    
# Line 4488  sub _tree_construction_main ($) { Line 6202  sub _tree_construction_main ($) {
6202                my $i;                my $i;
6203                INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {                INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6204                  my $node = $self->{open_elements}->[$_];                  my $node = $self->{open_elements}->[$_];
6205                  if ($node->[1] eq $token->{tag_name}) {                  if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
6206                      !!!cp ('t254');
6207                    $i = $_;                    $i = $_;
6208                    last INSCOPE;                    last INSCOPE;
6209                  } elsif ({                  } elsif ($node->[1] & TABLE_SCOPING_EL) {
6210                            table => 1, html => 1,                    !!!cp ('t255');
                          }->{$node->[1]}) {  
6211                    last INSCOPE;                    last INSCOPE;
6212                  }                  }
6213                } # INSCOPE                } # INSCOPE
6214                unless (defined $i) {                unless (defined $i) {
6215                  !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});                  !!!cp ('t256');
6216                    !!!parse-error (type => 'unmatched end tag',
6217                                    text => $token->{tag_name}, token => $token);
6218                  ## Ignore the token                  ## Ignore the token
6219                    !!!nack ('t256.1');
6220                  !!!next-token;                  !!!next-token;
6221                  redo B;                  next B;
6222                }                }
6223    
6224                ## Clear back to table body context                ## Clear back to table body context
6225                while (not {                while (not ($self->{open_elements}->[-1]->[1]
6226                  tbody => 1, tfoot => 1, thead => 1, html => 1,                                & TABLE_ROWS_SCOPING_EL)) {
6227                }->{$self->{open_elements}->[-1]->[1]}) {                  !!!cp ('t257');
6228                  !!!parse-error (type => 'not closed:'.$self->{open_elements}->[-1]->[1]);  ## ISSUE: Can this case be reached?
6229                  pop @{$self->{open_elements}};                  pop @{$self->{open_elements}};
6230                }                }
6231    
6232                pop @{$self->{open_elements}};                pop @{$self->{open_elements}};
6233                $self->{insertion_mode} = 'in table';                $self->{insertion_mode} = IN_TABLE_IM;
6234                  !!!nack ('t257.1');
6235                !!!next-token;                !!!next-token;
6236                redo B;                next B;
6237              } elsif ({              } elsif ({
6238                        body => 1, caption => 1, col => 1, colgroup => 1,                        body => 1, caption => 1, col => 1, colgroup => 1,
6239                        html => 1, td => 1, th => 1,                        html => 1, td => 1, th => 1,
6240                        tr => 1, # $self->{insertion_mode} eq 'in row'                        tr => 1, # $self->{insertion_mode} == IN_ROW_IM
6241                        tbody => 1, tfoot => 1, thead => 1, # $self->{insertion_mode} eq 'in table'                        tbody => 1, tfoot => 1, thead => 1, # $self->{insertion_mode} == IN_TABLE_IM
6242                       }->{$token->{tag_name}}) {                       }->{$token->{tag_name}}) {
6243                !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});            !!!cp ('t258');
6244                ## Ignore the token            !!!parse-error (type => 'unmatched end tag',
6245                !!!next-token;                            text => $token->{tag_name}, token => $token);
6246                redo B;            ## Ignore the token
6247              } else {            !!!nack ('t258.1');
6248                #             !!!next-token;
6249              }            next B;
6250            } else {          } else {
6251              die "$0: $token->{type}: Unknown token type";            !!!cp ('t259');
6252            }            !!!parse-error (type => 'in table:/',
6253                              text => $token->{tag_name}, token => $token);
6254    
6255              $insert = $insert_to_foster;
6256              #
6257            }
6258          } elsif ($token->{type} == END_OF_FILE_TOKEN) {
6259            unless ($self->{open_elements}->[-1]->[1] & HTML_EL and
6260                    @{$self->{open_elements}} == 1) { # redundant, maybe
6261              !!!parse-error (type => 'in body:#eof', token => $token);
6262              !!!cp ('t259.1');
6263              #
6264            } else {
6265              !!!cp ('t259.2');
6266              #
6267            }
6268    
6269            !!!parse-error (type => 'in table:'.$token->{tag_name});          ## Stop parsing
6270            $in_body->($insert_to_foster);          last B;
6271            redo B;        } else {
6272          } elsif ($self->{insertion_mode} eq 'in column group') {          die "$0: $token->{type}: Unknown token type";
6273            if ($token->{type} eq 'character') {        }
6274              if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {      } elsif ($self->{insertion_mode} == IN_COLUMN_GROUP_IM) {
6275              if ($token->{type} == CHARACTER_TOKEN) {
6276                if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
6277                $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);                $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
6278                unless (length $token->{data}) {                unless (length $token->{data}) {
6279                    !!!cp ('t260');
6280                  !!!next-token;                  !!!next-token;
6281                  redo B;                  next B;
6282                }                }
6283              }              }
6284                            
6285                !!!cp ('t261');
6286              #              #
6287            } elsif ($token->{type} eq 'start tag') {            } elsif ($token->{type} == START_TAG_TOKEN) {
6288              if ($token->{tag_name} eq 'col') {              if ($token->{tag_name} eq 'col') {
6289                !!!insert-element ($token->{tag_name}, $token->{attributes});                !!!cp ('t262');
6290                  !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
6291                pop @{$self->{open_elements}};                pop @{$self->{open_elements}};
6292                  !!!ack ('t262.1');
6293                !!!next-token;                !!!next-token;
6294                redo B;                next B;
6295              } else {              } else {
6296                  !!!cp ('t263');
6297                #                #
6298              }              }
6299            } elsif ($token->{type} eq 'end tag') {            } elsif ($token->{type} == END_TAG_TOKEN) {
6300              if ($token->{tag_name} eq 'colgroup') {              if ($token->{tag_name} eq 'colgroup') {
6301                if ($self->{open_elements}->[-1]->[1] eq 'html') {                if ($self->{open_elements}->[-1]->[1] & HTML_EL) {
6302                  !!!parse-error (type => 'unmatched end tag:colgroup');                  !!!cp ('t264');
6303                    !!!parse-error (type => 'unmatched end tag',
6304                                    text => 'colgroup', token => $token);
6305                  ## Ignore the token                  ## Ignore the token
6306                  !!!next-token;                  !!!next-token;
6307                  redo B;                  next B;
6308                } else {                } else {
6309                    !!!cp ('t265');
6310                  pop @{$self->{open_elements}}; # colgroup                  pop @{$self->{open_elements}}; # colgroup
6311                  $self->{insertion_mode} = 'in table';                  $self->{insertion_mode} = IN_TABLE_IM;
6312                  !!!next-token;                  !!!next-token;
6313                  redo B;                              next B;            
6314                }                }
6315              } elsif ($token->{tag_name} eq 'col') {              } elsif ($token->{tag_name} eq 'col') {
6316                !!!parse-error (type => 'unmatched end tag:col');                !!!cp ('t266');
6317                  !!!parse-error (type => 'unmatched end tag',
6318                                  text => 'col', token => $token);
6319                ## Ignore the token                ## Ignore the token
6320                !!!next-token;                !!!next-token;
6321                redo B;                next B;
6322              } else {              } else {
6323                  !!!cp ('t267');
6324                #                #
6325              }              }
6326            } else {        } elsif ($token->{type} == END_OF_FILE_TOKEN) {
6327              #          if ($self->{open_elements}->[-1]->[1] & HTML_EL and
6328            }              @{$self->{open_elements}} == 1) { # redundant, maybe
6329              !!!cp ('t270.2');
6330              ## Stop parsing.
6331              last B;
6332            } else {
6333              ## NOTE: As if </colgroup>.
6334              !!!cp ('t270.1');
6335              pop @{$self->{open_elements}}; # colgroup
6336              $self->{insertion_mode} = IN_TABLE_IM;
6337              ## Reprocess.
6338              next B;
6339            }
6340          } else {
6341            die "$0: $token->{type}: Unknown token type";
6342          }
6343    
6344            ## As if </colgroup>            ## As if </colgroup>
6345            if ($self->{open_elements}->[-1]->[1] eq 'html') {            if ($self->{open_elements}->[-1]->[1] & HTML_EL) {
6346              !!!parse-error (type => 'unmatched end tag:colgroup');              !!!cp ('t269');
6347    ## TODO: Wrong error type?
6348                !!!parse-error (type => 'unmatched end tag',
6349                                text => 'colgroup', token => $token);
6350              ## Ignore the token              ## Ignore the token
6351                !!!nack ('t269.1');
6352              !!!next-token;              !!!next-token;
6353              redo B;              next B;
6354            } else {            } else {
6355                !!!cp ('t270');
6356              pop @{$self->{open_elements}}; # colgroup              pop @{$self->{open_elements}}; # colgroup
6357              $self->{insertion_mode} = 'in table';              $self->{insertion_mode} = IN_TABLE_IM;
6358                !!!ack-later;
6359              ## reprocess              ## reprocess
6360              redo B;              next B;
6361              }
6362        } elsif ($self->{insertion_mode} & SELECT_IMS) {
6363          if ($token->{type} == CHARACTER_TOKEN) {
6364            !!!cp ('t271');
6365            $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
6366            !!!next-token;
6367            next B;
6368          } elsif ($token->{type} == START_TAG_TOKEN) {
6369            if ($token->{tag_name} eq 'option') {
6370              if ($self->{open_elements}->[-1]->[1] & OPTION_EL) {
6371                !!!cp ('t272');
6372                ## As if </option>
6373                pop @{$self->{open_elements}};
6374              } else {
6375                !!!cp ('t273');
6376            }            }
         } elsif ($self->{insertion_mode} eq 'in select') {  
           if ($token->{type} eq 'character') {  
             $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});  
             !!!next-token;  
             redo B;  
           } elsif ($token->{type} eq 'start tag') {  
             if ($token->{tag_name} eq 'option') {  
               if ($self->{open_elements}->[-1]->[1] eq 'option') {  
                 ## As if </option>  
                 pop @{$self->{open_elements}};  
               }  
6377    
6378                !!!insert-element ($token->{tag_name}, $token->{attributes});            !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
6379                !!!next-token;            !!!nack ('t273.1');
6380                redo B;            !!!next-token;
6381              } elsif ($token->{tag_name} eq 'optgroup') {            next B;
6382                if ($self->{open_elements}->[-1]->[1] eq 'option') {          } elsif ($token->{tag_name} eq 'optgroup') {
6383                  ## As if </option>            if ($self->{open_elements}->[-1]->[1] & OPTION_EL) {
6384                  pop @{$self->{open_elements}};              !!!cp ('t274');
6385                }              ## As if </option>
6386                pop @{$self->{open_elements}};
6387              } else {
6388                !!!cp ('t275');
6389              }
6390    
6391                if ($self->{open_elements}->[-1]->[1] eq 'optgroup') {            if ($self->{open_elements}->[-1]->[1] & OPTGROUP_EL) {
6392                  ## As if </optgroup>              !!!cp ('t276');
6393                  pop @{$self->{open_elements}};              ## As if </optgroup>
6394                }              pop @{$self->{open_elements}};
6395              } else {
6396                !!!cp ('t277');
6397              }
6398    
6399                !!!insert-element ($token->{tag_name}, $token->{attributes});            !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
6400                !!!next-token;            !!!nack ('t277.1');
6401                redo B;            !!!next-token;
6402              } elsif ($token->{tag_name} eq 'select') {            next B;
6403                !!!parse-error (type => 'not closed:select');          } elsif ({
6404                ## As if </select> instead                     select => 1, input => 1, textarea => 1,
6405                ## have an element in table scope                   }->{$token->{tag_name}} or
6406                my $i;                   ($self->{insertion_mode} == IN_SELECT_IN_TABLE_IM and
6407                INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {                    {
6408                  my $node = $self->{open_elements}->[$_];                     caption => 1, table => 1,
6409                  if ($node->[1] eq $token->{tag_name}) {                     tbody => 1, tfoot => 1, thead => 1,
6410                    $i = $_;                     tr => 1, td => 1, th => 1,
6411                    last INSCOPE;                    }->{$token->{tag_name}})) {
6412                  } elsif ({            ## TODO: The type below is not good - <select> is replaced by </select>
6413                            table => 1, html => 1,            !!!parse-error (type => 'not closed', text => 'select',
6414                           }->{$node->[1]}) {                            token => $token);
6415                    last INSCOPE;            ## NOTE: As if the token were </select> (<select> case) or
6416                  }            ## as if there were </select> (otherwise).
6417                } # INSCOPE            ## have an element in table scope
6418                unless (defined $i) {            my $i;
6419                  !!!parse-error (type => 'unmatched end tag:select');            INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6420                  ## Ignore the token              my $node = $self->{open_elements}->[$_];
6421                  !!!next-token;              if ($node->[1] & SELECT_EL) {
6422                  redo B;                !!!cp ('t278');
6423                }                $i = $_;
6424                  last INSCOPE;
6425                } elsif ($node->[1] & TABLE_SCOPING_EL) {
6426                  !!!cp ('t279');
6427                  last INSCOPE;
6428                }
6429              } # INSCOPE
6430              unless (defined $i) {
6431                !!!cp ('t280');
6432                !!!parse-error (type => 'unmatched end tag',
6433                                text => 'select', token => $token);
6434                ## Ignore the token
6435                !!!nack ('t280.1');
6436                !!!next-token;
6437                next B;
6438              }
6439                                
6440                splice @{$self->{open_elements}}, $i;            !!!cp ('t281');
6441              splice @{$self->{open_elements}}, $i;
6442    
6443                $self->_reset_insertion_mode;            $self->_reset_insertion_mode;
6444    
6445                !!!next-token;            if ($token->{tag_name} eq 'select') {
6446                redo B;              !!!nack ('t281.2');
6447              } else {              !!!next-token;
6448                #              next B;
6449              } else {
6450                !!!cp ('t281.1');
6451                !!!ack-later;
6452                ## Reprocess the token.
6453                next B;
6454              }
6455            } else {
6456              !!!cp ('t282');
6457              !!!parse-error (type => 'in select',
6458                              text => $token->{tag_name}, token => $token);
6459              ## Ignore the token
6460              !!!nack ('t282.1');
6461              !!!next-token;
6462              next B;
6463            }
6464          } elsif ($token->{type} == END_TAG_TOKEN) {
6465            if ($token->{tag_name} eq 'optgroup') {
6466              if ($self->{open_elements}->[-1]->[1] & OPTION_EL and
6467                  $self->{open_elements}->[-2]->[1] & OPTGROUP_EL) {
6468                !!!cp ('t283');
6469                ## As if </option>
6470                splice @{$self->{open_elements}}, -2;
6471              } elsif ($self->{open_elements}->[-1]->[1] & OPTGROUP_EL) {
6472                !!!cp ('t284');
6473                pop @{$self->{open_elements}};
6474              } else {
6475                !!!cp ('t285');
6476                !!!parse-error (type => 'unmatched end tag',
6477                                text => $token->{tag_name}, token => $token);
6478                ## Ignore the token
6479              }
6480              !!!nack ('t285.1');
6481              !!!next-token;
6482              next B;
6483            } elsif ($token->{tag_name} eq 'option') {
6484              if ($self->{open_elements}->[-1]->[1] & OPTION_EL) {
6485                !!!cp ('t286');
6486                pop @{$self->{open_elements}};
6487              } else {
6488                !!!cp ('t287');
6489                !!!parse-error (type => 'unmatched end tag',
6490                                text => $token->{tag_name}, token => $token);
6491                ## Ignore the token
6492              }
6493              !!!nack ('t287.1');
6494              !!!next-token;
6495              next B;
6496            } elsif ($token->{tag_name} eq 'select') {
6497              ## have an element in table scope
6498              my $i;
6499              INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6500                my $node = $self->{open_elements}->[$_];
6501                if ($node->[1] & SELECT_EL) {
6502                  !!!cp ('t288');
6503                  $i = $_;
6504                  last INSCOPE;
6505                } elsif ($node->[1] & TABLE_SCOPING_EL) {
6506                  !!!cp ('t289');
6507                  last INSCOPE;
6508              }              }
6509            } elsif ($token->{type} eq 'end tag') {            } # INSCOPE
6510              if ($token->{tag_name} eq 'optgroup') {            unless (defined $i) {
6511                if ($self->{open_elements}->[-1]->[1] eq 'option' and              !!!cp ('t290');
6512                    $self->{open_elements}->[-2]->[1] eq 'optgroup') {              !!!parse-error (type => 'unmatched end tag',
6513                  ## As if </option>                              text => $token->{tag_name}, token => $token);
6514                  splice @{$self->{open_elements}}, -2;              ## Ignore the token
6515                } elsif ($self->{open_elements}->[-1]->[1] eq 'optgroup') {              !!!nack ('t290.1');
6516                  pop @{$self->{open_elements}};              !!!next-token;
6517                } else {              next B;
6518                  !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});            }
                 ## Ignore the token  
               }  
               !!!next-token;  
               redo B;  
             } elsif ($token->{tag_name} eq 'option') {  
               if ($self->{open_elements}->[-1]->[1] eq 'option') {  
                 pop @{$self->{open_elements}};  
               } else {  
                 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});  
                 ## Ignore the token  
               }  
               !!!next-token;  
               redo B;  
             } elsif ($token->{tag_name} eq 'select') {  
               ## have an element in table scope  
               my $i;  
               INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {  
                 my $node = $self->{open_elements}->[$_];  
                 if ($node->[1] eq $token->{tag_name}) {  
                   $i = $_;  
                   last INSCOPE;  
                 } elsif ({  
                           table => 1, html => 1,  
                          }->{$node->[1]}) {  
                   last INSCOPE;  
                 }  
               } # INSCOPE  
               unless (defined $i) {  
                 !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});  
                 ## Ignore the token  
                 !!!next-token;  
                 redo B;  
               }  
6519                                
6520                splice @{$self->{open_elements}}, $i;            !!!cp ('t291');
6521              splice @{$self->{open_elements}}, $i;
6522    
6523                $self->_reset_insertion_mode;            $self->_reset_insertion_mode;
6524    
6525                !!!next-token;            !!!nack ('t291.1');
6526                redo B;            !!!next-token;
6527              } elsif ({            next B;
6528                        caption => 1, table => 1, tbody => 1,          } elsif ($self->{insertion_mode} == IN_SELECT_IN_TABLE_IM and
6529                        tfoot => 1, thead => 1, tr => 1, td => 1, th => 1,                   {
6530                       }->{$token->{tag_name}}) {                    caption => 1, table => 1, tbody => 1,
6531                !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});                    tfoot => 1, thead => 1, tr => 1, td => 1, th => 1,
6532                                   }->{$token->{tag_name}}) {
6533                ## have an element in table scope  ## TODO: The following is wrong?
6534                my $i;            !!!parse-error (type => 'unmatched end tag',
6535                INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {                            text => $token->{tag_name}, token => $token);
                 my $node = $self->{open_elements}->[$_];  
                 if ($node->[1] eq $token->{tag_name}) {  
                   $i = $_;  
                   last INSCOPE;  
                 } elsif ({  
                           table => 1, html => 1,  
                          }->{$node->[1]}) {  
                   last INSCOPE;  
                 }  
               } # INSCOPE  
               unless (defined $i) {  
                 ## Ignore the token  
                 !!!next-token;  
                 redo B;  
               }  
6536                                
6537                ## As if </select>            ## have an element in table scope
6538                ## have an element in table scope            my $i;
6539                undef $i;            INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6540                INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {              my $node = $self->{open_elements}->[$_];
6541                  my $node = $self->{open_elements}->[$_];              if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
6542                  if ($node->[1] eq 'select') {                !!!cp ('t292');
6543                    $i = $_;                $i = $_;
6544                    last INSCOPE;                last INSCOPE;
6545                  } elsif ({              } elsif ($node->[1] & TABLE_SCOPING_EL) {
6546                            table => 1, html => 1,                !!!cp ('t293');
6547                           }->{$node->[1]}) {                last INSCOPE;
6548                    last INSCOPE;              }
6549                  }            } # INSCOPE
6550                } # INSCOPE            unless (defined $i) {
6551                unless (defined $i) {              !!!cp ('t294');
6552                  !!!parse-error (type => 'unmatched end tag:select');              ## Ignore the token
6553                  ## Ignore the </select> token              !!!nack ('t294.1');
6554                  !!!next-token; ## TODO: ok?              !!!next-token;
6555                  redo B;              next B;
6556                }            }
6557                                
6558                splice @{$self->{open_elements}}, $i;            ## As if </select>
6559              ## have an element in table scope
6560                $self->_reset_insertion_mode;            undef $i;
6561              INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
6562                ## reprocess              my $node = $self->{open_elements}->[$_];
6563                redo B;              if ($node->[1] & SELECT_EL) {
6564              } else {                !!!cp ('t295');
6565                #                $i = $_;
6566                  last INSCOPE;
6567                } elsif ($node->[1] & TABLE_SCOPING_EL) {
6568    ## ISSUE: Can this state be reached?
6569                  !!!cp ('t296');
6570                  last INSCOPE;
6571              }              }
6572            } else {            } # INSCOPE
6573              #            unless (defined $i) {
6574                !!!cp ('t297');
6575    ## TODO: The following error type is correct?
6576                !!!parse-error (type => 'unmatched end tag',
6577                                text => 'select', token => $token);
6578                ## Ignore the </select> token
6579                !!!nack ('t297.1');
6580                !!!next-token; ## TODO: ok?
6581                next B;
6582            }            }
6583                  
6584              !!!cp ('t298');
6585              splice @{$self->{open_elements}}, $i;
6586    
6587              $self->_reset_insertion_mode;
6588    
6589            !!!parse-error (type => 'in select:'.$token->{tag_name});            !!!ack-later;
6590              ## reprocess
6591              next B;
6592            } else {
6593              !!!cp ('t299');
6594              !!!parse-error (type => 'in select:/',
6595                              text => $token->{tag_name}, token => $token);
6596            ## Ignore the token            ## Ignore the token
6597              !!!nack ('t299.3');
6598            !!!next-token;            !!!next-token;
6599            redo B;            next B;
6600          } elsif ($self->{insertion_mode} eq 'after body') {          }
6601            if ($token->{type} eq 'character') {        } elsif ($token->{type} == END_OF_FILE_TOKEN) {
6602              if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {          unless ($self->{open_elements}->[-1]->[1] & HTML_EL and
6603                my $data = $1;                  @{$self->{open_elements}} == 1) { # redundant, maybe
6604                ## As if in body            !!!cp ('t299.1');
6605                $reconstruct_active_formatting_elements->($insert_to_current);            !!!parse-error (type => 'in body:#eof', token => $token);
6606            } else {
6607              !!!cp ('t299.2');
6608            }
6609    
6610            ## Stop parsing.
6611            last B;
6612          } else {
6613            die "$0: $token->{type}: Unknown token type";
6614          }
6615        } elsif ($self->{insertion_mode} & BODY_AFTER_IMS) {
6616          if ($token->{type} == CHARACTER_TOKEN) {
6617            if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
6618              my $data = $1;
6619              ## As if in body
6620              $reconstruct_active_formatting_elements->($insert_to_current);
6621                                
6622                $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);            $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
6623              
6624              unless (length $token->{data}) {
6625                !!!cp ('t300');
6626                !!!next-token;
6627                next B;
6628              }
6629            }
6630            
6631            if ($self->{insertion_mode} == AFTER_HTML_BODY_IM) {
6632              !!!cp ('t301');
6633              !!!parse-error (type => 'after html:#text', token => $token);
6634              #
6635            } else {
6636              !!!cp ('t302');
6637              ## "after body" insertion mode
6638              !!!parse-error (type => 'after body:#text', token => $token);
6639              #
6640            }
6641    
6642                unless (length $token->{data}) {          $self->{insertion_mode} = IN_BODY_IM;
6643                  !!!next-token;          ## reprocess
6644                  redo B;          next B;
6645                }        } elsif ($token->{type} == START_TAG_TOKEN) {
6646              }          if ($self->{insertion_mode} == AFTER_HTML_BODY_IM) {
6647                          !!!cp ('t303');
6648              #            !!!parse-error (type => 'after html',
6649              !!!parse-error (type => 'after body:#character');                            text => $token->{tag_name}, token => $token);
6650            } elsif ($token->{type} eq 'start tag') {            #
6651              !!!parse-error (type => 'after body:'.$token->{tag_name});          } else {
6652              #            !!!cp ('t304');
6653            } elsif ($token->{type} eq 'end tag') {            ## "after body" insertion mode
6654              if ($token->{tag_name} eq 'html') {            !!!parse-error (type => 'after body',
6655                if (defined $self->{inner_html_node}) {                            text => $token->{tag_name}, token => $token);
6656                  !!!parse-error (type => 'unmatched end tag:html');            #
6657                  ## Ignore the token          }
6658                  !!!next-token;  
6659                  redo B;          $self->{insertion_mode} = IN_BODY_IM;
6660                } else {          !!!ack-later;
6661                  $previous_insertion_mode = $self->{insertion_mode};          ## reprocess
6662                  $self->{insertion_mode} = 'trailing end';          next B;
6663                  !!!next-token;        } elsif ($token->{type} == END_TAG_TOKEN) {
6664                  redo B;          if ($self->{insertion_mode} == AFTER_HTML_BODY_IM) {
6665                }            !!!cp ('t305');
6666              } else {            !!!parse-error (type => 'after html:/',
6667                !!!parse-error (type => 'after body:/'.$token->{tag_name});                            text => $token->{tag_name}, token => $token);
6668              }            
6669              $self->{insertion_mode} = IN_BODY_IM;
6670              ## Reprocess.
6671              next B;
6672            } else {
6673              !!!cp ('t306');
6674            }
6675    
6676            ## "after body" insertion mode
6677            if ($token->{tag_name} eq 'html') {
6678              if (defined $self->{inner_html_node}) {
6679                !!!cp ('t307');
6680                !!!parse-error (type => 'unmatched end tag',
6681                                text => 'html', token => $token);
6682                ## Ignore the token
6683                !!!next-token;
6684                next B;
6685            } else {            } else {
6686              die "$0: $token->{type}: Unknown token type";              !!!cp ('t308');
6687                $self->{insertion_mode} = AFTER_HTML_BODY_IM;
6688                !!!next-token;
6689                next B;
6690            }            }
6691            } else {
6692              !!!cp ('t309');
6693              !!!parse-error (type => 'after body:/',
6694                              text => $token->{tag_name}, token => $token);
6695    
6696            $self->{insertion_mode} = 'in body';            $self->{insertion_mode} = IN_BODY_IM;
6697            ## reprocess            ## reprocess
6698            redo B;            next B;
6699      } elsif ($self->{insertion_mode} eq 'in frameset') {          }
6700        if ($token->{type} eq 'character') {        } elsif ($token->{type} == END_OF_FILE_TOKEN) {
6701          if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {          !!!cp ('t309.2');
6702            ## Stop parsing
6703            last B;
6704          } else {
6705            die "$0: $token->{type}: Unknown token type";
6706          }
6707        } elsif ($self->{insertion_mode} & FRAME_IMS) {
6708          if ($token->{type} == CHARACTER_TOKEN) {
6709            if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
6710            $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);            $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
6711              
6712            unless (length $token->{data}) {            unless (length $token->{data}) {
6713                !!!cp ('t310');
6714              !!!next-token;              !!!next-token;
6715              redo B;              next B;
6716            }            }
6717          }          }
6718            
6719          !!!parse-error (type => 'in frameset:#character');          if ($token->{data} =~ s/^[^\x09\x0A\x0C\x20]+//) {
6720          ## Ignore the token            if ($self->{insertion_mode} == IN_FRAMESET_IM) {
6721          !!!next-token;              !!!cp ('t311');
6722          redo B;              !!!parse-error (type => 'in frameset:#text', token => $token);
6723        } elsif ($token->{type} eq 'start tag') {            } elsif ($self->{insertion_mode} == AFTER_FRAMESET_IM) {
6724          if ($token->{tag_name} eq 'frameset') {              !!!cp ('t312');
6725            !!!insert-element ($token->{tag_name}, $token->{attributes});              !!!parse-error (type => 'after frameset:#text', token => $token);
6726              } else { # "after after frameset"
6727                !!!cp ('t313');
6728                !!!parse-error (type => 'after html:#text', token => $token);
6729              }
6730              
6731              ## Ignore the token.
6732              if (length $token->{data}) {
6733                !!!cp ('t314');
6734                ## reprocess the rest of characters
6735              } else {
6736                !!!cp ('t315');
6737                !!!next-token;
6738              }
6739              next B;
6740            }
6741            
6742            die qq[$0: Character "$token->{data}"];
6743          } elsif ($token->{type} == START_TAG_TOKEN) {
6744            if ($token->{tag_name} eq 'frameset' and
6745                $self->{insertion_mode} == IN_FRAMESET_IM) {
6746              !!!cp ('t318');
6747              !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
6748              !!!nack ('t318.1');
6749            !!!next-token;            !!!next-token;
6750            redo B;            next B;
6751          } elsif ($token->{tag_name} eq 'frame') {          } elsif ($token->{tag_name} eq 'frame' and
6752            !!!insert-element ($token->{tag_name}, $token->{attributes});                   $self->{insertion_mode} == IN_FRAMESET_IM) {
6753              !!!cp ('t319');
6754              !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
6755            pop @{$self->{open_elements}};            pop @{$self->{open_elements}};
6756              !!!ack ('t319.1');
6757            !!!next-token;            !!!next-token;
6758            redo B;            next B;
6759          } elsif ($token->{tag_name} eq 'noframes') {          } elsif ($token->{tag_name} eq 'noframes') {
6760            ## NOTE: As if in body.            !!!cp ('t320');
6761            $parse_rcdata->(CDATA_CONTENT_MODEL, $insert_to_current);            ## NOTE: As if in head.
6762            redo B;            $parse_rcdata->(CDATA_CONTENT_MODEL);
6763          } else {            next B;
6764            !!!parse-error (type => 'in frameset:'.$token->{tag_name});  
6765              ## NOTE: |<!DOCTYPE HTML><frameset></frameset></html><noframes></noframes>|
6766              ## has no parse error.
6767            } else {
6768              if ($self->{insertion_mode} == IN_FRAMESET_IM) {
6769                !!!cp ('t321');
6770                !!!parse-error (type => 'in frameset',
6771                                text => $token->{tag_name}, token => $token);
6772              } elsif ($self->{insertion_mode} == AFTER_FRAMESET_IM) {
6773                !!!cp ('t322');
6774                !!!parse-error (type => 'after frameset',
6775                                text => $token->{tag_name}, token => $token);
6776              } else { # "after after frameset"
6777                !!!cp ('t322.2');
6778                !!!parse-error (type => 'after after frameset',
6779                                text => $token->{tag_name}, token => $token);
6780              }
6781            ## Ignore the token            ## Ignore the token
6782              !!!nack ('t322.1');
6783            !!!next-token;            !!!next-token;
6784            redo B;            next B;
6785          }          }
6786        } elsif ($token->{type} eq 'end tag') {        } elsif ($token->{type} == END_TAG_TOKEN) {
6787          if ($token->{tag_name} eq 'frameset') {          if ($token->{tag_name} eq 'frameset' and
6788            if ($self->{open_elements}->[-1]->[1] eq 'html' and              $self->{insertion_mode} == IN_FRAMESET_IM) {
6789              if ($self->{open_elements}->[-1]->[1] & HTML_EL and
6790                @{$self->{open_elements}} == 1) {                @{$self->{open_elements}} == 1) {
6791              !!!parse-error (type => 'unmatched end tag:'.$token->{tag_name});              !!!cp ('t325');
6792                !!!parse-error (type => 'unmatched end tag',
6793                                text => $token->{tag_name}, token => $token);
6794              ## Ignore the token              ## Ignore the token
6795              !!!next-token;              !!!next-token;
6796            } else {            } else {
6797                !!!cp ('t326');
6798              pop @{$self->{open_elements}};              pop @{$self->{open_elements}};
6799              !!!next-token;              !!!next-token;
6800            }            }
6801    
6802            if (not defined $self->{inner_html_node} and            if (not defined $self->{inner_html_node} and
6803                $self->{open_elements}->[-1]->[1] ne 'frameset') {                not ($self->{open_elements}->[-1]->[1] & FRAMESET_EL)) {
6804              $self->{insertion_mode} = 'after frameset';              !!!cp ('t327');
6805                $self->{insertion_mode} = AFTER_FRAMESET_IM;
6806              } else {
6807                !!!cp ('t328');
6808            }            }
6809            redo B;            next B;
6810            } elsif ($token->{tag_name} eq 'html' and
6811                     $self->{insertion_mode} == AFTER_FRAMESET_IM) {
6812              !!!cp ('t329');
6813              $self->{insertion_mode} = AFTER_HTML_FRAMESET_IM;
6814              !!!next-token;
6815              next B;
6816          } else {          } else {
6817            !!!parse-error (type => 'in frameset:/'.$token->{tag_name});            if ($self->{insertion_mode} == IN_FRAMESET_IM) {
6818                !!!cp ('t330');
6819                !!!parse-error (type => 'in frameset:/',
6820                                text => $token->{tag_name}, token => $token);
6821              } elsif ($self->{insertion_mode} == AFTER_FRAMESET_IM) {
6822                !!!cp ('t330.1');
6823                !!!parse-error (type => 'after frameset:/',
6824                                text => $token->{tag_name}, token => $token);
6825              } else { # "after after html"
6826                !!!cp ('t331');
6827                !!!parse-error (type => 'after after frameset:/',
6828                                text => $token->{tag_name}, token => $token);
6829              }
6830            ## Ignore the token            ## Ignore the token
6831            !!!next-token;            !!!next-token;
6832            redo B;            next B;
6833            }
6834          } elsif ($token->{type} == END_OF_FILE_TOKEN) {
6835            unless ($self->{open_elements}->[-1]->[1] & HTML_EL and
6836                    @{$self->{open_elements}} == 1) { # redundant, maybe
6837              !!!cp ('t331.1');
6838              !!!parse-error (type => 'in body:#eof', token => $token);
6839            } else {
6840              !!!cp ('t331.2');
6841          }          }
6842            
6843            ## Stop parsing
6844            last B;
6845        } else {        } else {
6846          die "$0: $token->{type}: Unknown token type";          die "$0: $token->{type}: Unknown token type";
6847        }        }
6848      } elsif ($self->{insertion_mode} eq 'after frameset') {      } else {
6849        if ($token->{type} eq 'character') {        die "$0: $self->{insertion_mode}: Unknown insertion mode";
6850              if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {      }
               $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);  
6851    
6852                unless (length $token->{data}) {      ## "in body" insertion mode
6853                  !!!next-token;      if ($token->{type} == START_TAG_TOKEN) {
6854                  redo B;        if ($token->{tag_name} eq 'script') {
6855            !!!cp ('t332');
6856            ## NOTE: This is an "as if in head" code clone
6857            $script_start_tag->();
6858            next B;
6859          } elsif ($token->{tag_name} eq 'style') {
6860            !!!cp ('t333');
6861            ## NOTE: This is an "as if in head" code clone
6862            $parse_rcdata->(CDATA_CONTENT_MODEL);
6863            next B;
6864          } elsif ({
6865                    base => 1, command => 1, eventsource => 1, link => 1,
6866                   }->{$token->{tag_name}}) {
6867            !!!cp ('t334');
6868            ## NOTE: This is an "as if in head" code clone, only "-t" differs
6869            !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
6870            pop @{$self->{open_elements}};
6871            !!!ack ('t334.1');
6872            !!!next-token;
6873            next B;
6874          } elsif ($token->{tag_name} eq 'meta') {
6875            ## NOTE: This is an "as if in head" code clone, only "-t" differs
6876            !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
6877            my $meta_el = pop @{$self->{open_elements}};
6878    
6879            unless ($self->{confident}) {
6880              if ($token->{attributes}->{charset}) {
6881                !!!cp ('t335');
6882                ## NOTE: Whether the encoding is supported or not is handled
6883                ## in the {change_encoding} callback.
6884                $self->{change_encoding}
6885                    ->($self, $token->{attributes}->{charset}->{value}, $token);
6886                
6887                $meta_el->[0]->get_attribute_node_ns (undef, 'charset')
6888                    ->set_user_data (manakai_has_reference =>
6889                                         $token->{attributes}->{charset}
6890                                             ->{has_reference});
6891              } elsif ($token->{attributes}->{content}) {
6892                if ($token->{attributes}->{content}->{value}
6893                    =~ /[Cc][Hh][Aa][Rr][Ss][Ee][Tt]
6894                        [\x09\x0A\x0C\x0D\x20]*=
6895                        [\x09\x0A\x0C\x0D\x20]*(?>"([^"]*)"|'([^']*)'|
6896                        ([^"'\x09\x0A\x0C\x0D\x20][^\x09\x0A\x0C\x0D\x20\x3B]*))
6897                       /x) {
6898                  !!!cp ('t336');
6899                  ## NOTE: Whether the encoding is supported or not is handled
6900                  ## in the {change_encoding} callback.
6901                  $self->{change_encoding}
6902                      ->($self, defined $1 ? $1 : defined $2 ? $2 : $3, $token);
6903                  $meta_el->[0]->get_attribute_node_ns (undef, 'content')
6904                      ->set_user_data (manakai_has_reference =>
6905                                           $token->{attributes}->{content}
6906                                                 ->{has_reference});
6907                }
6908              }
6909            } else {
6910              if ($token->{attributes}->{charset}) {
6911                !!!cp ('t337');
6912                $meta_el->[0]->get_attribute_node_ns (undef, 'charset')
6913                    ->set_user_data (manakai_has_reference =>
6914                                         $token->{attributes}->{charset}
6915                                             ->{has_reference});
6916              }
6917              if ($token->{attributes}->{content}) {
6918                !!!cp ('t338');
6919                $meta_el->[0]->get_attribute_node_ns (undef, 'content')
6920                    ->set_user_data (manakai_has_reference =>
6921                                         $token->{attributes}->{content}
6922                                             ->{has_reference});
6923              }
6924            }
6925    
6926            !!!ack ('t338.1');
6927            !!!next-token;
6928            next B;
6929          } elsif ($token->{tag_name} eq 'title') {
6930            !!!cp ('t341');
6931            ## NOTE: This is an "as if in head" code clone
6932            $parse_rcdata->(RCDATA_CONTENT_MODEL);
6933            next B;
6934          } elsif ($token->{tag_name} eq 'body') {
6935            !!!parse-error (type => 'in body', text => 'body', token => $token);
6936                  
6937            if (@{$self->{open_elements}} == 1 or
6938                not ($self->{open_elements}->[1]->[1] & BODY_EL)) {
6939              !!!cp ('t342');
6940              ## Ignore the token
6941            } else {
6942              my $body_el = $self->{open_elements}->[1]->[0];
6943              for my $attr_name (keys %{$token->{attributes}}) {
6944                unless ($body_el->has_attribute_ns (undef, $attr_name)) {
6945                  !!!cp ('t343');
6946                  $body_el->set_attribute_ns
6947                    (undef, [undef, $attr_name],
6948                     $token->{attributes}->{$attr_name}->{value});
6949                }
6950              }
6951            }
6952            !!!nack ('t343.1');
6953            !!!next-token;
6954            next B;
6955          } elsif ({
6956                    ## NOTE: Start tags for non-phrasing flow content elements
6957    
6958                    ## NOTE: The normal one
6959                    address => 1, article => 1, aside => 1, blockquote => 1,
6960                    center => 1, datagrid => 1, details => 1, dialog => 1,
6961                    dir => 1, div => 1, dl => 1, fieldset => 1, figure => 1,
6962                    footer => 1, h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1,
6963                    h6 => 1, header => 1, menu => 1, nav => 1, ol => 1, p => 1,
6964                    section => 1, ul => 1,
6965                    ## NOTE: As normal, but drops leading newline
6966                    pre => 1, listing => 1,
6967                    ## NOTE: As normal, but interacts with the form element pointer
6968                    form => 1,
6969                    
6970                    table => 1,
6971                    hr => 1,
6972                   }->{$token->{tag_name}}) {
6973            if ($token->{tag_name} eq 'form' and defined $self->{form_element}) {
6974              !!!cp ('t350');
6975              !!!parse-error (type => 'in form:form', token => $token);
6976              ## Ignore the token
6977              !!!nack ('t350.1');
6978              !!!next-token;
6979              next B;
6980            }
6981    
6982            ## has a p element in scope
6983            INSCOPE: for (reverse @{$self->{open_elements}}) {
6984              if ($_->[1] & P_EL) {
6985                !!!cp ('t344');
6986                !!!back-token; # <form>
6987                $token = {type => END_TAG_TOKEN, tag_name => 'p',
6988                          line => $token->{line}, column => $token->{column}};
6989                next B;
6990              } elsif ($_->[1] & SCOPING_EL) {
6991                !!!cp ('t345');
6992                last INSCOPE;
6993              }
6994            } # INSCOPE
6995              
6996            !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
6997            if ($token->{tag_name} eq 'pre' or $token->{tag_name} eq 'listing') {
6998              !!!nack ('t346.1');
6999              !!!next-token;
7000              if ($token->{type} == CHARACTER_TOKEN) {
7001                $token->{data} =~ s/^\x0A//;
7002                unless (length $token->{data}) {
7003                  !!!cp ('t346');
7004                  !!!next-token;
7005                } else {
7006                  !!!cp ('t349');
7007                }
7008              } else {
7009                !!!cp ('t348');
7010              }
7011            } elsif ($token->{tag_name} eq 'form') {
7012              !!!cp ('t347.1');
7013              $self->{form_element} = $self->{open_elements}->[-1]->[0];
7014    
7015              !!!nack ('t347.2');
7016              !!!next-token;
7017            } elsif ($token->{tag_name} eq 'table') {
7018              !!!cp ('t382');
7019              push @{$open_tables}, [$self->{open_elements}->[-1]->[0]];
7020              
7021              $self->{insertion_mode} = IN_TABLE_IM;
7022    
7023              !!!nack ('t382.1');
7024              !!!next-token;
7025            } elsif ($token->{tag_name} eq 'hr') {
7026              !!!cp ('t386');
7027              pop @{$self->{open_elements}};
7028            
7029              !!!nack ('t386.1');
7030              !!!next-token;
7031            } else {
7032              !!!nack ('t347.1');
7033              !!!next-token;
7034            }
7035            next B;
7036          } elsif ($token->{tag_name} eq 'li') {
7037            ## NOTE: As normal, but imply </li> when there's another <li> ...
7038    
7039            ## NOTE: Special, Scope (<li><foo><li> == <li><foo><li/></foo></li>)
7040              ## Interpreted as <li><foo/></li><li/> (non-conforming)
7041              ## blockquote (O9.27), center (O), dd (Fx3, O, S3.1.2, IE7),
7042              ## dt (Fx, O, S, IE), dl (O), fieldset (O, S, IE), form (Fx, O, S),
7043              ## hn (O), pre (O), applet (O, S), button (O, S), marquee (Fx, O, S),
7044              ## object (Fx)
7045              ## Generate non-tree (non-conforming)
7046              ## basefont (IE7 (where basefont is non-void)), center (IE),
7047              ## form (IE), hn (IE)
7048            ## address, div, p (<li><foo><li> == <li><foo/></li><li/>)
7049              ## Interpreted as <li><foo><li/></foo></li> (non-conforming)
7050              ## div (Fx, S)
7051    
7052            my $non_optional;
7053            my $i = -1;
7054    
7055            ## 1.
7056            for my $node (reverse @{$self->{open_elements}}) {
7057              if ($node->[1] & LI_EL) {
7058                ## 2. (a) As if </li>
7059                {
7060                  ## If no </li> - not applied
7061                  #
7062    
7063                  ## Otherwise
7064    
7065                  ## 1. generate implied end tags, except for </li>
7066                  #
7067    
7068                  ## 2. If current node != "li", parse error
7069                  if ($non_optional) {
7070                    !!!parse-error (type => 'not closed',
7071                                    text => $non_optional->[0]->manakai_local_name,
7072                                    token => $token);
7073                    !!!cp ('t355');
7074                  } else {
7075                    !!!cp ('t356');
7076                }                }
7077    
7078                  ## 3. Pop
7079                  splice @{$self->{open_elements}}, $i;
7080              }              }
7081    
7082              if ($token->{data} =~ s/^[^\x09\x0A\x0B\x0C\x20]+//) {              last; ## 2. (b) goto 5.
7083                !!!parse-error (type => 'after frameset:#character');            } elsif (
7084                       ## NOTE: not "formatting" and not "phrasing"
7085                       ($node->[1] & SPECIAL_EL or
7086                        $node->[1] & SCOPING_EL) and
7087                       ## NOTE: "li", "dt", and "dd" are in |SPECIAL_EL|.
7088    
7089                       (not $node->[1] & ADDRESS_EL) &
7090                       (not $node->[1] & DIV_EL) &
7091                       (not $node->[1] & P_EL)) {
7092                ## 3.
7093                !!!cp ('t357');
7094                last; ## goto 5.
7095              } elsif ($node->[1] & END_TAG_OPTIONAL_EL) {
7096                !!!cp ('t358');
7097                #
7098              } else {
7099                !!!cp ('t359');
7100                $non_optional ||= $node;
7101                #
7102              }
7103              ## 4.
7104              ## goto 2.
7105              $i--;
7106            }
7107    
7108            ## 5. (a) has a |p| element in scope
7109            INSCOPE: for (reverse @{$self->{open_elements}}) {
7110              if ($_->[1] & P_EL) {
7111                !!!cp ('t353');
7112    
7113                ## NOTE: |<p><li>|, for example.
7114    
7115                !!!back-token; # <x>
7116                $token = {type => END_TAG_TOKEN, tag_name => 'p',
7117                          line => $token->{line}, column => $token->{column}};
7118                next B;
7119              } elsif ($_->[1] & SCOPING_EL) {
7120                !!!cp ('t354');
7121                last INSCOPE;
7122              }
7123            } # INSCOPE
7124    
7125            ## 5. (b) insert
7126            !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
7127            !!!nack ('t359.1');
7128            !!!next-token;
7129            next B;
7130          } elsif ($token->{tag_name} eq 'dt' or
7131                   $token->{tag_name} eq 'dd') {
7132            ## NOTE: As normal, but imply </dt> or </dd> when ...
7133    
7134            my $non_optional;
7135            my $i = -1;
7136    
7137            ## 1.
7138            for my $node (reverse @{$self->{open_elements}}) {
7139              if ($node->[1] & DT_EL or $node->[1] & DD_EL) {
7140                ## 2. (a) As if </li>
7141                {
7142                  ## If no </li> - not applied
7143                  #
7144    
7145                ## Ignore the token.                ## Otherwise
7146                if (length $token->{data}) {  
7147                  ## reprocess the rest of characters                ## 1. generate implied end tags, except for </dt> or </dd>
7148                  #
7149    
7150                  ## 2. If current node != "dt"|"dd", parse error
7151                  if ($non_optional) {
7152                    !!!parse-error (type => 'not closed',
7153                                    text => $non_optional->[0]->manakai_local_name,
7154                                    token => $token);
7155                    !!!cp ('t355.1');
7156                } else {                } else {
7157                  !!!next-token;                  !!!cp ('t356.1');
7158                }                }
7159                redo B;  
7160                  ## 3. Pop
7161                  splice @{$self->{open_elements}}, $i;
7162              }              }
7163    
7164          die qq[$0: Character "$token->{data}"];              last; ## 2. (b) goto 5.
7165        } elsif ($token->{type} eq 'start tag') {            } elsif (
7166          if ($token->{tag_name} eq 'noframes') {                     ## NOTE: not "formatting" and not "phrasing"
7167            ## NOTE: As if in body.                     ($node->[1] & SPECIAL_EL or
7168            $parse_rcdata->(CDATA_CONTENT_MODEL, $insert_to_current);                      $node->[1] & SCOPING_EL) and
7169            redo B;                     ## NOTE: "li", "dt", and "dd" are in |SPECIAL_EL|.
7170    
7171                       (not $node->[1] & ADDRESS_EL) &
7172                       (not $node->[1] & DIV_EL) &
7173                       (not $node->[1] & P_EL)) {
7174                ## 3.
7175                !!!cp ('t357.1');
7176                last; ## goto 5.
7177              } elsif ($node->[1] & END_TAG_OPTIONAL_EL) {
7178                !!!cp ('t358.1');
7179                #
7180              } else {
7181                !!!cp ('t359.1');
7182                $non_optional ||= $node;
7183                #
7184              }
7185              ## 4.
7186              ## goto 2.
7187              $i--;
7188            }
7189    
7190            ## 5. (a) has a |p| element in scope
7191            INSCOPE: for (reverse @{$self->{open_elements}}) {
7192              if ($_->[1] & P_EL) {
7193                !!!cp ('t353.1');
7194                !!!back-token; # <x>
7195                $token = {type => END_TAG_TOKEN, tag_name => 'p',
7196                          line => $token->{line}, column => $token->{column}};
7197                next B;
7198              } elsif ($_->[1] & SCOPING_EL) {
7199                !!!cp ('t354.1');
7200                last INSCOPE;
7201              }
7202            } # INSCOPE
7203    
7204            ## 5. (b) insert
7205            !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
7206            !!!nack ('t359.2');
7207            !!!next-token;
7208            next B;
7209          } elsif ($token->{tag_name} eq 'plaintext') {
7210            ## NOTE: As normal, but effectively ends parsing
7211    
7212            ## has a p element in scope
7213            INSCOPE: for (reverse @{$self->{open_elements}}) {
7214              if ($_->[1] & P_EL) {
7215                !!!cp ('t367');
7216                !!!back-token; # <plaintext>
7217                $token = {type => END_TAG_TOKEN, tag_name => 'p',
7218                          line => $token->{line}, column => $token->{column}};
7219                next B;
7220              } elsif ($_->[1] & SCOPING_EL) {
7221                !!!cp ('t368');
7222                last INSCOPE;
7223              }
7224            } # INSCOPE
7225              
7226            !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
7227              
7228            $self->{content_model} = PLAINTEXT_CONTENT_MODEL;
7229              
7230            !!!nack ('t368.1');
7231            !!!next-token;
7232            next B;
7233          } elsif ($token->{tag_name} eq 'a') {
7234            AFE: for my $i (reverse 0..$#$active_formatting_elements) {
7235              my $node = $active_formatting_elements->[$i];
7236              if ($node->[1] & A_EL) {
7237                !!!cp ('t371');
7238                !!!parse-error (type => 'in a:a', token => $token);
7239                
7240                !!!back-token; # <a>
7241                $token = {type => END_TAG_TOKEN, tag_name => 'a',
7242                          line => $token->{line}, column => $token->{column}};
7243                $formatting_end_tag->($token);
7244                
7245                AFE2: for (reverse 0..$#$active_formatting_elements) {
7246                  if ($active_formatting_elements->[$_]->[0] eq $node->[0]) {
7247                    !!!cp ('t372');
7248                    splice @$active_formatting_elements, $_, 1;
7249                    last AFE2;
7250                  }
7251                } # AFE2
7252                OE: for (reverse 0..$#{$self->{open_elements}}) {
7253                  if ($self->{open_elements}->[$_]->[0] eq $node->[0]) {
7254                    !!!cp ('t373');
7255                    splice @{$self->{open_elements}}, $_, 1;
7256                    last OE;
7257                  }
7258                } # OE
7259                last AFE;
7260              } elsif ($node->[0] eq '#marker') {
7261                !!!cp ('t374');
7262                last AFE;
7263              }
7264            } # AFE
7265              
7266            $reconstruct_active_formatting_elements->($insert_to_current);
7267    
7268            !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
7269            push @$active_formatting_elements, $self->{open_elements}->[-1];
7270    
7271            !!!nack ('t374.1');
7272            !!!next-token;
7273            next B;
7274          } elsif ($token->{tag_name} eq 'nobr') {
7275            $reconstruct_active_formatting_elements->($insert_to_current);
7276    
7277            ## has a |nobr| element in scope
7278            INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
7279              my $node = $self->{open_elements}->[$_];
7280              if ($node->[1] & NOBR_EL) {
7281                !!!cp ('t376');
7282                !!!parse-error (type => 'in nobr:nobr', token => $token);
7283                !!!back-token; # <nobr>
7284                $token = {type => END_TAG_TOKEN, tag_name => 'nobr',
7285                          line => $token->{line}, column => $token->{column}};
7286                next B;
7287              } elsif ($node->[1] & SCOPING_EL) {
7288                !!!cp ('t377');
7289                last INSCOPE;
7290              }
7291            } # INSCOPE
7292            
7293            !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
7294            push @$active_formatting_elements, $self->{open_elements}->[-1];
7295            
7296            !!!nack ('t377.1');
7297            !!!next-token;
7298            next B;
7299          } elsif ($token->{tag_name} eq 'button') {
7300            ## has a button element in scope
7301            INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
7302              my $node = $self->{open_elements}->[$_];
7303              if ($node->[1] & BUTTON_EL) {
7304                !!!cp ('t378');
7305                !!!parse-error (type => 'in button:button', token => $token);
7306                !!!back-token; # <button>
7307                $token = {type => END_TAG_TOKEN, tag_name => 'button',
7308                          line => $token->{line}, column => $token->{column}};
7309                next B;
7310              } elsif ($node->[1] & SCOPING_EL) {
7311                !!!cp ('t379');
7312                last INSCOPE;
7313              }
7314            } # INSCOPE
7315              
7316            $reconstruct_active_formatting_elements->($insert_to_current);
7317              
7318            !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
7319    
7320            ## TODO: associate with $self->{form_element} if defined
7321    
7322            push @$active_formatting_elements, ['#marker', ''];
7323    
7324            !!!nack ('t379.1');
7325            !!!next-token;
7326            next B;
7327          } elsif ({
7328                    xmp => 1,
7329                    iframe => 1,
7330                    noembed => 1,
7331                    noframes => 1, ## NOTE: This is an "as if in head" code clone.
7332                    noscript => 0, ## TODO: 1 if scripting is enabled
7333                   }->{$token->{tag_name}}) {
7334            if ($token->{tag_name} eq 'xmp') {
7335              !!!cp ('t381');
7336              $reconstruct_active_formatting_elements->($insert_to_current);
7337          } else {          } else {
7338            !!!parse-error (type => 'after frameset:'.$token->{tag_name});            !!!cp ('t399');
7339            }
7340            ## NOTE: There is an "as if in body" code clone.
7341            $parse_rcdata->(CDATA_CONTENT_MODEL);
7342            next B;
7343          } elsif ($token->{tag_name} eq 'isindex') {
7344            !!!parse-error (type => 'isindex', token => $token);
7345            
7346            if (defined $self->{form_element}) {
7347              !!!cp ('t389');
7348            ## Ignore the token            ## Ignore the token
7349              !!!nack ('t389'); ## NOTE: Not acknowledged.
7350            !!!next-token;            !!!next-token;
7351            redo B;            next B;
7352          }          } else {
7353        } elsif ($token->{type} eq 'end tag') {            !!!ack ('t391.1');
7354          if ($token->{tag_name} eq 'html') {  
7355            $previous_insertion_mode = $self->{insertion_mode};            my $at = $token->{attributes};
7356            $self->{insertion_mode} = 'trailing end';            my $form_attrs;
7357              $form_attrs->{action} = $at->{action} if $at->{action};
7358              my $prompt_attr = $at->{prompt};
7359              $at->{name} = {name => 'name', value => 'isindex'};
7360              delete $at->{action};
7361              delete $at->{prompt};
7362              my @tokens = (
7363                            {type => START_TAG_TOKEN, tag_name => 'form',
7364                             attributes => $form_attrs,
7365                             line => $token->{line}, column => $token->{column}},
7366                            {type => START_TAG_TOKEN, tag_name => 'hr',
7367                             line => $token->{line}, column => $token->{column}},
7368                            {type => START_TAG_TOKEN, tag_name => 'p',
7369                             line => $token->{line}, column => $token->{column}},
7370                            {type => START_TAG_TOKEN, tag_name => 'label',
7371                             line => $token->{line}, column => $token->{column}},
7372                           );
7373              if ($prompt_attr) {
7374                !!!cp ('t390');
7375                push @tokens, {type => CHARACTER_TOKEN, data => $prompt_attr->{value},
7376                               #line => $token->{line}, column => $token->{column},
7377                              };
7378              } else {
7379                !!!cp ('t391');
7380                push @tokens, {type => CHARACTER_TOKEN,
7381                               data => 'This is a searchable index. Insert your search keywords here: ',
7382                               #line => $token->{line}, column => $token->{column},
7383                              }; # SHOULD
7384                ## TODO: make this configurable
7385              }
7386              push @tokens,
7387                            {type => START_TAG_TOKEN, tag_name => 'input', attributes => $at,
7388                             line => $token->{line}, column => $token->{column}},
7389                            #{type => CHARACTER_TOKEN, data => ''}, # SHOULD
7390                            {type => END_TAG_TOKEN, tag_name => 'label',
7391                             line => $token->{line}, column => $token->{column}},
7392                            {type => END_TAG_TOKEN, tag_name => 'p',
7393                             line => $token->{line}, column => $token->{column}},
7394                            {type => START_TAG_TOKEN, tag_name => 'hr',
7395                             line => $token->{line}, column => $token->{column}},
7396                            {type => END_TAG_TOKEN, tag_name => 'form',
7397                             line => $token->{line}, column => $token->{column}};
7398              !!!back-token (@tokens);
7399            !!!next-token;            !!!next-token;
7400            redo B;            next B;
7401            }
7402          } elsif ($token->{tag_name} eq 'textarea') {
7403            my $tag_name = $token->{tag_name};
7404            my $el;
7405            !!!create-element ($el, $HTML_NS, $token->{tag_name}, $token->{attributes}, $token);
7406            
7407            ## TODO: $self->{form_element} if defined
7408            $self->{content_model} = RCDATA_CONTENT_MODEL;
7409            delete $self->{escape}; # MUST
7410            
7411            $insert->($el);
7412            
7413            my $text = '';
7414            !!!nack ('t392.1');
7415            !!!next-token;
7416            if ($token->{type} == CHARACTER_TOKEN) {
7417              $token->{data} =~ s/^\x0A//;
7418              unless (length $token->{data}) {
7419                !!!cp ('t392');
7420                !!!next-token;
7421              } else {
7422                !!!cp ('t393');
7423              }
7424          } else {          } else {
7425            !!!parse-error (type => 'after frameset:/'.$token->{tag_name});            !!!cp ('t394');
7426            ## Ignore the token          }
7427            while ($token->{type} == CHARACTER_TOKEN) {
7428              !!!cp ('t395');
7429              $text .= $token->{data};
7430            !!!next-token;            !!!next-token;
           redo B;  
7431          }          }
7432            if (length $text) {
7433              !!!cp ('t396');
7434              $el->manakai_append_text ($text);
7435            }
7436            
7437            $self->{content_model} = PCDATA_CONTENT_MODEL;
7438            
7439            if ($token->{type} == END_TAG_TOKEN and
7440                $token->{tag_name} eq $tag_name) {
7441              !!!cp ('t397');
7442              ## Ignore the token
7443            } else {
7444              !!!cp ('t398');
7445              !!!parse-error (type => 'in RCDATA:#eof', token => $token);
7446            }
7447            !!!next-token;
7448            next B;
7449          } elsif ($token->{tag_name} eq 'optgroup' or
7450                   $token->{tag_name} eq 'option') {
7451            ## has an |option| element in scope
7452            INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
7453              my $node = $self->{open_elements}->[$_];
7454              if ($node->[1] & OPTION_EL) {
7455                !!!cp ('t397.1');
7456                ## NOTE: As if </option>
7457                !!!back-token; # <option> or <optgroup>
7458                $token = {type => END_TAG_TOKEN, tag_name => 'option',
7459                          line => $token->{line}, column => $token->{column}};
7460                next B;
7461              } elsif ($node->[1] & SCOPING_EL) {
7462                !!!cp ('t397.2');
7463                last INSCOPE;
7464              }
7465            } # INSCOPE
7466    
7467            $reconstruct_active_formatting_elements->($insert_to_current);
7468    
7469            !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
7470    
7471            !!!nack ('t397.3');
7472            !!!next-token;
7473            redo B;
7474          } elsif ($token->{tag_name} eq 'rt' or
7475                   $token->{tag_name} eq 'rp') {
7476            ## has a |ruby| element in scope
7477            INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
7478              my $node = $self->{open_elements}->[$_];
7479              if ($node->[1] & RUBY_EL) {
7480                !!!cp ('t398.1');
7481                ## generate implied end tags
7482                while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
7483                  !!!cp ('t398.2');
7484                  pop @{$self->{open_elements}};
7485                }
7486                unless ($self->{open_elements}->[-1]->[1] & RUBY_EL) {
7487                  !!!cp ('t398.3');
7488                  !!!parse-error (type => 'not closed',
7489                                  text => $self->{open_elements}->[-1]->[0]
7490                                      ->manakai_local_name,
7491                                  token => $token);
7492                  pop @{$self->{open_elements}}
7493                      while not $self->{open_elements}->[-1]->[1] & RUBY_EL;
7494                }
7495                last INSCOPE;
7496              } elsif ($node->[1] & SCOPING_EL) {
7497                !!!cp ('t398.4');
7498                last INSCOPE;
7499              }
7500            } # INSCOPE
7501    
7502            !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
7503    
7504            !!!nack ('t398.5');
7505            !!!next-token;
7506            redo B;
7507          } elsif ($token->{tag_name} eq 'math' or
7508                   $token->{tag_name} eq 'svg') {
7509            $reconstruct_active_formatting_elements->($insert_to_current);
7510    
7511            ## "Adjust MathML attributes" ('math' only) - done in insert-element-f
7512    
7513            ## "adjust SVG attributes" ('svg' only) - done in insert-element-f
7514    
7515            ## "adjust foreign attributes" - done in insert-element-f
7516            
7517            !!!insert-element-f ($token->{tag_name} eq 'math' ? $MML_NS : $SVG_NS, $token->{tag_name}, $token->{attributes}, $token);
7518            
7519            if ($self->{self_closing}) {
7520              pop @{$self->{open_elements}};
7521              !!!ack ('t398.6');
7522            } else {
7523              !!!cp ('t398.7');
7524              $self->{insertion_mode} |= IN_FOREIGN_CONTENT_IM;
7525              ## NOTE: |<body><math><mi><svg>| -> "in foreign content" insertion
7526              ## mode, "in body" (not "in foreign content") secondary insertion
7527              ## mode, maybe.
7528            }
7529    
7530            !!!next-token;
7531            next B;
7532          } elsif ({
7533                    caption => 1, col => 1, colgroup => 1, frame => 1,
7534                    frameset => 1, head => 1,
7535                    tbody => 1, td => 1, tfoot => 1, th => 1,
7536                    thead => 1, tr => 1,
7537                   }->{$token->{tag_name}}) {
7538            !!!cp ('t401');
7539            !!!parse-error (type => 'in body',
7540                            text => $token->{tag_name}, token => $token);
7541            ## Ignore the token
7542            !!!nack ('t401.1'); ## NOTE: |<col/>| or |<frame/>| here is an error.
7543            !!!next-token;
7544            next B;
7545          } elsif ($token->{tag_name} eq 'param' or
7546                   $token->{tag_name} eq 'source') {
7547            !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
7548            pop @{$self->{open_elements}};
7549    
7550            !!!ack ('t398.5');
7551            !!!next-token;
7552            redo B;
7553        } else {        } else {
7554          die "$0: $token->{type}: Unknown token type";          if ($token->{tag_name} eq 'image') {
7555              !!!cp ('t384');
7556              !!!parse-error (type => 'image', token => $token);
7557              $token->{tag_name} = 'img';
7558            } else {
7559              !!!cp ('t385');
7560            }
7561    
7562            ## NOTE: There is an "as if <br>" code clone.
7563            $reconstruct_active_formatting_elements->($insert_to_current);
7564            
7565            !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
7566    
7567            if ({
7568                 applet => 1, marquee => 1, object => 1,
7569                }->{$token->{tag_name}}) {
7570              !!!cp ('t380');
7571              push @$active_formatting_elements, ['#marker', ''];
7572              !!!nack ('t380.1');
7573            } elsif ({
7574                      b => 1, big => 1, em => 1, font => 1, i => 1,
7575                      s => 1, small => 1, strike => 1,
7576                      strong => 1, tt => 1, u => 1,
7577                     }->{$token->{tag_name}}) {
7578              !!!cp ('t375');
7579              push @$active_formatting_elements, $self->{open_elements}->[-1];
7580              !!!nack ('t375.1');
7581            } elsif ($token->{tag_name} eq 'input') {
7582              !!!cp ('t388');
7583              ## TODO: associate with $self->{form_element} if defined
7584              pop @{$self->{open_elements}};
7585              !!!ack ('t388.2');
7586            } elsif ({
7587                      area => 1, basefont => 1, bgsound => 1, br => 1,
7588                      embed => 1, img => 1, spacer => 1, wbr => 1,
7589                     }->{$token->{tag_name}}) {
7590              !!!cp ('t388.1');
7591              pop @{$self->{open_elements}};
7592              !!!ack ('t388.3');
7593            } elsif ($token->{tag_name} eq 'select') {
7594              ## TODO: associate with $self->{form_element} if defined
7595            
7596              if ($self->{insertion_mode} & TABLE_IMS or
7597                  $self->{insertion_mode} & BODY_TABLE_IMS or
7598                  $self->{insertion_mode} == IN_COLUMN_GROUP_IM) {
7599                !!!cp ('t400.1');
7600                $self->{insertion_mode} = IN_SELECT_IN_TABLE_IM;
7601              } else {
7602                !!!cp ('t400.2');
7603                $self->{insertion_mode} = IN_SELECT_IM;
7604              }
7605              !!!nack ('t400.3');
7606            } else {
7607              !!!nack ('t402');
7608            }
7609            
7610            !!!next-token;
7611            next B;
7612        }        }
7613        } elsif ($token->{type} == END_TAG_TOKEN) {
7614          if ($token->{tag_name} eq 'body') {
7615            ## has a |body| element in scope
7616            my $i;
7617            INSCOPE: {
7618              for (reverse @{$self->{open_elements}}) {
7619                if ($_->[1] & BODY_EL) {
7620                  !!!cp ('t405');
7621                  $i = $_;
7622                  last INSCOPE;
7623                } elsif ($_->[1] & SCOPING_EL) {
7624                  !!!cp ('t405.1');
7625                  last;
7626                }
7627              }
7628    
7629        ## ISSUE: An issue in spec here            ## NOTE: |<marquee></body>|, |<svg><foreignobject></body>|
7630      } elsif ($self->{insertion_mode} eq 'trailing end') {  
7631        ## states in the main stage is preserved yet # MUST            !!!parse-error (type => 'unmatched end tag',
7632                                    text => $token->{tag_name}, token => $token);
7633        if ($token->{type} eq 'character') {            ## NOTE: Ignore the token.
7634          if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) {            !!!next-token;
7635            my $data = $1;            next B;
7636            ## As if in the main phase.          } # INSCOPE
7637            ## NOTE: The insertion mode in the main phase  
7638            ## just before the phase has been changed to the trailing          for (@{$self->{open_elements}}) {
7639            ## end phase is either "after body" or "after frameset".            unless ($_->[1] & ALL_END_TAG_OPTIONAL_EL) {
7640            $reconstruct_active_formatting_elements->($insert_to_current);              !!!cp ('t403');
7641                !!!parse-error (type => 'not closed',
7642                                text => $_->[0]->manakai_local_name,
7643                                token => $token);
7644                last;
7645              } else {
7646                !!!cp ('t404');
7647              }
7648            }
7649    
7650            $self->{insertion_mode} = AFTER_BODY_IM;
7651            !!!next-token;
7652            next B;
7653          } elsif ($token->{tag_name} eq 'html') {
7654            ## TODO: Update this code.  It seems that the code below is not
7655            ## up-to-date, though it has same effect as speced.
7656            if (@{$self->{open_elements}} > 1 and
7657                $self->{open_elements}->[1]->[1] & BODY_EL) {
7658              unless ($self->{open_elements}->[-1]->[1] & BODY_EL) {
7659                !!!cp ('t406');
7660                !!!parse-error (type => 'not closed',
7661                                text => $self->{open_elements}->[1]->[0]
7662                                    ->manakai_local_name,
7663                                token => $token);
7664              } else {
7665                !!!cp ('t407');
7666              }
7667              $self->{insertion_mode} = AFTER_BODY_IM;
7668              ## reprocess
7669              next B;
7670            } else {
7671              !!!cp ('t408');
7672              !!!parse-error (type => 'unmatched end tag',
7673                              text => $token->{tag_name}, token => $token);
7674              ## Ignore the token
7675              !!!next-token;
7676              next B;
7677            }
7678          } elsif ({
7679                    ## NOTE: End tags for non-phrasing flow content elements
7680    
7681                    ## NOTE: The normal ones
7682                    address => 1, article => 1, aside => 1, blockquote => 1,
7683                    center => 1, datagrid => 1, details => 1, dialog => 1,
7684                    dir => 1, div => 1, dl => 1, fieldset => 1, figure => 1,
7685                    footer => 1, header => 1, listing => 1, menu => 1, nav => 1,
7686                    ol => 1, pre => 1, section => 1, ul => 1,
7687    
7688                    ## NOTE: As normal, but ... optional tags
7689                    dd => 1, dt => 1, li => 1,
7690    
7691                    applet => 1, button => 1, marquee => 1, object => 1,
7692                   }->{$token->{tag_name}}) {
7693            ## NOTE: Code for <li> start tags includes "as if </li>" code.
7694            ## Code for <dt> or <dd> start tags includes "as if </dt> or
7695            ## </dd>" code.
7696    
7697            ## has an element in scope
7698            my $i;
7699            INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
7700              my $node = $self->{open_elements}->[$_];
7701              if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
7702                !!!cp ('t410');
7703                $i = $_;
7704                last INSCOPE;
7705              } elsif ($node->[1] & SCOPING_EL) {
7706                !!!cp ('t411');
7707                last INSCOPE;
7708              }
7709            } # INSCOPE
7710    
7711            unless (defined $i) { # has an element in scope
7712              !!!cp ('t413');
7713              !!!parse-error (type => 'unmatched end tag',
7714                              text => $token->{tag_name}, token => $token);
7715              ## NOTE: Ignore the token.
7716            } else {
7717              ## Step 1. generate implied end tags
7718              while ({
7719                      ## END_TAG_OPTIONAL_EL
7720                      dd => ($token->{tag_name} ne 'dd'),
7721                      dt => ($token->{tag_name} ne 'dt'),
7722                      li => ($token->{tag_name} ne 'li'),
7723                      option => 1,
7724                      optgroup => 1,
7725                      p => 1,
7726                      rt => 1,
7727                      rp => 1,
7728                     }->{$self->{open_elements}->[-1]->[0]->manakai_local_name}) {
7729                !!!cp ('t409');
7730                pop @{$self->{open_elements}};
7731              }
7732    
7733              ## Step 2.
7734              if ($self->{open_elements}->[-1]->[0]->manakai_local_name
7735                      ne $token->{tag_name}) {
7736                !!!cp ('t412');
7737                !!!parse-error (type => 'not closed',
7738                                text => $self->{open_elements}->[-1]->[0]
7739                                    ->manakai_local_name,
7740                                token => $token);
7741              } else {
7742                !!!cp ('t414');
7743              }
7744    
7745              ## Step 3.
7746              splice @{$self->{open_elements}}, $i;
7747    
7748              ## Step 4.
7749              $clear_up_to_marker->()
7750                  if {
7751                    applet => 1, button => 1, marquee => 1, object => 1,
7752                  }->{$token->{tag_name}};
7753            }
7754            !!!next-token;
7755            next B;
7756          } elsif ($token->{tag_name} eq 'form') {
7757            ## NOTE: As normal, but interacts with the form element pointer
7758    
7759            undef $self->{form_element};
7760    
7761            ## has an element in scope
7762            my $i;
7763            INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
7764              my $node = $self->{open_elements}->[$_];
7765              if ($node->[1] & FORM_EL) {
7766                !!!cp ('t418');
7767                $i = $_;
7768                last INSCOPE;
7769              } elsif ($node->[1] & SCOPING_EL) {
7770                !!!cp ('t419');
7771                last INSCOPE;
7772              }
7773            } # INSCOPE
7774    
7775            unless (defined $i) { # has an element in scope
7776              !!!cp ('t421');
7777              !!!parse-error (type => 'unmatched end tag',
7778                              text => $token->{tag_name}, token => $token);
7779              ## NOTE: Ignore the token.
7780            } else {
7781              ## Step 1. generate implied end tags
7782              while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
7783                !!!cp ('t417');
7784                pop @{$self->{open_elements}};
7785              }
7786                        
7787            $self->{open_elements}->[-1]->[0]->manakai_append_text ($data);            ## Step 2.
7788              if ($self->{open_elements}->[-1]->[0]->manakai_local_name
7789                      ne $token->{tag_name}) {
7790                !!!cp ('t417.1');
7791                !!!parse-error (type => 'not closed',
7792                                text => $self->{open_elements}->[-1]->[0]
7793                                    ->manakai_local_name,
7794                                token => $token);
7795              } else {
7796                !!!cp ('t420');
7797              }  
7798                        
7799            unless (length $token->{data}) {            ## Step 3.
7800              !!!next-token;            splice @{$self->{open_elements}}, $i;
7801              redo B;          }
7802    
7803            !!!next-token;
7804            next B;
7805          } elsif ({
7806                    ## NOTE: As normal, except acts as a closer for any ...
7807                    h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,
7808                   }->{$token->{tag_name}}) {
7809            ## has an element in scope
7810            my $i;
7811            INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
7812              my $node = $self->{open_elements}->[$_];
7813              if ($node->[1] & HEADING_EL) {
7814                !!!cp ('t423');
7815                $i = $_;
7816                last INSCOPE;
7817              } elsif ($node->[1] & SCOPING_EL) {
7818                !!!cp ('t424');
7819                last INSCOPE;
7820              }
7821            } # INSCOPE
7822    
7823            unless (defined $i) { # has an element in scope
7824              !!!cp ('t425.1');
7825              !!!parse-error (type => 'unmatched end tag',
7826                              text => $token->{tag_name}, token => $token);
7827              ## NOTE: Ignore the token.
7828            } else {
7829              ## Step 1. generate implied end tags
7830              while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
7831                !!!cp ('t422');
7832                pop @{$self->{open_elements}};
7833              }
7834              
7835              ## Step 2.
7836              if ($self->{open_elements}->[-1]->[0]->manakai_local_name
7837                      ne $token->{tag_name}) {
7838                !!!cp ('t425');
7839                !!!parse-error (type => 'unmatched end tag',
7840                                text => $token->{tag_name}, token => $token);
7841              } else {
7842                !!!cp ('t426');
7843            }            }
7844    
7845              ## Step 3.
7846              splice @{$self->{open_elements}}, $i;
7847          }          }
7848            
7849            !!!next-token;
7850            next B;
7851          } elsif ($token->{tag_name} eq 'p') {
7852            ## NOTE: As normal, except </p> implies <p> and ...
7853    
7854          !!!parse-error (type => 'after html:#character');          ## has an element in scope
7855          $self->{insertion_mode} = $previous_insertion_mode;          my $non_optional;
7856          ## reprocess          my $i;
7857          redo B;          INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
7858        } elsif ($token->{type} eq 'start tag') {            my $node = $self->{open_elements}->[$_];
7859          !!!parse-error (type => 'after html:'.$token->{tag_name});            if ($node->[1] & P_EL) {
7860          $self->{insertion_mode} = $previous_insertion_mode;              !!!cp ('t410.1');
7861          ## reprocess              $i = $_;
7862          redo B;              last INSCOPE;
7863        } elsif ($token->{type} eq 'end tag') {            } elsif ($node->[1] & SCOPING_EL) {
7864          !!!parse-error (type => 'after html:/'.$token->{tag_name});              !!!cp ('t411.1');
7865          $self->{insertion_mode} = $previous_insertion_mode;              last INSCOPE;
7866          ## reprocess            } elsif ($node->[1] & END_TAG_OPTIONAL_EL) {
7867          redo B;              ## NOTE: |END_TAG_OPTIONAL_EL| includes "p"
7868                !!!cp ('t411.2');
7869                #
7870              } else {
7871                !!!cp ('t411.3');
7872                $non_optional ||= $node;
7873                #
7874              }
7875            } # INSCOPE
7876    
7877            if (defined $i) {
7878              ## 1. Generate implied end tags
7879              #
7880    
7881              ## 2. If current node != "p", parse error
7882              if ($non_optional) {
7883                !!!cp ('t412.1');
7884                !!!parse-error (type => 'not closed',
7885                                text => $non_optional->[0]->manakai_local_name,
7886                                token => $token);
7887              } else {
7888                !!!cp ('t414.1');
7889              }
7890    
7891              ## 3. Pop
7892              splice @{$self->{open_elements}}, $i;
7893            } else {
7894              !!!cp ('t413.1');
7895              !!!parse-error (type => 'unmatched end tag',
7896                              text => $token->{tag_name}, token => $token);
7897    
7898              !!!cp ('t415.1');
7899              ## As if <p>, then reprocess the current token
7900              my $el;
7901              !!!create-element ($el, $HTML_NS, 'p',, $token);
7902              $insert->($el);
7903              ## NOTE: Not inserted into |$self->{open_elements}|.
7904            }
7905    
7906            !!!next-token;
7907            next B;
7908          } elsif ({
7909                    a => 1,
7910                    b => 1, big => 1, em => 1, font => 1, i => 1,
7911                    nobr => 1, s => 1, small => 1, strike => 1,
7912                    strong => 1, tt => 1, u => 1,
7913                   }->{$token->{tag_name}}) {
7914            !!!cp ('t427');
7915            $formatting_end_tag->($token);
7916            next B;
7917          } elsif ($token->{tag_name} eq 'br') {
7918            !!!cp ('t428');
7919            !!!parse-error (type => 'unmatched end tag',
7920                            text => 'br', token => $token);
7921    
7922            ## As if <br>
7923            $reconstruct_active_formatting_elements->($insert_to_current);
7924            
7925            my $el;
7926            !!!create-element ($el, $HTML_NS, 'br',, $token);
7927            $insert->($el);
7928            
7929            ## Ignore the token.
7930            !!!next-token;
7931            next B;
7932        } else {        } else {
7933          die "$0: $token->{type}: Unknown token";          if ($token->{tag_name} eq 'sarcasm') {
7934              sleep 0.001; # take a deep breath
7935            }
7936    
7937            ## Step 1
7938            my $node_i = -1;
7939            my $node = $self->{open_elements}->[$node_i];
7940    
7941            ## Step 2
7942            S2: {
7943              my $node_tag_name = $node->[0]->manakai_local_name;
7944              $node_tag_name =~ tr/A-Z/a-z/; # for SVG camelCase tag names
7945              if ($node_tag_name eq $token->{tag_name}) {
7946                ## Step 1
7947                ## generate implied end tags
7948                while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
7949                  !!!cp ('t430');
7950                  ## NOTE: |<ruby><rt></ruby>|.
7951                  ## ISSUE: <ruby><rt></rt> will also take this code path,
7952                  ## which seems wrong.
7953                  pop @{$self->{open_elements}};
7954                  $node_i++;
7955                }
7956            
7957                ## Step 2
7958                my $current_tag_name
7959                    = $self->{open_elements}->[-1]->[0]->manakai_local_name;
7960                $current_tag_name =~ tr/A-Z/a-z/;
7961                if ($current_tag_name ne $token->{tag_name}) {
7962                  !!!cp ('t431');
7963                  ## NOTE: <x><y></x>
7964                  !!!parse-error (type => 'not closed',
7965                                  text => $self->{open_elements}->[-1]->[0]
7966                                      ->manakai_local_name,
7967                                  token => $token);
7968                } else {
7969                  !!!cp ('t432');
7970                }
7971                
7972                ## Step 3
7973                splice @{$self->{open_elements}}, $node_i if $node_i < 0;
7974    
7975                !!!next-token;
7976                last S2;
7977              } else {
7978                ## Step 3
7979                if (not ($node->[1] & FORMATTING_EL) and
7980                    #not $phrasing_category->{$node->[1]} and
7981                    ($node->[1] & SPECIAL_EL or
7982                     $node->[1] & SCOPING_EL)) {
7983                  !!!cp ('t433');
7984                  !!!parse-error (type => 'unmatched end tag',
7985                                  text => $token->{tag_name}, token => $token);
7986                  ## Ignore the token
7987                  !!!next-token;
7988                  last S2;
7989    
7990                  ## NOTE: |<span><dd></span>a|: In Safari 3.1.2 and Opera
7991                  ## 9.27, "a" is a child of <dd> (conforming).  In
7992                  ## Firefox 3.0.2, "a" is a child of <body>.  In WinIE 7,
7993                  ## "a" is a child of both <body> and <dd>.
7994                }
7995                
7996                !!!cp ('t434');
7997              }
7998              
7999              ## Step 4
8000              $node_i--;
8001              $node = $self->{open_elements}->[$node_i];
8002              
8003              ## Step 5;
8004              redo S2;
8005            } # S2
8006            next B;
8007        }        }
8008      } else {      }
8009        die "$0: $self->{insertion_mode}: Unknown insertion mode";      next B;
8010      } continue { # B
8011        if ($self->{insertion_mode} & IN_FOREIGN_CONTENT_IM) {
8012          ## NOTE: The code below is executed in cases where it does not have
8013          ## to be, but it it is harmless even in those cases.
8014          ## has an element in scope
8015          INSCOPE: {
8016            for (reverse 0..$#{$self->{open_elements}}) {
8017              my $node = $self->{open_elements}->[$_];
8018              if ($node->[1] & FOREIGN_EL) {
8019                last INSCOPE;
8020              } elsif ($node->[1] & SCOPING_EL) {
8021                last;
8022              }
8023            }
8024            
8025            ## NOTE: No foreign element in scope.
8026            $self->{insertion_mode} &= ~ IN_FOREIGN_CONTENT_IM;
8027          } # INSCOPE
8028      }      }
8029    } # B    } # B
8030    
# Line 4970  sub _tree_construction_main ($) { Line 8033  sub _tree_construction_main ($) {
8033    ## TODO: script stuffs    ## TODO: script stuffs
8034  } # _tree_construct_main  } # _tree_construct_main
8035    
8036  sub set_inner_html ($$$) {  sub set_inner_html ($$$$;$) {
8037    my $class = shift;    my $class = shift;
8038    my $node = shift;    my $node = shift;
8039    my $s = \$_[0];    #my $s = \$_[0];
8040    my $onerror = $_[1];    my $onerror = $_[1];
8041      my $get_wrapper = $_[2] || sub ($) { return $_[0] };
8042    
8043      ## ISSUE: Should {confident} be true?
8044    
8045    my $nt = $node->node_type;    my $nt = $node->node_type;
8046    if ($nt == 9) {    if ($nt == 9) {
# Line 4991  sub set_inner_html ($$$) { Line 8057  sub set_inner_html ($$$) {
8057      }      }
8058    
8059      ## Step 3, 4, 5 # MUST      ## Step 3, 4, 5 # MUST
8060      $class->parse_string ($$s => $node, $onerror);      $class->parse_char_string ($_[0] => $node, $onerror, $get_wrapper);
8061    } elsif ($nt == 1) {    } elsif ($nt == 1) {
8062      ## TODO: If non-html element      ## TODO: If non-html element
8063    
8064      ## NOTE: Most of this code is copied from |parse_string|      ## NOTE: Most of this code is copied from |parse_string|
8065    
8066    ## TODO: Support for $get_wrapper
8067    
8068      ## Step 1 # MUST      ## Step 1 # MUST
8069      my $this_doc = $node->owner_document;      my $this_doc = $node->owner_document;
8070      my $doc = $this_doc->implementation->create_document;      my $doc = $this_doc->implementation->create_document;
# Line 5004  sub set_inner_html ($$$) { Line 8072  sub set_inner_html ($$$) {
8072      my $p = $class->new;      my $p = $class->new;
8073      $p->{document} = $doc;      $p->{document} = $doc;
8074    
8075      ## Step 9 # MUST      ## Step 8 # MUST
8076      my $i = 0;      my $i = 0;
8077      my $line = 1;      $p->{line_prev} = $p->{line} = 1;
8078      my $column = 0;      $p->{column_prev} = $p->{column} = 0;
8079      $p->{set_next_input_character} = sub {      require Whatpm::Charset::DecodeHandle;
8080        my $input = Whatpm::Charset::DecodeHandle::CharString->new (\($_[0]));
8081        $input = $get_wrapper->($input);
8082        $p->{set_nc} = sub {
8083        my $self = shift;        my $self = shift;
8084    
8085        pop @{$self->{prev_input_character}};        my $char = '';
8086        unshift @{$self->{prev_input_character}}, $self->{next_input_character};        if (defined $self->{next_nc}) {
8087            $char = $self->{next_nc};
8088            delete $self->{next_nc};
8089            $self->{nc} = ord $char;
8090          } else {
8091            $self->{char_buffer} = '';
8092            $self->{char_buffer_pos} = 0;
8093            
8094            my $count = $input->manakai_read_until
8095                ($self->{char_buffer}, qr/[^\x00\x0A\x0D]/,
8096                 $self->{char_buffer_pos});
8097            if ($count) {
8098              $self->{line_prev} = $self->{line};
8099              $self->{column_prev} = $self->{column};
8100              $self->{column}++;
8101              $self->{nc}
8102                  = ord substr ($self->{char_buffer},
8103                                $self->{char_buffer_pos}++, 1);
8104              return;
8105            }
8106            
8107            if ($input->read ($char, 1)) {
8108              $self->{nc} = ord $char;
8109            } else {
8110              $self->{nc} = -1;
8111              return;
8112            }
8113          }
8114    
8115          ($p->{line_prev}, $p->{column_prev}) = ($p->{line}, $p->{column});
8116          $p->{column}++;
8117    
8118        $self->{next_input_character} = -1 and return if $i >= length $$s;        if ($self->{nc} == 0x000A) { # LF
8119        $self->{next_input_character} = ord substr $$s, $i++, 1;          $p->{line}++;
8120        $column++;          $p->{column} = 0;
8121            !!!cp ('i1');
8122        if ($self->{next_input_character} == 0x000A) { # LF        } elsif ($self->{nc} == 0x000D) { # CR
8123          $line++;  ## TODO: support for abort/streaming
8124          $column = 0;          my $next = '';
8125        } elsif ($self->{next_input_character} == 0x000D) { # CR          if ($input->read ($next, 1) and $next ne "\x0A") {
8126          $i++ if substr ($$s, $i, 1) eq "\x0A";            $self->{next_nc} = $next;
8127          $self->{next_input_character} = 0x000A; # LF # MUST          }
8128          $line++;          $self->{nc} = 0x000A; # LF # MUST
8129          $column = 0;          $p->{line}++;
8130        } elsif ($self->{next_input_character} > 0x10FFFF) {          $p->{column} = 0;
8131          $self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST          !!!cp ('i2');
8132        } elsif ($self->{next_input_character} == 0x0000) { # NULL        } elsif ($self->{nc} == 0x0000) { # NULL
8133            !!!cp ('i4');
8134          !!!parse-error (type => 'NULL');          !!!parse-error (type => 'NULL');
8135          $self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST          $self->{nc} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
8136        }        }
8137      };      };
8138      $p->{prev_input_character} = [-1, -1, -1];  
8139      $p->{next_input_character} = -1;      $p->{read_until} = sub {
8140              #my ($scalar, $specials_range, $offset) = @_;
8141          return 0 if defined $p->{next_nc};
8142    
8143          my $pattern = qr/[^$_[1]\x00\x0A\x0D]/;
8144          my $offset = $_[2] || 0;
8145          
8146          if ($p->{char_buffer_pos} < length $p->{char_buffer}) {
8147            pos ($p->{char_buffer}) = $p->{char_buffer_pos};
8148            if ($p->{char_buffer} =~ /\G(?>$pattern)+/) {
8149              substr ($_[0], $offset)
8150                  = substr ($p->{char_buffer}, $-[0], $+[0] - $-[0]);
8151              my $count = $+[0] - $-[0];
8152              if ($count) {
8153                $p->{column} += $count;
8154                $p->{char_buffer_pos} += $count;
8155                $p->{line_prev} = $p->{line};
8156                $p->{column_prev} = $p->{column} - 1;
8157                $p->{nc} = -1;
8158              }
8159              return $count;
8160            } else {
8161              return 0;
8162            }
8163          } else {
8164            my $count = $input->manakai_read_until ($_[0], $pattern, $_[2]);
8165            if ($count) {
8166              $p->{column} += $count;
8167              $p->{column_prev} += $count;
8168              $p->{nc} = -1;
8169            }
8170            return $count;
8171          }
8172        }; # $p->{read_until}
8173    
8174      my $ponerror = $onerror || sub {      my $ponerror = $onerror || sub {
8175        my (%opt) = @_;        my (%opt) = @_;
8176        warn "Parse error ($opt{type}) at line $opt{line} column $opt{column}\n";        my $line = $opt{line};
8177          my $column = $opt{column};
8178          if (defined $opt{token} and defined $opt{token}->{line}) {
8179            $line = $opt{token}->{line};
8180            $column = $opt{token}->{column};
8181          }
8182          warn "Parse error ($opt{type}) at line $line column $column\n";
8183      };      };
8184      $p->{parse_error} = sub {      $p->{parse_error} = sub {
8185        $ponerror->(@_, line => $line, column => $column);        $ponerror->(line => $p->{line}, column => $p->{column}, @_);
8186      };      };
8187            
8188        my $char_onerror = sub {
8189          my (undef, $type, %opt) = @_;
8190          $ponerror->(layer => 'encode',
8191                      line => $p->{line}, column => $p->{column} + 1,
8192                      %opt, type => $type);
8193        }; # $char_onerror
8194        $input->onerror ($char_onerror);
8195    
8196      $p->_initialize_tokenizer;      $p->_initialize_tokenizer;
8197      $p->_initialize_tree_constructor;      $p->_initialize_tree_constructor;
8198    
8199      ## Step 2      ## Step 2
8200      my $node_ln = $node->local_name;      my $node_ln = $node->manakai_local_name;
8201      $p->{content_model} = {      $p->{content_model} = {
8202        title => RCDATA_CONTENT_MODEL,        title => RCDATA_CONTENT_MODEL,
8203        textarea => RCDATA_CONTENT_MODEL,        textarea => RCDATA_CONTENT_MODEL,
# Line 5065  sub set_inner_html ($$$) { Line 8214  sub set_inner_html ($$$) {
8214          unless defined $p->{content_model};          unless defined $p->{content_model};
8215          ## ISSUE: What is "the name of the element"? local name?          ## ISSUE: What is "the name of the element"? local name?
8216    
8217      $p->{inner_html_node} = [$node, $node_ln];      $p->{inner_html_node} = [$node, $el_category->{$node_ln}];
8218          ## TODO: Foreign element OK?
8219    
8220      ## Step 4      ## Step 3
8221      my $root = $doc->create_element_ns      my $root = $doc->create_element_ns
8222        ('http://www.w3.org/1999/xhtml', [undef, 'html']);        ('http://www.w3.org/1999/xhtml', [undef, 'html']);
8223    
8224      ## Step 5 # MUST      ## Step 4 # MUST
8225      $doc->append_child ($root);      $doc->append_child ($root);
8226    
8227      ## Step 6 # MUST      ## Step 5 # MUST
8228      push @{$p->{open_elements}}, [$root, 'html'];      push @{$p->{open_elements}}, [$root, $el_category->{html}];
8229    
8230      undef $p->{head_element};      undef $p->{head_element};
8231        undef $p->{head_element_inserted};
8232    
8233      ## Step 7 # MUST      ## Step 6 # MUST
8234      $p->_reset_insertion_mode;      $p->_reset_insertion_mode;
8235    
8236      ## Step 8 # MUST      ## Step 7 # MUST
8237      my $anode = $node;      my $anode = $node;
8238      AN: while (defined $anode) {      AN: while (defined $anode) {
8239        if ($anode->node_type == 1) {        if ($anode->node_type == 1) {
8240          my $nsuri = $anode->namespace_uri;          my $nsuri = $anode->namespace_uri;
8241          if (defined $nsuri and $nsuri eq 'http://www.w3.org/1999/xhtml') {          if (defined $nsuri and $nsuri eq 'http://www.w3.org/1999/xhtml') {
8242            if ($anode->local_name eq 'form') { ## TODO: case?            if ($anode->manakai_local_name eq 'form') {
8243                !!!cp ('i5');
8244              $p->{form_element} = $anode;              $p->{form_element} = $anode;
8245              last AN;              last AN;
8246            }            }
# Line 5097  sub set_inner_html ($$$) { Line 8249  sub set_inner_html ($$$) {
8249        $anode = $anode->parent_node;        $anode = $anode->parent_node;
8250      } # AN      } # AN
8251            
8252      ## Step 3 # MUST      ## Step 9 # MUST
     ## Step 10 # MUST  
8253      {      {
8254        my $self = $p;        my $self = $p;
8255        !!!next-token;        !!!next-token;
8256      }      }
8257      $p->_tree_construction_main;      $p->_tree_construction_main;
8258    
8259      ## Step 11 # MUST      ## Step 10 # MUST
8260      my @cn = @{$node->child_nodes};      my @cn = @{$node->child_nodes};
8261      for (@cn) {      for (@cn) {
8262        $node->remove_child ($_);        $node->remove_child ($_);
8263      }      }
8264      ## ISSUE: mutation events? read-only?      ## ISSUE: mutation events? read-only?
8265    
8266      ## Step 12 # MUST      ## Step 11 # MUST
8267      @cn = @{$root->child_nodes};      @cn = @{$root->child_nodes};
8268      for (@cn) {      for (@cn) {
8269        $this_doc->adopt_node ($_);        $this_doc->adopt_node ($_);
# Line 5121  sub set_inner_html ($$$) { Line 8272  sub set_inner_html ($$$) {
8272      ## ISSUE: mutation events?      ## ISSUE: mutation events?
8273    
8274      $p->_terminate_tree_constructor;      $p->_terminate_tree_constructor;
8275    
8276        delete $p->{parse_error}; # delete loop
8277    } else {    } else {
8278      die "$0: |set_inner_html| is not defined for node of type $nt";      die "$0: |set_inner_html| is not defined for node of type $nt";
8279    }    }
# Line 5128  sub set_inner_html ($$$) { Line 8281  sub set_inner_html ($$$) {
8281    
8282  } # tree construction stage  } # tree construction stage
8283    
8284  sub get_inner_html ($$$) {  package Whatpm::HTML::RestartParser;
8285    my (undef, $node, $on_error) = @_;  push our @ISA, 'Error';
   
   ## Step 1  
   my $s = '';  
   
   my $in_cdata;  
   my $parent = $node;  
   while (defined $parent) {  
     if ($parent->node_type == 1 and  
         $parent->namespace_uri eq 'http://www.w3.org/1999/xhtml' and  
         {  
           style => 1, script => 1, xmp => 1, iframe => 1,  
           noembed => 1, noframes => 1, noscript => 1,  
         }->{$parent->local_name}) { ## TODO: case thingy  
       $in_cdata = 1;  
     }  
     $parent = $parent->parent_node;  
   }  
   
   ## Step 2  
   my @node = @{$node->child_nodes};  
   C: while (@node) {  
     my $child = shift @node;  
     unless (ref $child) {  
       if ($child eq 'cdata-out') {  
         $in_cdata = 0;  
       } else {  
         $s .= $child; # end tag  
       }  
       next C;  
     }  
       
     my $nt = $child->node_type;  
     if ($nt == 1) { # Element  
       my $tag_name = $child->tag_name; ## TODO: manakai_tag_name  
       $s .= '<' . $tag_name;  
       ## NOTE: Non-HTML case:  
       ## <http://permalink.gmane.org/gmane.org.w3c.whatwg.discuss/11191>  
   
       my @attrs = @{$child->attributes}; # sort order MUST be stable  
       for my $attr (@attrs) { # order is implementation dependent  
         my $attr_name = $attr->name; ## TODO: manakai_name  
         $s .= ' ' . $attr_name . '="';  
         my $attr_value = $attr->value;  
         ## escape  
         $attr_value =~ s/&/&amp;/g;  
         $attr_value =~ s/</&lt;/g;  
         $attr_value =~ s/>/&gt;/g;  
         $attr_value =~ s/"/&quot;/g;  
         $s .= $attr_value . '"';  
       }  
       $s .= '>';  
         
       next C if {  
         area => 1, base => 1, basefont => 1, bgsound => 1,  
         br => 1, col => 1, embed => 1, frame => 1, hr => 1,  
         img => 1, input => 1, link => 1, meta => 1, param => 1,  
         spacer => 1, wbr => 1,  
       }->{$tag_name};  
   
       $s .= "\x0A" if $tag_name eq 'pre' or $tag_name eq 'textarea';  
   
       if (not $in_cdata and {  
         style => 1, script => 1, xmp => 1, iframe => 1,  
         noembed => 1, noframes => 1, noscript => 1,  
         plaintext => 1,  
       }->{$tag_name}) {  
         unshift @node, 'cdata-out';  
         $in_cdata = 1;  
       }  
   
       unshift @node, @{$child->child_nodes}, '</' . $tag_name . '>';  
     } elsif ($nt == 3 or $nt == 4) {  
       if ($in_cdata) {  
         $s .= $child->data;  
       } else {  
         my $value = $child->data;  
         $value =~ s/&/&amp;/g;  
         $value =~ s/</&lt;/g;  
         $value =~ s/>/&gt;/g;  
         $value =~ s/"/&quot;/g;  
         $s .= $value;  
       }  
     } elsif ($nt == 8) {  
       $s .= '<!--' . $child->data . '-->';  
     } elsif ($nt == 10) {  
       $s .= '<!DOCTYPE ' . $child->name . '>';  
     } elsif ($nt == 5) { # entrefs  
       push @node, @{$child->child_nodes};  
     } else {  
       $on_error->($child) if defined $on_error;  
     }  
     ## ISSUE: This code does not support PIs.  
   } # C  
     
   ## Step 3  
   return \$s;  
 } # get_inner_html  
8286    
8287  1;  1;
8288  # $Date$  # $Date$

Legend:
Removed from v.1.48  
changed lines
  Added in v.1.204

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24