package Whatpm::HTML;
use strict;
our $VERSION=do{my @r=(q$Revision: 1.241 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
use Error qw(:try);

use Whatpm::HTML::Tokenizer;

## NOTE: This module don't check all HTML5 parse errors; character
## encoding related parse errors are expected to be handled by relevant
## modules.
## Parse errors for control characters that are not allowed in HTML5
## documents, for surrogate code points, and for noncharacter code
## points, as well as U+FFFD substitions for characters whose code points
## is higher than U+10FFFF may be detected by combining the parser with
## the checker implemented by Whatpm::Charset::UnicodeChecker (for its
## usage example, see |t/HTML-tree.t| in the Whatpm package or the 
## WebHACC::Language::HTML module in the WebHACC package).

## ISSUE:
## var doc = implementation.createDocument (null, null, null);
## doc.write ('');
## alert (doc.compatMode);

require IO::Handle;

## Namespace URLs

my $HTML_NS = q<http://www.w3.org/1999/xhtml>;
my $MML_NS = q<http://www.w3.org/1998/Math/MathML>;
my $SVG_NS = q<http://www.w3.org/2000/svg>;
my $XLINK_NS = q<http://www.w3.org/1999/xlink>;
my $XML_NS = q<http://www.w3.org/XML/1998/namespace>;
my $XMLNS_NS = q<http://www.w3.org/2000/xmlns/>;

## Element categories

## Bits 12-15
sub SPECIAL_EL () { 0b1_000000000000000 }
sub SCOPING_EL () { 0b1_00000000000000 }
sub FORMATTING_EL () { 0b1_0000000000000 }
sub PHRASING_EL () { 0b1_000000000000 }

## Bits 10-11
#sub FOREIGN_EL () { 0b1_00000000000 } # see Whatpm::HTML::Tokenizer
sub FOREIGN_FLOW_CONTENT_EL () { 0b1_0000000000 }

## Bits 6-9
sub TABLE_SCOPING_EL () { 0b1_000000000 }
sub TABLE_ROWS_SCOPING_EL () { 0b1_00000000 }
sub TABLE_ROW_SCOPING_EL () { 0b1_0000000 }
sub TABLE_ROWS_EL () { 0b1_000000 }

## Bit 5
sub ADDRESS_DIV_P_EL () { 0b1_00000 }

## NOTE: Used in </body> and EOF algorithms.
## Bit 4
sub ALL_END_TAG_OPTIONAL_EL () { 0b1_0000 }

## NOTE: Used in "generate implied end tags" algorithm.
## NOTE: There is a code where a modified version of
## END_TAG_OPTIONAL_EL is used in "generate implied end tags"
## implementation (search for the algorithm name).
## Bit 3
sub END_TAG_OPTIONAL_EL () { 0b1_000 }

## Bits 0-2

sub MISC_SPECIAL_EL () { SPECIAL_EL | 0b000 }
sub FORM_EL () { SPECIAL_EL | 0b001 }
sub FRAMESET_EL () { SPECIAL_EL | 0b010 }
sub HEADING_EL () { SPECIAL_EL | 0b011 }
sub SELECT_EL () { SPECIAL_EL | 0b100 }
sub SCRIPT_EL () { SPECIAL_EL | 0b101 }

sub ADDRESS_DIV_EL () { SPECIAL_EL | ADDRESS_DIV_P_EL | 0b001 }
sub BODY_EL () { SPECIAL_EL | ALL_END_TAG_OPTIONAL_EL | 0b001 }

sub DTDD_EL () {
  SPECIAL_EL |
  END_TAG_OPTIONAL_EL |
  ALL_END_TAG_OPTIONAL_EL |
  0b010
}
sub LI_EL () {
  SPECIAL_EL |
  END_TAG_OPTIONAL_EL |
  ALL_END_TAG_OPTIONAL_EL |
  0b100
}
sub P_EL () {
  SPECIAL_EL |
  ADDRESS_DIV_P_EL |
  END_TAG_OPTIONAL_EL |
  ALL_END_TAG_OPTIONAL_EL |
  0b001
}

sub TABLE_ROW_EL () {
  SPECIAL_EL |
  TABLE_ROWS_EL |
  TABLE_ROW_SCOPING_EL |
  ALL_END_TAG_OPTIONAL_EL |
  0b001
}
sub TABLE_ROW_GROUP_EL () {
  SPECIAL_EL |
  TABLE_ROWS_EL |
  TABLE_ROWS_SCOPING_EL |
  ALL_END_TAG_OPTIONAL_EL |
  0b001
}

sub MISC_SCOPING_EL () { SCOPING_EL | 0b000 }
sub BUTTON_EL () { SCOPING_EL | 0b001 }
sub CAPTION_EL () { SCOPING_EL | 0b010 }
sub HTML_EL () {
  SCOPING_EL |
  TABLE_SCOPING_EL |
  TABLE_ROWS_SCOPING_EL |
  TABLE_ROW_SCOPING_EL |
  ALL_END_TAG_OPTIONAL_EL |
  0b001
}
sub TABLE_EL () {
  SCOPING_EL |
  TABLE_ROWS_EL |
  TABLE_SCOPING_EL |
  0b001
}
sub TABLE_CELL_EL () {
  SCOPING_EL |
  TABLE_ROW_SCOPING_EL |
  ALL_END_TAG_OPTIONAL_EL |
  0b001
}

sub MISC_FORMATTING_EL () { FORMATTING_EL | 0b000 }
sub A_EL () { FORMATTING_EL | 0b001 }
sub NOBR_EL () { FORMATTING_EL | 0b010 }

sub RUBY_EL () { PHRASING_EL | 0b001 }

## ISSUE: ALL_END_TAG_OPTIONAL_EL?
sub OPTGROUP_EL () { PHRASING_EL | END_TAG_OPTIONAL_EL | 0b001 }
sub OPTION_EL () { PHRASING_EL | END_TAG_OPTIONAL_EL | 0b010 }
sub RUBY_COMPONENT_EL () { PHRASING_EL | END_TAG_OPTIONAL_EL | 0b100 }

sub MML_AXML_EL () { PHRASING_EL | FOREIGN_EL | 0b001 }

my $el_category = {
  a => A_EL,
  address => ADDRESS_DIV_EL,
  applet => MISC_SCOPING_EL,
  area => MISC_SPECIAL_EL,
  article => MISC_SPECIAL_EL,
  aside => MISC_SPECIAL_EL,
  b => FORMATTING_EL,
  base => MISC_SPECIAL_EL,
  basefont => MISC_SPECIAL_EL,
  bgsound => MISC_SPECIAL_EL,
  big => FORMATTING_EL,
  blockquote => MISC_SPECIAL_EL,
  body => BODY_EL,
  br => MISC_SPECIAL_EL,
  button => BUTTON_EL,
  caption => CAPTION_EL,
  center => MISC_SPECIAL_EL,
  col => MISC_SPECIAL_EL,
  colgroup => MISC_SPECIAL_EL,
  command => MISC_SPECIAL_EL,
  datagrid => MISC_SPECIAL_EL,
  dd => DTDD_EL,
  details => MISC_SPECIAL_EL,
  dialog => MISC_SPECIAL_EL,
  dir => MISC_SPECIAL_EL,
  div => ADDRESS_DIV_EL,
  dl => MISC_SPECIAL_EL,
  dt => DTDD_EL,
  em => FORMATTING_EL,
  embed => MISC_SPECIAL_EL,
  fieldset => MISC_SPECIAL_EL,
  figure => MISC_SPECIAL_EL,
  font => FORMATTING_EL,
  footer => MISC_SPECIAL_EL,
  form => FORM_EL,
  frame => MISC_SPECIAL_EL,
  frameset => FRAMESET_EL,
  h1 => HEADING_EL,
  h2 => HEADING_EL,
  h3 => HEADING_EL,
  h4 => HEADING_EL,
  h5 => HEADING_EL,
  h6 => HEADING_EL,
  head => MISC_SPECIAL_EL,
  header => MISC_SPECIAL_EL,
  hgroup => MISC_SPECIAL_EL,
  hr => MISC_SPECIAL_EL,
  html => HTML_EL,
  i => FORMATTING_EL,
  iframe => MISC_SPECIAL_EL,
  img => MISC_SPECIAL_EL,
  #image => MISC_SPECIAL_EL, ## NOTE: Commented out in the spec.
  input => MISC_SPECIAL_EL,
  isindex => MISC_SPECIAL_EL,
  ## XXX keygen? (Whether a void element is in Special or not does not
  ## affect to the processing, however.)
  li => LI_EL,
  link => MISC_SPECIAL_EL,
  listing => MISC_SPECIAL_EL,
  marquee => MISC_SCOPING_EL,
  menu => MISC_SPECIAL_EL,
  meta => MISC_SPECIAL_EL,
  nav => MISC_SPECIAL_EL,
  nobr => NOBR_EL,
  noembed => MISC_SPECIAL_EL,
  noframes => MISC_SPECIAL_EL,
  noscript => MISC_SPECIAL_EL,
  object => MISC_SCOPING_EL,
  ol => MISC_SPECIAL_EL,
  optgroup => OPTGROUP_EL,
  option => OPTION_EL,
  p => P_EL,
  param => MISC_SPECIAL_EL,
  plaintext => MISC_SPECIAL_EL,
  pre => MISC_SPECIAL_EL,
  rp => RUBY_COMPONENT_EL,
  rt => RUBY_COMPONENT_EL,
  ruby => RUBY_EL,
  s => FORMATTING_EL,
  script => MISC_SPECIAL_EL,
  select => SELECT_EL,
  section => MISC_SPECIAL_EL,
  small => FORMATTING_EL,
  spacer => MISC_SPECIAL_EL,
  strike => FORMATTING_EL,
  strong => FORMATTING_EL,
  style => MISC_SPECIAL_EL,
  table => TABLE_EL,
  tbody => TABLE_ROW_GROUP_EL,
  td => TABLE_CELL_EL,
  textarea => MISC_SPECIAL_EL,
  tfoot => TABLE_ROW_GROUP_EL,
  th => TABLE_CELL_EL,
  thead => TABLE_ROW_GROUP_EL,
  title => MISC_SPECIAL_EL,
  tr => TABLE_ROW_EL,
  tt => FORMATTING_EL,
  u => FORMATTING_EL,
  ul => MISC_SPECIAL_EL,
  wbr => MISC_SPECIAL_EL,
  xmp => MISC_SPECIAL_EL,
};

my $el_category_f = {
  $MML_NS => {
    'annotation-xml' => MML_AXML_EL,
    mi => FOREIGN_EL | FOREIGN_FLOW_CONTENT_EL,
    mo => FOREIGN_EL | FOREIGN_FLOW_CONTENT_EL,
    mn => FOREIGN_EL | FOREIGN_FLOW_CONTENT_EL,
    ms => FOREIGN_EL | FOREIGN_FLOW_CONTENT_EL,
    mtext => FOREIGN_EL | FOREIGN_FLOW_CONTENT_EL,
  },
  $SVG_NS => {
    foreignObject => SCOPING_EL | FOREIGN_EL | FOREIGN_FLOW_CONTENT_EL,
    desc => FOREIGN_EL | FOREIGN_FLOW_CONTENT_EL,
    title => FOREIGN_EL | FOREIGN_FLOW_CONTENT_EL,
  },
  ## NOTE: In addition, FOREIGN_EL is set to non-HTML elements.
};

my $svg_attr_name = {
  attributename => 'attributeName',
  attributetype => 'attributeType',
  basefrequency => 'baseFrequency',
  baseprofile => 'baseProfile',
  calcmode => 'calcMode',
  clippathunits => 'clipPathUnits',
  contentscripttype => 'contentScriptType',
  contentstyletype => 'contentStyleType',
  diffuseconstant => 'diffuseConstant',
  edgemode => 'edgeMode',
  externalresourcesrequired => 'externalResourcesRequired',
  filterres => 'filterRes',
  filterunits => 'filterUnits',
  glyphref => 'glyphRef',
  gradienttransform => 'gradientTransform',
  gradientunits => 'gradientUnits',
  kernelmatrix => 'kernelMatrix',
  kernelunitlength => 'kernelUnitLength',
  keypoints => 'keyPoints',
  keysplines => 'keySplines',
  keytimes => 'keyTimes',
  lengthadjust => 'lengthAdjust',
  limitingconeangle => 'limitingConeAngle',
  markerheight => 'markerHeight',
  markerunits => 'markerUnits',
  markerwidth => 'markerWidth',
  maskcontentunits => 'maskContentUnits',
  maskunits => 'maskUnits',
  numoctaves => 'numOctaves',
  pathlength => 'pathLength',
  patterncontentunits => 'patternContentUnits',
  patterntransform => 'patternTransform',
  patternunits => 'patternUnits',
  pointsatx => 'pointsAtX',
  pointsaty => 'pointsAtY',
  pointsatz => 'pointsAtZ',
  preservealpha => 'preserveAlpha',
  preserveaspectratio => 'preserveAspectRatio',
  primitiveunits => 'primitiveUnits',
  refx => 'refX',
  refy => 'refY',
  repeatcount => 'repeatCount',
  repeatdur => 'repeatDur',
  requiredextensions => 'requiredExtensions',
  requiredfeatures => 'requiredFeatures',
  specularconstant => 'specularConstant',
  specularexponent => 'specularExponent',
  spreadmethod => 'spreadMethod',
  startoffset => 'startOffset',
  stddeviation => 'stdDeviation',
  stitchtiles => 'stitchTiles',
  surfacescale => 'surfaceScale',
  systemlanguage => 'systemLanguage',
  tablevalues => 'tableValues',
  targetx => 'targetX',
  targety => 'targetY',
  textlength => 'textLength',
  viewbox => 'viewBox',
  viewtarget => 'viewTarget',
  xchannelselector => 'xChannelSelector',
  ychannelselector => 'yChannelSelector',
  zoomandpan => 'zoomAndPan',
};

my $foreign_attr_xname = {
  'xlink:actuate' => [$XLINK_NS, ['xlink', 'actuate']],
  'xlink:arcrole' => [$XLINK_NS, ['xlink', 'arcrole']],
  'xlink:href' => [$XLINK_NS, ['xlink', 'href']],
  'xlink:role' => [$XLINK_NS, ['xlink', 'role']],
  'xlink:show' => [$XLINK_NS, ['xlink', 'show']],
  'xlink:title' => [$XLINK_NS, ['xlink', 'title']],
  'xlink:type' => [$XLINK_NS, ['xlink', 'type']],
  'xml:base' => [$XML_NS, ['xml', 'base']],
  'xml:lang' => [$XML_NS, ['xml', 'lang']],
  'xml:space' => [$XML_NS, ['xml', 'space']],
  'xmlns' => [$XMLNS_NS, [undef, 'xmlns']],
  'xmlns:xlink' => [$XMLNS_NS, ['xmlns', 'xlink']],
};

## ISSUE: xmlns:xlink="non-xlink-ns" is not an error.

## TODO: Invoke the reset algorithm when a resettable element is
## created (cf. HTML5 revision 2259).

sub parse_byte_string ($$$$;$) {
  my $self = shift;
  my $charset_name = shift;
  open my $input, '<', ref $_[0] ? $_[0] : \($_[0]);
  return $self->parse_byte_stream ($charset_name, $input, @_[1..$#_]);
} # parse_byte_string

sub parse_byte_stream ($$$$;$$) {
  # my ($self, $charset_name, $byte_stream, $doc, $onerror, $get_wrapper) = @_;
  my $self = ref $_[0] ? shift : shift->new;
  my $charset_name = shift;
  my $byte_stream = $_[0];

  my $onerror = $_[2] || sub {
    my (%opt) = @_;
    warn "Parse error ($opt{type})\n";
  };
  $self->{parse_error} = $onerror; # updated later by parse_char_string

  my $get_wrapper = $_[3] || sub ($) {
    return $_[0]; # $_[0] = byte stream handle, returned = arg to char handle
  };

  ## HTML5 encoding sniffing algorithm
  require Message::Charset::Info;
  my $charset;
  my $buffer;
  my ($char_stream, $e_status);

  SNIFFING: {
    ## NOTE: By setting |allow_fallback| option true when the
    ## |get_decode_handle| method is invoked, we ignore what the HTML5
    ## spec requires, i.e. unsupported encoding should be ignored.
      ## TODO: We should not do this unless the parser is invoked
      ## in the conformance checking mode, in which this behavior
      ## would be useful.

    ## Step 1
    if (defined $charset_name) {
      $charset = Message::Charset::Info->get_by_html_name ($charset_name);
          ## TODO: Is this ok?  Transfer protocol's parameter should be
          ## interpreted in its semantics?

      ($char_stream, $e_status) = $charset->get_decode_handle
          ($byte_stream, allow_error_reporting => 1,
           allow_fallback => 1);
      if ($char_stream) {
        $self->{confident} = 1;
        last SNIFFING;
      } else {
        !!!parse-error (type => 'charset:not supported',
                        layer => 'encode',
                        line => 1, column => 1,
                        value => $charset_name,
                        level => $self->{level}->{uncertain});
      }
    }

    ## Step 2
    my $byte_buffer = '';
    for (1..1024) {
      my $char = $byte_stream->getc;
      last unless defined $char;
      $byte_buffer .= $char;
    } ## TODO: timeout

    ## Step 3
    if ($byte_buffer =~ /^\xFE\xFF/) {
      $charset = Message::Charset::Info->get_by_html_name ('utf-16be');
      ($char_stream, $e_status) = $charset->get_decode_handle
          ($byte_stream, allow_error_reporting => 1,
           allow_fallback => 1, byte_buffer => \$byte_buffer);
      $self->{confident} = 1;
      last SNIFFING;
    } elsif ($byte_buffer =~ /^\xFF\xFE/) {
      $charset = Message::Charset::Info->get_by_html_name ('utf-16le');
      ($char_stream, $e_status) = $charset->get_decode_handle
          ($byte_stream, allow_error_reporting => 1,
           allow_fallback => 1, byte_buffer => \$byte_buffer);
      $self->{confident} = 1;
      last SNIFFING;
    } elsif ($byte_buffer =~ /^\xEF\xBB\xBF/) {
      $charset = Message::Charset::Info->get_by_html_name ('utf-8');
      ($char_stream, $e_status) = $charset->get_decode_handle
          ($byte_stream, allow_error_reporting => 1,
           allow_fallback => 1, byte_buffer => \$byte_buffer);
      $self->{confident} = 1;
      last SNIFFING;
    }

    ## Step 4
    ## TODO: <meta charset>

    ## Step 5
    ## TODO: from history

    ## Step 6
    require Whatpm::Charset::UniversalCharDet;
    $charset_name = Whatpm::Charset::UniversalCharDet->detect_byte_string
        ($byte_buffer);
    if (defined $charset_name) {
      $charset = Message::Charset::Info->get_by_html_name ($charset_name);

      require Whatpm::Charset::DecodeHandle;
      $buffer = Whatpm::Charset::DecodeHandle::ByteBuffer->new
          ($byte_stream);
      ($char_stream, $e_status) = $charset->get_decode_handle
          ($buffer, allow_error_reporting => 1,
           allow_fallback => 1, byte_buffer => \$byte_buffer);
      if ($char_stream) {
        $buffer->{buffer} = $byte_buffer;
        !!!parse-error (type => 'sniffing:chardet',
                        text => $charset_name,
                        level => $self->{level}->{info},
                        layer => 'encode',
                        line => 1, column => 1);
        $self->{confident} = 0;
        last SNIFFING;
      }
    }

    ## Step 7: default
    ## TODO: Make this configurable.
    $charset = Message::Charset::Info->get_by_html_name ('windows-1252');
        ## NOTE: We choose |windows-1252| here, since |utf-8| should be 
        ## detectable in the step 6.
    require Whatpm::Charset::DecodeHandle;
    $buffer = Whatpm::Charset::DecodeHandle::ByteBuffer->new
        ($byte_stream);
    ($char_stream, $e_status)
        = $charset->get_decode_handle ($buffer,
                                       allow_error_reporting => 1,
                                       allow_fallback => 1,
                                       byte_buffer => \$byte_buffer);
    $buffer->{buffer} = $byte_buffer;
    !!!parse-error (type => 'sniffing:default',
                    text => 'windows-1252',
                    level => $self->{level}->{info},
                    line => 1, column => 1,
                    layer => 'encode');
    $self->{confident} = 0;
  } # SNIFFING

  if ($e_status & Message::Charset::Info::FALLBACK_ENCODING_IMPL ()) {
    $self->{input_encoding} = $charset->get_iana_name; ## TODO: Should we set actual charset decoder's encoding name?
    !!!parse-error (type => 'chardecode:fallback',
                    #text => $self->{input_encoding},
                    level => $self->{level}->{uncertain},
                    line => 1, column => 1,
                    layer => 'encode');
  } elsif (not ($e_status &
                Message::Charset::Info::ERROR_REPORTING_ENCODING_IMPL ())) {
    $self->{input_encoding} = $charset->get_iana_name;
    !!!parse-error (type => 'chardecode:no error',
                    text => $self->{input_encoding},
                    level => $self->{level}->{uncertain},
                    line => 1, column => 1,
                    layer => 'encode');
  } else {
    $self->{input_encoding} = $charset->get_iana_name;
  }

  $self->{change_encoding} = sub {
    my $self = shift;
    $charset_name = shift;
    my $token = shift;

    $charset = Message::Charset::Info->get_by_html_name ($charset_name);
    ($char_stream, $e_status) = $charset->get_decode_handle
        ($byte_stream, allow_error_reporting => 1, allow_fallback => 1,
         byte_buffer => \ $buffer->{buffer});
    
    if ($char_stream) { # if supported
      ## "Change the encoding" algorithm:
      
      ## Step 1
      if (defined $self->{input_encoding} and
          $self->{input_encoding} eq $charset_name) {
        !!!parse-error (type => 'charset label:matching',
                        text => $charset_name,
                        level => $self->{level}->{info});
        $self->{confident} = 1;
        return;
      }

      ## Step 2 (HTML5 revision 3205)
      if (defined $self->{input_encoding} and
          Message::Charset::Info->get_by_html_name ($self->{input_encoding})
          ->{category} & Message::Charset::Info::CHARSET_CATEGORY_UTF16 ()) {
        $self->{confident} = 1;
        return;
      }

      ## Step 3
      if ($charset->{category} &
          Message::Charset::Info::CHARSET_CATEGORY_UTF16 ()) {
        $charset = Message::Charset::Info->get_by_html_name ('utf-8');
        ($char_stream, $e_status) = $charset->get_decode_handle
            ($byte_stream,
             byte_buffer => \ $buffer->{buffer});
      }
      $charset_name = $charset->get_iana_name;

      !!!parse-error (type => 'charset label detected',
                      text => $self->{input_encoding},
                      value => $charset_name,
                      level => $self->{level}->{warn},
                      token => $token);
      
      ## Step 4
      # if (can) {
        ## change the encoding on the fly.
        #$self->{confident} = 1;
        #return;
      # }
      
      ## Step 5
      throw Whatpm::HTML::RestartParser ();
    }
  }; # $self->{change_encoding}

  my $char_onerror = sub {
    my (undef, $type, %opt) = @_;
    !!!parse-error (layer => 'encode',
                    line => $self->{line}, column => $self->{column} + 1,
                    %opt, type => $type);
    if ($opt{octets}) {
      ${$opt{octets}} = "\x{FFFD}"; # relacement character
    }
  };

  my $wrapped_char_stream = $get_wrapper->($char_stream);
  $wrapped_char_stream->onerror ($char_onerror);

  my @args = ($_[1], $_[2]); # $doc, $onerror - $get_wrapper = undef;
  my $return;
  try {
    $return = $self->parse_char_stream ($wrapped_char_stream, @args);  
  } catch Whatpm::HTML::RestartParser with {
    ## NOTE: Invoked after {change_encoding}.

    if ($e_status & Message::Charset::Info::FALLBACK_ENCODING_IMPL ()) {
      $self->{input_encoding} = $charset->get_iana_name; ## TODO: Should we set actual charset decoder's encoding name?
      !!!parse-error (type => 'chardecode:fallback',
                      level => $self->{level}->{uncertain},
                      #text => $self->{input_encoding},
                      line => 1, column => 1,
                      layer => 'encode');
    } elsif (not ($e_status &
                  Message::Charset::Info::ERROR_REPORTING_ENCODING_IMPL ())) {
      $self->{input_encoding} = $charset->get_iana_name;
      !!!parse-error (type => 'chardecode:no error',
                      text => $self->{input_encoding},
                      level => $self->{level}->{uncertain},
                      line => 1, column => 1,
                      layer => 'encode');
    } else {
      $self->{input_encoding} = $charset->get_iana_name;
    }
    $self->{confident} = 1;

    $wrapped_char_stream = $get_wrapper->($char_stream);
    $wrapped_char_stream->onerror ($char_onerror);

    $return = $self->parse_char_stream ($wrapped_char_stream, @args);
  };
  return $return;
} # parse_byte_stream

## NOTE: HTML5 spec says that the encoding layer MUST NOT strip BOM
## and the HTML layer MUST ignore it.  However, we does strip BOM in
## the encoding layer and the HTML layer does not ignore any U+FEFF,
## because the core part of our HTML parser expects a string of character,
## not a string of bytes or code units or anything which might contain a BOM.
## Therefore, any parser interface that accepts a string of bytes,
## such as |parse_byte_string| in this module, must ensure that it does
## strip the BOM and never strip any ZWNBSP.

sub parse_char_string ($$$;$$) {
  #my ($self, $s, $doc, $onerror, $get_wrapper) = @_;
  my $self = shift;
  my $s = ref $_[0] ? $_[0] : \($_[0]);
  require Whatpm::Charset::DecodeHandle;
  my $input = Whatpm::Charset::DecodeHandle::CharString->new ($s);
  return $self->parse_char_stream ($input, @_[1..$#_]);
} # parse_char_string
*parse_string = \&parse_char_string; ## NOTE: Alias for backward compatibility.

sub parse_char_stream ($$$;$$) {
  my $self = ref $_[0] ? shift : shift->new;
  my $input = $_[0];
  $self->{document} = $_[1];
  @{$self->{document}->child_nodes} = ();

  ## NOTE: |set_inner_html| copies most of this method's code

  ## Confidence: irrelevant.
  $self->{confident} = 1 unless exists $self->{confident};

  $self->{document}->input_encoding ($self->{input_encoding})
      if defined $self->{input_encoding};
## TODO: |{input_encoding}| is needless?

  $self->{line_prev} = $self->{line} = 1;
  $self->{column_prev} = -1;
  $self->{column} = 0;
  $self->{set_nc} = sub {
    my $self = shift;

    my $char = '';
    if (defined $self->{next_nc}) {
      $char = $self->{next_nc};
      delete $self->{next_nc};
      $self->{nc} = ord $char;
    } else {
      $self->{char_buffer} = '';
      $self->{char_buffer_pos} = 0;

      my $count = $input->manakai_read_until
         ($self->{char_buffer}, qr/[^\x00\x0A\x0D]/, $self->{char_buffer_pos});
      if ($count) {
        $self->{line_prev} = $self->{line};
        $self->{column_prev} = $self->{column};
        $self->{column}++;
        $self->{nc}
            = ord substr ($self->{char_buffer}, $self->{char_buffer_pos}++, 1);
        return;
      }

      if ($input->read ($char, 1)) {
        $self->{nc} = ord $char;
      } else {
        $self->{nc} = -1;
        return;
      }
    }

    ($self->{line_prev}, $self->{column_prev})
        = ($self->{line}, $self->{column});
    $self->{column}++;
    
    if ($self->{nc} == 0x000A) { # LF
      !!!cp ('j1');
      $self->{line}++;
      $self->{column} = 0;
    } elsif ($self->{nc} == 0x000D) { # CR
      !!!cp ('j2');
## TODO: support for abort/streaming
      my $next = '';
      if ($input->read ($next, 1) and $next ne "\x0A") {
        $self->{next_nc} = $next;
      }
      $self->{nc} = 0x000A; # LF # MUST
      $self->{line}++;
      $self->{column} = 0;
    } elsif ($self->{nc} == 0x0000) { # NULL
      !!!cp ('j4');
      !!!parse-error (type => 'NULL');
      $self->{nc} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
    }
  };

  $self->{read_until} = sub {
    #my ($scalar, $specials_range, $offset) = @_;
    return 0 if defined $self->{next_nc};

    my $pattern = qr/[^$_[1]\x00\x0A\x0D]/;
    my $offset = $_[2] || 0;

    if ($self->{char_buffer_pos} < length $self->{char_buffer}) {
      pos ($self->{char_buffer}) = $self->{char_buffer_pos};
      if ($self->{char_buffer} =~ /\G(?>$pattern)+/) {
        substr ($_[0], $offset)
            = substr ($self->{char_buffer}, $-[0], $+[0] - $-[0]);
        my $count = $+[0] - $-[0];
        if ($count) {
          $self->{column} += $count;
          $self->{char_buffer_pos} += $count;
          $self->{line_prev} = $self->{line};
          $self->{column_prev} = $self->{column} - 1;
          $self->{nc} = -1;
        }
        return $count;
      } else {
        return 0;
      }
    } else {
      my $count = $input->manakai_read_until ($_[0], $pattern, $_[2]);
      if ($count) {
        $self->{column} += $count;
        $self->{line_prev} = $self->{line};
        $self->{column_prev} = $self->{column} - 1;
        $self->{nc} = -1;
      }
      return $count;
    }
  }; # $self->{read_until}

  my $onerror = $_[2] || sub {
    my (%opt) = @_;
    my $line = $opt{token} ? $opt{token}->{line} : $opt{line};
    my $column = $opt{token} ? $opt{token}->{column} : $opt{column};
    warn "Parse error ($opt{type}) at line $line column $column\n";
  };
  $self->{parse_error} = sub {
    $onerror->(line => $self->{line}, column => $self->{column}, @_);
  };

  my $char_onerror = sub {
    my (undef, $type, %opt) = @_;
    !!!parse-error (layer => 'encode',
                    line => $self->{line}, column => $self->{column} + 1,
                    %opt, type => $type);
  }; # $char_onerror

  if ($_[3]) {
    $input = $_[3]->($input);
    $input->onerror ($char_onerror);
  } else {
    $input->onerror ($char_onerror) unless defined $input->onerror;
  }

  $self->_initialize_tokenizer;
  $self->_initialize_tree_constructor;
  $self->_construct_tree;
  $self->_terminate_tree_constructor;

  delete $self->{parse_error}; # remove loop

  return $self->{document};
} # parse_char_stream

sub new ($) {
  my $class = shift;
  my $self = bless {
    level => {must => 'm',
              should => 's',
              warn => 'w',
              info => 'i',
              uncertain => 'u'},
  }, $class;
  $self->{set_nc} = sub {
    $self->{nc} = -1;
  };
  $self->{parse_error} = sub {
    # 
  };
  $self->{change_encoding} = sub {
    # if ($_[0] is a supported encoding) {
    #   run "change the encoding" algorithm;
    #   throw Whatpm::HTML::RestartParser (charset => $new_encoding);
    # }
  };
  $self->{application_cache_selection} = sub {
    #
  };
  return $self;
} # new

## Insertion modes

sub AFTER_HTML_IMS () { 0b100 }
sub HEAD_IMS ()       { 0b1000 }
sub BODY_IMS ()       { 0b10000 }
sub BODY_TABLE_IMS () { 0b100000 }
sub TABLE_IMS ()      { 0b1000000 }
sub ROW_IMS ()        { 0b10000000 }
sub BODY_AFTER_IMS () { 0b100000000 }
sub FRAME_IMS ()      { 0b1000000000 }
sub SELECT_IMS ()     { 0b10000000000 }
#sub IN_FOREIGN_CONTENT_IM () { 0b100000000000 } # see Whatpm::HTML::Tokenizer
    ## NOTE: "in foreign content" insertion mode is special; it is combined
    ## with the secondary insertion mode.  In this parser, they are stored
    ## together in the bit-or'ed form.
sub IN_CDATA_RCDATA_IM () { 0b1000000000000 }
    ## NOTE: "in CDATA/RCDATA" insertion mode is also special; it is
    ## combined with the original insertion mode.  In thie parser,
    ## they are stored together in the bit-or'ed form.

sub IM_MASK () { 0b11111111111 }

## NOTE: "initial" and "before html" insertion modes have no constants.

## NOTE: "after after body" insertion mode.
sub AFTER_HTML_BODY_IM () { AFTER_HTML_IMS | BODY_AFTER_IMS }

## NOTE: "after after frameset" insertion mode.
sub AFTER_HTML_FRAMESET_IM () { AFTER_HTML_IMS | FRAME_IMS }

sub IN_HEAD_IM () { HEAD_IMS | 0b00 }
sub IN_HEAD_NOSCRIPT_IM () { HEAD_IMS | 0b01 }
sub AFTER_HEAD_IM () { HEAD_IMS | 0b10 }
sub BEFORE_HEAD_IM () { HEAD_IMS | 0b11 }
sub IN_BODY_IM () { BODY_IMS }
sub IN_CELL_IM () { BODY_IMS | BODY_TABLE_IMS | 0b01 }
sub IN_CAPTION_IM () { BODY_IMS | BODY_TABLE_IMS | 0b10 }
sub IN_ROW_IM () { TABLE_IMS | ROW_IMS | 0b01 }
sub IN_TABLE_BODY_IM () { TABLE_IMS | ROW_IMS | 0b10 }
sub IN_TABLE_IM () { TABLE_IMS }
sub AFTER_BODY_IM () { BODY_AFTER_IMS }
sub IN_FRAMESET_IM () { FRAME_IMS | 0b01 }
sub AFTER_FRAMESET_IM () { FRAME_IMS | 0b10 }
sub IN_SELECT_IM () { SELECT_IMS | 0b01 }
sub IN_SELECT_IN_TABLE_IM () { SELECT_IMS | 0b10 }
sub IN_COLUMN_GROUP_IM () { 0b10 }

sub _initialize_tree_constructor ($) {
  my $self = shift;
  ## NOTE: $self->{document} MUST be specified before this method is called
  $self->{document}->strict_error_checking (0);
  ## TODO: Turn mutation events off # MUST
  ## TODO: Turn loose Document option (manakai extension) on
  $self->{document}->manakai_is_html (1); # MUST
  $self->{document}->set_user_data (manakai_source_line => 1);
  $self->{document}->set_user_data (manakai_source_column => 1);

  $self->{frameset_ok} = 1;
} # _initialize_tree_constructor

sub _terminate_tree_constructor ($) {
  my $self = shift;
  $self->{document}->strict_error_checking (1);
  ## TODO: Turn mutation events on
} # _terminate_tree_constructor

## ISSUE: Should append_child (for example) in script executed in tree construction stage fire mutation events?

{ # tree construction stage
  my $token;

sub _construct_tree ($) {
  my ($self) = @_;

  ## When an interactive UA render the $self->{document} available
  ## to the user, or when it begin accepting user input, are
  ## not defined.
  
  !!!next-token;

  undef $self->{form_element};
  undef $self->{head_element};
  undef $self->{head_element_inserted};
  $self->{open_elements} = [];
  undef $self->{inner_html_node};
  undef $self->{ignore_newline};

  ## NOTE: The "initial" insertion mode.
  $self->_tree_construction_initial; # MUST

  ## NOTE: The "before html" insertion mode.
  $self->_tree_construction_root_element;
  $self->{insertion_mode} = BEFORE_HEAD_IM;

  ## NOTE: The "before head" insertion mode and so on.
  $self->_tree_construction_main;
} # _construct_tree

sub _tree_construction_initial ($) {
  my $self = shift;

  ## NOTE: "initial" insertion mode

  INITIAL: {
    if ($token->{type} == DOCTYPE_TOKEN) {
      ## NOTE: Conformance checkers MAY, instead of reporting "not
      ## HTML5" error, switch to a conformance checking mode for
      ## another language.  (We don't support such mode switchings; it
      ## is nonsense to do anything different from what browsers do.)
      my $doctype_name = $token->{name};
      $doctype_name = '' unless defined $doctype_name;
      my $doctype = $self->{document}->create_document_type_definition
          ($doctype_name);

      $doctype_name =~ tr/A-Z/a-z/; # ASCII case-insensitive
      if ($doctype_name ne 'html') {
        !!!cp ('t1');
        !!!parse-error (type => 'not HTML5', token => $token);
      } elsif (defined $token->{pubid}) {
        !!!cp ('t2');
        ## XXX Obsolete permitted DOCTYPEs
        !!!parse-error (type => 'not HTML5', token => $token);
      } elsif (defined $token->{sysid}) {
        if ($token->{sysid} eq 'about:legacy-compat') {
          !!!cp ('t1.2'); ## <!DOCTYPE HTML SYSTEM "about:legacy-compat">
          !!!parse-error (type => 'XSLT-compat', token => $token,
                          level => $self->{level}->{should});
        } else {
          !!!parse-error (type => 'not HTML5', token => $token);
        }
      } else { ## <!DOCTYPE HTML>
        !!!cp ('t3');
        #
      }
      
      ## NOTE: Default value for both |public_id| and |system_id| attributes
      ## are empty strings, so that we don't set any value in missing cases.
      $doctype->public_id ($token->{pubid}) if defined $token->{pubid};
      $doctype->system_id ($token->{sysid}) if defined $token->{sysid};

      ## NOTE: Other DocumentType attributes are null or empty lists.
      ## In Firefox3, |internalSubset| attribute is set to the empty
      ## string, while |null| is an allowed value for the attribute
      ## according to DOM3 Core.
      $self->{document}->append_child ($doctype);
      
      if ($token->{quirks} or $doctype_name ne 'html') {
        !!!cp ('t4');
        $self->{document}->manakai_compat_mode ('quirks');
      } elsif (defined $token->{pubid}) {
        my $pubid = $token->{pubid};
        $pubid =~ tr/a-z/A-z/;
        my $prefix = [
          "+//SILMARIL//DTD HTML PRO V0R11 19970101//",
          "-//ADVASOFT LTD//DTD HTML 3.0 ASWEDIT + EXTENSIONS//",
          "-//AS//DTD HTML 3.0 ASWEDIT + EXTENSIONS//",
          "-//IETF//DTD HTML 2.0 LEVEL 1//",
          "-//IETF//DTD HTML 2.0 LEVEL 2//",
          "-//IETF//DTD HTML 2.0 STRICT LEVEL 1//",
          "-//IETF//DTD HTML 2.0 STRICT LEVEL 2//",
          "-//IETF//DTD HTML 2.0 STRICT//",
          "-//IETF//DTD HTML 2.0//",
          "-//IETF//DTD HTML 2.1E//",
          "-//IETF//DTD HTML 3.0//",
          "-//IETF//DTD HTML 3.2 FINAL//",
          "-//IETF//DTD HTML 3.2//",
          "-//IETF//DTD HTML 3//",
          "-//IETF//DTD HTML LEVEL 0//",
          "-//IETF//DTD HTML LEVEL 1//",
          "-//IETF//DTD HTML LEVEL 2//",
          "-//IETF//DTD HTML LEVEL 3//",
          "-//IETF//DTD HTML STRICT LEVEL 0//",
          "-//IETF//DTD HTML STRICT LEVEL 1//",
          "-//IETF//DTD HTML STRICT LEVEL 2//",
          "-//IETF//DTD HTML STRICT LEVEL 3//",
          "-//IETF//DTD HTML STRICT//",
          "-//IETF//DTD HTML//",
          "-//METRIUS//DTD METRIUS PRESENTATIONAL//",
          "-//MICROSOFT//DTD INTERNET EXPLORER 2.0 HTML STRICT//",
          "-//MICROSOFT//DTD INTERNET EXPLORER 2.0 HTML//",
          "-//MICROSOFT//DTD INTERNET EXPLORER 2.0 TABLES//",
          "-//MICROSOFT//DTD INTERNET EXPLORER 3.0 HTML STRICT//",
          "-//MICROSOFT//DTD INTERNET EXPLORER 3.0 HTML//",
          "-//MICROSOFT//DTD INTERNET EXPLORER 3.0 TABLES//",
          "-//NETSCAPE COMM. CORP.//DTD HTML//",
          "-//NETSCAPE COMM. CORP.//DTD STRICT HTML//",
          "-//O'REILLY AND ASSOCIATES//DTD HTML 2.0//",
          "-//O'REILLY AND ASSOCIATES//DTD HTML EXTENDED 1.0//",
          "-//O'REILLY AND ASSOCIATES//DTD HTML EXTENDED RELAXED 1.0//",
          "-//SOFTQUAD SOFTWARE//DTD HOTMETAL PRO 6.0::19990601::EXTENSIONS TO HTML 4.0//",
          "-//SOFTQUAD//DTD HOTMETAL PRO 4.0::19971010::EXTENSIONS TO HTML 4.0//",
          "-//SPYGLASS//DTD HTML 2.0 EXTENDED//",
          "-//SQ//DTD HTML 2.0 HOTMETAL + EXTENSIONS//",
          "-//SUN MICROSYSTEMS CORP.//DTD HOTJAVA HTML//",
          "-//SUN MICROSYSTEMS CORP.//DTD HOTJAVA STRICT HTML//",
          "-//W3C//DTD HTML 3 1995-03-24//",
          "-//W3C//DTD HTML 3.2 DRAFT//",
          "-//W3C//DTD HTML 3.2 FINAL//",
          "-//W3C//DTD HTML 3.2//",
          "-//W3C//DTD HTML 3.2S DRAFT//",
          "-//W3C//DTD HTML 4.0 FRAMESET//",
          "-//W3C//DTD HTML 4.0 TRANSITIONAL//",
          "-//W3C//DTD HTML EXPERIMETNAL 19960712//",
          "-//W3C//DTD HTML EXPERIMENTAL 970421//",
          "-//W3C//DTD W3 HTML//",
          "-//W3O//DTD W3 HTML 3.0//",
          "-//WEBTECHS//DTD MOZILLA HTML 2.0//",
          "-//WEBTECHS//DTD MOZILLA HTML//",
        ]; # $prefix
        my $match;
        for (@$prefix) {
          if (substr ($prefix, 0, length $_) eq $_) {
            $match = 1;
            last;
          }
        }
        if ($match or
            $pubid eq "-//W3O//DTD W3 HTML STRICT 3.0//EN//" or
            $pubid eq "-/W3C/DTD HTML 4.0 TRANSITIONAL/EN" or
            $pubid eq "HTML") {
          !!!cp ('t5');
          $self->{document}->manakai_compat_mode ('quirks');
        } elsif ($pubid =~ m[^-//W3C//DTD HTML 4.01 FRAMESET//] or
                 $pubid =~ m[^-//W3C//DTD HTML 4.01 TRANSITIONAL//]) {
          if (defined $token->{sysid}) {
            !!!cp ('t6');
            $self->{document}->manakai_compat_mode ('quirks');
          } else {
            !!!cp ('t7');
            $self->{document}->manakai_compat_mode ('limited quirks');
          }
        } elsif ($pubid =~ m[^-//W3C//DTD XHTML 1.0 FRAMESET//] or
                 $pubid =~ m[^-//W3C//DTD XHTML 1.0 TRANSITIONAL//]) {
          !!!cp ('t8');
          $self->{document}->manakai_compat_mode ('limited quirks');
        } else {
          !!!cp ('t9');
        }
      } else {
        !!!cp ('t10');
      }
      if (defined $token->{sysid}) {
        my $sysid = $token->{sysid};
        $sysid =~ tr/A-Z/a-z/;
        if ($sysid eq "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd") {
          ## NOTE: Ensure that |PUBLIC "(limited quirks)" "(quirks)"| is 
          ## marked as quirks.
          $self->{document}->manakai_compat_mode ('quirks');
          !!!cp ('t11');
        } else {
          !!!cp ('t12');
        }
      } else {
        !!!cp ('t13');
      }
      
      ## Go to the "before html" insertion mode.
      !!!next-token;
      return;
    } elsif ({
              START_TAG_TOKEN, 1,
              END_TAG_TOKEN, 1,
              END_OF_FILE_TOKEN, 1,
             }->{$token->{type}}) {
      !!!cp ('t14');
      !!!parse-error (type => 'no DOCTYPE', token => $token);
      $self->{document}->manakai_compat_mode ('quirks');
      ## Go to the "before html" insertion mode.
      ## reprocess
      !!!ack-later;
      return;
    } elsif ($token->{type} == CHARACTER_TOKEN) {
      if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
        ## Ignore the token

        unless (length $token->{data}) {
          !!!cp ('t15');
          ## Stay in the insertion mode.
          !!!next-token;
          redo INITIAL;
        } else {
          !!!cp ('t16');
        }
      } else {
        !!!cp ('t17');
      }

      !!!parse-error (type => 'no DOCTYPE', token => $token);
      $self->{document}->manakai_compat_mode ('quirks');
      ## Go to the "before html" insertion mode.
      ## reprocess
      return;
    } elsif ($token->{type} == COMMENT_TOKEN) {
      !!!cp ('t18');
      my $comment = $self->{document}->create_comment ($token->{data});
      $self->{document}->append_child ($comment);
      
      ## Stay in the insertion mode.
      !!!next-token;
      redo INITIAL;
    } else {
      die "$0: $token->{type}: Unknown token type";
    }
  } # INITIAL

  die "$0: _tree_construction_initial: This should be never reached";
} # _tree_construction_initial

sub _tree_construction_root_element ($) {
  my $self = shift;

  ## NOTE: "before html" insertion mode.
  
  B: {
      if ($token->{type} == DOCTYPE_TOKEN) {
        !!!cp ('t19');
        !!!parse-error (type => 'in html:#DOCTYPE', token => $token);
        ## Ignore the token
        ## Stay in the insertion mode.
        !!!next-token;
        redo B;
      } elsif ($token->{type} == COMMENT_TOKEN) {
        !!!cp ('t20');
        my $comment = $self->{document}->create_comment ($token->{data});
        $self->{document}->append_child ($comment);
        ## Stay in the insertion mode.
        !!!next-token;
        redo B;
      } elsif ($token->{type} == CHARACTER_TOKEN) {
        if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
          ## Ignore the token.

          unless (length $token->{data}) {
            !!!cp ('t21');
            ## Stay in the insertion mode.
            !!!next-token;
            redo B;
          } else {
            !!!cp ('t22');
          }
        } else {
          !!!cp ('t23');
        }

        $self->{application_cache_selection}->(undef);

        #
      } elsif ($token->{type} == START_TAG_TOKEN) {
        if ($token->{tag_name} eq 'html') {
          my $root_element;
          !!!create-element ($root_element, $HTML_NS, $token->{tag_name}, $token->{attributes}, $token);
          $self->{document}->append_child ($root_element);
          push @{$self->{open_elements}},
              [$root_element, $el_category->{html}];

          if ($token->{attributes}->{manifest}) {
            !!!cp ('t24');
            $self->{application_cache_selection}
                ->($token->{attributes}->{manifest}->{value});
            ## ISSUE: Spec is unclear on relative references.
            ## According to Hixie (#whatwg 2008-03-19), it should be
            ## resolved against the base URI of the document in HTML
            ## or xml:base of the element in XHTML.
          } else {
            !!!cp ('t25');
            $self->{application_cache_selection}->(undef);
          }

          !!!nack ('t25c');

          !!!next-token;
          return; ## Go to the "before head" insertion mode.
        } else {
          !!!cp ('t25.1');
          #
        }
      } elsif ({
                END_TAG_TOKEN, 1,
                END_OF_FILE_TOKEN, 1,
               }->{$token->{type}}) {
        !!!cp ('t26');
        #
      } else {
        die "$0: $token->{type}: Unknown token type";
      }

    my $root_element;
    !!!create-element ($root_element, $HTML_NS, 'html',, $token);
    $self->{document}->append_child ($root_element);
    push @{$self->{open_elements}}, [$root_element, $el_category->{html}];

    $self->{application_cache_selection}->(undef);

    ## NOTE: Reprocess the token.
    !!!ack-later;
    return; ## Go to the "before head" insertion mode.
  } # B

  die "$0: _tree_construction_root_element: This should never be reached";
} # _tree_construction_root_element

sub _reset_insertion_mode ($) {
  my $self = shift;

    ## Step 1
    my $last;
    
    ## Step 2
    my $i = -1;
    my $node = $self->{open_elements}->[$i];
    
    ## Step 3
    S3: {
      if ($self->{open_elements}->[0]->[0] eq $node->[0]) {
        $last = 1;
        if (defined $self->{inner_html_node}) {
          !!!cp ('t28');
          $node = $self->{inner_html_node};
        } else {
          die "_reset_insertion_mode: t27";
        }
      }
      
      ## Step 4..14
      my $new_mode;
      if ($node->[1] & FOREIGN_EL) {
        !!!cp ('t28.1');
        ## NOTE: Strictly spaking, the line below only applies to MathML and
        ## SVG elements.  Currently the HTML syntax supports only MathML and
        ## SVG elements as foreigners.
        $new_mode = IN_BODY_IM | IN_FOREIGN_CONTENT_IM;
      } elsif ($node->[1] == TABLE_CELL_EL) {
        if ($last) {
          !!!cp ('t28.2');
          #
        } else {
          !!!cp ('t28.3');
          $new_mode = IN_CELL_IM;
        }
      } else {
        !!!cp ('t28.4');
        $new_mode = {
                      select => IN_SELECT_IM,
                      ## NOTE: |option| and |optgroup| do not set
                      ## insertion mode to "in select" by themselves.
                      tr => IN_ROW_IM,
                      tbody => IN_TABLE_BODY_IM,
                      thead => IN_TABLE_BODY_IM,
                      tfoot => IN_TABLE_BODY_IM,
                      caption => IN_CAPTION_IM,
                      colgroup => IN_COLUMN_GROUP_IM,
                      table => IN_TABLE_IM,
                      head => IN_BODY_IM, # not in head!
                      body => IN_BODY_IM,
                      frameset => IN_FRAMESET_IM,
                     }->{$node->[0]->manakai_local_name};
      }
      $self->{insertion_mode} = $new_mode and return if defined $new_mode;
      
      ## Step 15
      if ($node->[1] == HTML_EL) {
        unless (defined $self->{head_element}) {
          !!!cp ('t29');
          $self->{insertion_mode} = BEFORE_HEAD_IM;
        } else {
          ## ISSUE: Can this state be reached?
          !!!cp ('t30');
          $self->{insertion_mode} = AFTER_HEAD_IM;
        }
        return;
      } else {
        !!!cp ('t31');
      }
      
      ## Step 16
      $self->{insertion_mode} = IN_BODY_IM and return if $last;
      
      ## Step 17
      $i--;
      $node = $self->{open_elements}->[$i];
      
      ## Step 18
      redo S3;
    } # S3

  die "$0: _reset_insertion_mode: This line should never be reached";
} # _reset_insertion_mode

sub _tree_construction_main ($) {
  my $self = shift;

  my $active_formatting_elements = [];

  my $reconstruct_active_formatting_elements = sub { # MUST
    my $insert = shift;

    ## Step 1
    return unless @$active_formatting_elements;

    ## Step 3
    my $i = -1;
    my $entry = $active_formatting_elements->[$i];

    ## Step 2
    return if $entry->[0] eq '#marker';
    for (@{$self->{open_elements}}) {
      if ($entry->[0] eq $_->[0]) {
        !!!cp ('t32');
        return;
      }
    }
    
    S4: {
      ## Step 4
      last S4 if $active_formatting_elements->[0]->[0] eq $entry->[0];

      ## Step 5
      $i--;
      $entry = $active_formatting_elements->[$i];

      ## Step 6
      if ($entry->[0] eq '#marker') {
        !!!cp ('t33_1');
        #
      } else {
        my $in_open_elements;
        OE: for (@{$self->{open_elements}}) {
          if ($entry->[0] eq $_->[0]) {
            !!!cp ('t33');
            $in_open_elements = 1;
            last OE;
          }
        }
        if ($in_open_elements) {
          !!!cp ('t34');
          #
        } else {
          ## NOTE: <!DOCTYPE HTML><p><b><i><u></p> <p>X
          !!!cp ('t35');
          redo S4;
        }
      }

      ## Step 7
      $i++;
      $entry = $active_formatting_elements->[$i];
    } # S4

    S7: {
      ## Step 8
      my $clone = [$entry->[0]->clone_node (0), $entry->[1]];
    
      ## Step 9
      $insert->($clone->[0]);
      push @{$self->{open_elements}}, $clone;
      
      ## Step 10
      $active_formatting_elements->[$i] = $self->{open_elements}->[-1];

      ## Step 11
      unless ($clone->[0] eq $active_formatting_elements->[-1]->[0]) {
        !!!cp ('t36');
        ## Step 7'
        $i++;
        $entry = $active_formatting_elements->[$i];
        
        redo S7;
      }

      !!!cp ('t37');
    } # S7
  }; # $reconstruct_active_formatting_elements

  my $clear_up_to_marker = sub {
    for (reverse 0..$#$active_formatting_elements) {
      if ($active_formatting_elements->[$_]->[0] eq '#marker') {
        !!!cp ('t38');
        splice @$active_formatting_elements, $_;
        return;
      }
    }

    !!!cp ('t39');
  }; # $clear_up_to_marker

  my $insert;

  my $parse_rcdata = sub ($) {
    my ($content_model_flag) = @_;

    ## Step 1
    my $start_tag_name = $token->{tag_name};
    !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);

    ## Step 2
    $self->{content_model} = $content_model_flag; # CDATA or RCDATA
    delete $self->{escape}; # MUST

    ## Step 3, 4
    $self->{insertion_mode} |= IN_CDATA_RCDATA_IM;

    !!!nack ('t40.1');
    !!!next-token;
  }; # $parse_rcdata

  my $script_start_tag = sub () {
    ## Step 1
    my $script_el;
    !!!create-element ($script_el, $HTML_NS, 'script', $token->{attributes}, $token);

    ## Step 2
    ## TODO: mark as "parser-inserted"

    ## Step 3
    ## TODO: Mark as "already executed", if ...

    ## Step 4 (HTML5 revision 2702)
    $insert->($script_el);
    push @{$self->{open_elements}}, [$script_el, $el_category->{script}];

    ## Step 5
    $self->{content_model} = CDATA_CONTENT_MODEL;
    delete $self->{escape}; # MUST

    ## Step 6-7
    $self->{insertion_mode} |= IN_CDATA_RCDATA_IM;

    !!!nack ('t40.2');
    !!!next-token;
  }; # $script_start_tag

  ## NOTE: $open_tables->[-1]->[0] is the "current table" element node.
  ## NOTE: $open_tables->[-1]->[1] is the "tainted" flag (OBSOLETE; unused).
  ## NOTE: $open_tables->[-1]->[2] is set false when non-Text node inserted.
  my $open_tables = [[$self->{open_elements}->[0]->[0]]];

  my $formatting_end_tag = sub {
    my $end_tag_token = shift;
    my $tag_name = $end_tag_token->{tag_name};

    ## NOTE: The adoption agency algorithm (AAA).

    FET: {
      ## Step 1
      my $formatting_element;
      my $formatting_element_i_in_active;
      AFE: for (reverse 0..$#$active_formatting_elements) {
        if ($active_formatting_elements->[$_]->[0] eq '#marker') {
          !!!cp ('t52');
          last AFE;
        } elsif ($active_formatting_elements->[$_]->[0]->manakai_local_name
                     eq $tag_name) {
          !!!cp ('t51');
          $formatting_element = $active_formatting_elements->[$_];
          $formatting_element_i_in_active = $_;
          last AFE;
        }
      } # AFE
      unless (defined $formatting_element) {
        !!!cp ('t53');
        !!!parse-error (type => 'unmatched end tag', text => $tag_name, token => $end_tag_token);
        ## Ignore the token
        !!!next-token;
        return;
      }
      ## has an element in scope
      my $in_scope = 1;
      my $formatting_element_i_in_open;  
      INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
        my $node = $self->{open_elements}->[$_];
        if ($node->[0] eq $formatting_element->[0]) {
          if ($in_scope) {
            !!!cp ('t54');
            $formatting_element_i_in_open = $_;
            last INSCOPE;
          } else { # in open elements but not in scope
            !!!cp ('t55');
            !!!parse-error (type => 'unmatched end tag',
                            text => $token->{tag_name},
                            token => $end_tag_token);
            ## Ignore the token
            !!!next-token;
            return;
          }
        } elsif ($node->[1] & SCOPING_EL) {
          !!!cp ('t56');
          $in_scope = 0;
        }
      } # INSCOPE
      unless (defined $formatting_element_i_in_open) {
        !!!cp ('t57');
        !!!parse-error (type => 'unmatched end tag',
                        text => $token->{tag_name},
                        token => $end_tag_token);
        pop @$active_formatting_elements; # $formatting_element
        !!!next-token; ## TODO: ok?
        return;
      }
      if (not $self->{open_elements}->[-1]->[0] eq $formatting_element->[0]) {
        !!!cp ('t58');
        !!!parse-error (type => 'not closed',
                        text => $self->{open_elements}->[-1]->[0]
                            ->manakai_local_name,
                        token => $end_tag_token);
      }
      
      ## Step 2
      my $furthest_block;
      my $furthest_block_i_in_open;
      OE: for (reverse 0..$#{$self->{open_elements}}) {
        my $node = $self->{open_elements}->[$_];
        if (not ($node->[1] & FORMATTING_EL) and 
            #not $phrasing_category->{$node->[1]} and
            ($node->[1] & SPECIAL_EL or
             $node->[1] & SCOPING_EL)) { ## Scoping is redundant, maybe
          !!!cp ('t59');
          $furthest_block = $node;
          $furthest_block_i_in_open = $_;
	  ## NOTE: The topmost (eldest) node.
        } elsif ($node->[0] eq $formatting_element->[0]) {
          !!!cp ('t60');
          last OE;
        }
      } # OE
      
      ## Step 3
      unless (defined $furthest_block) { # MUST
        !!!cp ('t61');
        splice @{$self->{open_elements}}, $formatting_element_i_in_open;
        splice @$active_formatting_elements, $formatting_element_i_in_active, 1;
        !!!next-token;
        return;
      }
      
      ## Step 4
      my $common_ancestor_node = $self->{open_elements}->[$formatting_element_i_in_open - 1];
      
      ## Step 5
      my $furthest_block_parent = $furthest_block->[0]->parent_node;
      if (defined $furthest_block_parent) {
        !!!cp ('t62');
        $furthest_block_parent->remove_child ($furthest_block->[0]);
      }
      
      ## Step 6
      my $bookmark_prev_el
        = $active_formatting_elements->[$formatting_element_i_in_active - 1]
          ->[0];
      
      ## Step 7
      my $node = $furthest_block;
      my $node_i_in_open = $furthest_block_i_in_open;
      my $last_node = $furthest_block;
      S7: {
        ## Step 1
        $node_i_in_open--;
        $node = $self->{open_elements}->[$node_i_in_open];
        
        ## Step 2
        my $node_i_in_active;
        S7S2: {
          for (reverse 0..$#$active_formatting_elements) {
            if ($active_formatting_elements->[$_]->[0] eq $node->[0]) {
              !!!cp ('t63');
              $node_i_in_active = $_;
              last S7S2;
            }
          }
          splice @{$self->{open_elements}}, $node_i_in_open, 1;
          redo S7;
        } # S7S2
        
        ## Step 3
        last S7 if $node->[0] eq $formatting_element->[0];
        
        ## Step 4
        if ($last_node->[0] eq $furthest_block->[0]) {
          !!!cp ('t64');
          $bookmark_prev_el = $node->[0];
        }
        
        ## Step 5
        if ($node->[0]->has_child_nodes ()) {
          !!!cp ('t65');
          my $clone = [$node->[0]->clone_node (0), $node->[1]];
          $active_formatting_elements->[$node_i_in_active] = $clone;
          $self->{open_elements}->[$node_i_in_open] = $clone;
          $node = $clone;
        }
        
        ## Step 6
        $node->[0]->append_child ($last_node->[0]);
        
        ## Step 7
        $last_node = $node;
        
        ## Step 8
        redo S7;
      } # S7  
      
      ## Step 8
      if ($common_ancestor_node->[1] & TABLE_ROWS_EL) {
        ## Foster parenting.
        my $foster_parent_element;
        my $next_sibling;
        OE: for (reverse 0..$#{$self->{open_elements}}) {
          if ($self->{open_elements}->[$_]->[1] == TABLE_EL) {
            !!!cp ('t65.2');
            $foster_parent_element = $self->{open_elements}->[$_ - 1]->[0];
            $next_sibling = $self->{open_elements}->[$_]->[0];
            undef $next_sibling
                unless $next_sibling->parent_node eq $foster_parent_element;
            last OE;
          }
        } # OE
        $foster_parent_element ||= $self->{open_elements}->[0]->[0];

        $foster_parent_element->insert_before ($last_node->[0], $next_sibling);
        $open_tables->[-1]->[1] = 1; # tainted
      } else {
        !!!cp ('t65.3');
        $common_ancestor_node->[0]->append_child ($last_node->[0]);
      }
      
      ## Step 9
      my $clone = [$formatting_element->[0]->clone_node (0),
                   $formatting_element->[1]];
      
      ## Step 10
      my @cn = @{$furthest_block->[0]->child_nodes};
      $clone->[0]->append_child ($_) for @cn;
      
      ## Step 11
      $furthest_block->[0]->append_child ($clone->[0]);
      
      ## Step 12
      my $i;
      AFE: for (reverse 0..$#$active_formatting_elements) {
        if ($active_formatting_elements->[$_]->[0] eq $formatting_element->[0]) {
          !!!cp ('t66');
          splice @$active_formatting_elements, $_, 1;
          $i-- and last AFE if defined $i;
        } elsif ($active_formatting_elements->[$_]->[0] eq $bookmark_prev_el) {
          !!!cp ('t67');
          $i = $_;
        }
      } # AFE
      splice @$active_formatting_elements, $i + 1, 0, $clone;
      
      ## Step 13
      undef $i;
      OE: for (reverse 0..$#{$self->{open_elements}}) {
        if ($self->{open_elements}->[$_]->[0] eq $formatting_element->[0]) {
          !!!cp ('t68');
          splice @{$self->{open_elements}}, $_, 1;
          $i-- and last OE if defined $i;
        } elsif ($self->{open_elements}->[$_]->[0] eq $furthest_block->[0]) {
          !!!cp ('t69');
          $i = $_;
        }
      } # OE
      splice @{$self->{open_elements}}, $i + 1, 0, $clone;
      
      ## Step 14
      redo FET;
    } # FET
  }; # $formatting_end_tag

  $insert = my $insert_to_current = sub {
    $self->{open_elements}->[-1]->[0]->append_child ($_[0]);
  }; # $insert_to_current

  ## Foster parenting.  Note that there are three "foster parenting"
  ## code in the parser: for elements (this one), for texts, and for
  ## elements in the AAA code.
  my $insert_to_foster = sub {
    my $child = shift;
    if ($self->{open_elements}->[-1]->[1] & TABLE_ROWS_EL) {
      # MUST
      my $foster_parent_element;
      my $next_sibling;
      OE: for (reverse 0..$#{$self->{open_elements}}) {
        if ($self->{open_elements}->[$_]->[1] == TABLE_EL) {
          !!!cp ('t71');
          $foster_parent_element = $self->{open_elements}->[$_ - 1]->[0];
          $next_sibling = $self->{open_elements}->[$_]->[0];
          undef $next_sibling
              unless $next_sibling->parent_node eq $foster_parent_element;
          last OE;
        }
      } # OE
      $foster_parent_element ||= $self->{open_elements}->[0]->[0];

      $foster_parent_element->insert_before ($child, $next_sibling);
      $open_tables->[-1]->[1] = 1; # tainted
    } else {
      !!!cp ('t72');
      $self->{open_elements}->[-1]->[0]->append_child ($child);
    }
  }; # $insert_to_foster

  ## NOTE: Insert a character (MUST): When a character is inserted, if
  ## the last node that was inserted by the parser is a Text node and
  ## the character has to be inserted after that node, then the
  ## character is appended to the Text node.  However, if any other
  ## node is inserted by the parser, then a new Text node is created
  ## and the character is appended as that Text node.  If I'm not
  ## wrong, for a parser with scripting disabled, there are only two
  ## cases where this occurs.  One is the case where an element node
  ## is inserted to the |head| element.  This is covered by using the
  ## |$self->{head_element_inserted}| flag.  Another is the case where
  ## an element or comment is inserted into the |table| subtree while
  ## foster parenting happens.  This is covered by using the [2] flag
  ## of the |$open_tables| structure.  All other cases are handled
  ## simply by calling |manakai_append_text| method.

  ## TODO: |<body><script>document.write("a<br>");
  ## document.body.removeChild (document.body.lastChild);
  ## document.write ("b")</script>|

  B: while (1) {

    ## The "in table text" insertion mode.
    if ($self->{insertion_mode} & TABLE_IMS and
        not $self->{insertion_mode} & IN_FOREIGN_CONTENT_IM and
        not $self->{insertion_mode} & IN_CDATA_RCDATA_IM) {
      C: {
        my $s;
        if ($token->{type} == CHARACTER_TOKEN) {
          !!!cp ('t194');
          $self->{pending_chars} ||= [];
          push @{$self->{pending_chars}}, $token;
          !!!next-token;
          next B;
        } else {
          if ($self->{pending_chars}) {
            $s = join '', map { $_->{data} } @{$self->{pending_chars}};
            delete $self->{pending_chars};
            if ($s =~ /[^\x09\x0A\x0C\x0D\x20]/) {
              !!!cp ('t195');
              #
            } else {
              !!!cp ('t195.1');
              #$self->{open_elements}->[-1]->[0]->manakai_append_text ($s);
              $self->{open_elements}->[-1]->[0]->append_child
                  ($self->{document}->create_text_node ($s));
              last C;
            }
          } else {
            !!!cp ('t195.2');
            last C;
          }
        }

        ## Foster parenting.
        !!!parse-error (type => 'in table:#text', token => $token);

        ## NOTE: As if in body, but insert into the foster parent element.
        $reconstruct_active_formatting_elements->($insert_to_foster);
            
        if ($self->{open_elements}->[-1]->[1] & TABLE_ROWS_EL) {
          # MUST
          my $foster_parent_element;
          my $next_sibling;
          OE: for (reverse 0..$#{$self->{open_elements}}) {
            if ($self->{open_elements}->[$_]->[1] == TABLE_EL) {
              !!!cp ('t197');
              $foster_parent_element = $self->{open_elements}->[$_ - 1]->[0];
              $next_sibling = $self->{open_elements}->[$_]->[0];
              undef $next_sibling
                unless $next_sibling->parent_node eq $foster_parent_element;
              last OE;
            }
          } # OE
          $foster_parent_element ||= $self->{open_elements}->[0]->[0];

          !!!cp ('t199');
          $foster_parent_element->insert_before
              ($self->{document}->create_text_node ($s), $next_sibling);

          $open_tables->[-1]->[1] = 1; # tainted
          $open_tables->[-1]->[2] = 1; # ~node inserted
        } else {
          ## NOTE: Fragment case or in a foster parent'ed element
          ## (e.g. |<table><span>a|).  In fragment case, whether the
          ## character is appended to existing node or a new node is
          ## created is irrelevant, since the foster parent'ed nodes
          ## are discarded and fragment parsing does not invoke any
          ## script.
          !!!cp ('t200');
          $self->{open_elements}->[-1]->[0]->manakai_append_text ($s);
        }
      } # C
    } # TABLE_IMS

    if ($token->{type} == DOCTYPE_TOKEN) {
      !!!cp ('t73');
      !!!parse-error (type => 'in html:#DOCTYPE', token => $token);
      ## Ignore the token
      ## Stay in the phase
      !!!next-token;
      next B;
    } elsif ($token->{type} == START_TAG_TOKEN and
             $token->{tag_name} eq 'html') {
      if ($self->{insertion_mode} == AFTER_HTML_BODY_IM) {
        !!!cp ('t79');
        !!!parse-error (type => 'after html', text => 'html', token => $token);
        $self->{insertion_mode} = AFTER_BODY_IM;
      } elsif ($self->{insertion_mode} == AFTER_HTML_FRAMESET_IM) {
        !!!cp ('t80');
        !!!parse-error (type => 'after html', text => 'html', token => $token);
        $self->{insertion_mode} = AFTER_FRAMESET_IM;
      } else {
        !!!cp ('t81');
      }

      !!!cp ('t82');
      !!!parse-error (type => 'not first start tag', token => $token);
      my $top_el = $self->{open_elements}->[0]->[0];
      for my $attr_name (keys %{$token->{attributes}}) {
        unless ($top_el->has_attribute_ns (undef, $attr_name)) {
          !!!cp ('t84');
          $top_el->set_attribute_ns
            (undef, [undef, $attr_name], 
             $token->{attributes}->{$attr_name}->{value});
        }
      }
      !!!nack ('t84.1');
      !!!next-token;
      next B;
    } elsif ($token->{type} == COMMENT_TOKEN) {
      my $comment = $self->{document}->create_comment ($token->{data});
      if ($self->{insertion_mode} & AFTER_HTML_IMS) {
        !!!cp ('t85');
        $self->{document}->append_child ($comment);
      } elsif ($self->{insertion_mode} == AFTER_BODY_IM) {
        !!!cp ('t86');
        $self->{open_elements}->[0]->[0]->append_child ($comment);
      } else {
        !!!cp ('t87');
        $self->{open_elements}->[-1]->[0]->append_child ($comment);
        $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
      }
      !!!next-token;
      next B;
    } elsif ($self->{insertion_mode} & IN_CDATA_RCDATA_IM) {
      if ($token->{type} == CHARACTER_TOKEN) {
        $token->{data} =~ s/^\x0A// if $self->{ignore_newline};
        delete $self->{ignore_newline};

        if (length $token->{data}) {
          !!!cp ('t43');
          $self->{open_elements}->[-1]->[0]->manakai_append_text
              ($token->{data});
        } else {
          !!!cp ('t43.1');
        }
        !!!next-token;
        next B;
      } elsif ($token->{type} == END_TAG_TOKEN) {
        delete $self->{ignore_newline};

        if ($token->{tag_name} eq 'script') {
          !!!cp ('t50');
          
          ## Para 1-2
          my $script = pop @{$self->{open_elements}};
          
          ## Para 3
          $self->{insertion_mode} &= ~ IN_CDATA_RCDATA_IM;

          ## Para 4
          ## TODO: $old_insertion_point = $current_insertion_point;
          ## TODO: $current_insertion_point = just before $self->{nc};

          ## Para 5
          ## TODO: Run the $script->[0].

          ## Para 6
          ## TODO: $current_insertion_point = $old_insertion_point;

          ## Para 7
          ## TODO: if ($pending_external_script) {
            ## TODO: ...
          ## TODO: }

          !!!next-token;
          next B;
        } else {
          !!!cp ('t42');
 
          pop @{$self->{open_elements}};

          $self->{insertion_mode} &= ~ IN_CDATA_RCDATA_IM;
          !!!next-token;
          next B;
        }
      } elsif ($token->{type} == END_OF_FILE_TOKEN) {
        delete $self->{ignore_newline};

        !!!cp ('t44');
        !!!parse-error (type => 'not closed',
                        text => $self->{open_elements}->[-1]->[0]
                            ->manakai_local_name,
                        token => $token);

        #if ($self->{open_elements}->[-1]->[1] == SCRIPT_EL) {
        #  ## TODO: Mark as "already executed"
        #}

        pop @{$self->{open_elements}};

        $self->{insertion_mode} &= ~ IN_CDATA_RCDATA_IM;
        ## Reprocess.
        next B;
      } else {
        die "$0: $token->{type}: In CDATA/RCDATA: Unknown token type";        
      }
    } elsif ($self->{insertion_mode} & IN_FOREIGN_CONTENT_IM) {
      if ($token->{type} == CHARACTER_TOKEN) {
        !!!cp ('t87.1');
        $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
        !!!next-token;
        next B;
      } elsif ($token->{type} == START_TAG_TOKEN) {
        if ((not {mglyph => 1, malignmark => 1}->{$token->{tag_name}} and
             $self->{open_elements}->[-1]->[1] & FOREIGN_FLOW_CONTENT_EL) or
            not ($self->{open_elements}->[-1]->[1] & FOREIGN_EL) or
            ($token->{tag_name} eq 'svg' and
             $self->{open_elements}->[-1]->[1] == MML_AXML_EL)) {
          ## NOTE: "using the rules for secondary insertion mode"then"continue"
          !!!cp ('t87.2');
          #
        } elsif ({
                  b => 1, big => 1, blockquote => 1, body => 1, br => 1,
                  center => 1, code => 1, dd => 1, div => 1, dl => 1, dt => 1,
                  em => 1, embed => 1, h1 => 1, h2 => 1, h3 => 1,
                  h4 => 1, h5 => 1, h6 => 1, head => 1, hr => 1, i => 1,
                  img => 1, li => 1, listing => 1, menu => 1, meta => 1,
                  nobr => 1, ol => 1, p => 1, pre => 1, ruby => 1, s => 1,
                  small => 1, span => 1, strong => 1, strike => 1, sub => 1,
                  sup => 1, table => 1, tt => 1, u => 1, ul => 1, var => 1,
                 }->{$token->{tag_name}} or
                 ($token->{tag_name} eq 'font' and
                  ($token->{attributes}->{color} or
                   $token->{attributes}->{face} or
                   $token->{attributes}->{size}))) {
          !!!cp ('t87.2');
          !!!parse-error (type => 'not closed',
                          text => $self->{open_elements}->[-1]->[0]
                              ->manakai_local_name,
                          token => $token);

          pop @{$self->{open_elements}}
              while $self->{open_elements}->[-1]->[1] & FOREIGN_EL;

          $self->{insertion_mode} &= ~ IN_FOREIGN_CONTENT_IM;
          ## Reprocess.
          next B;
        } else {
          my $nsuri = $self->{open_elements}->[-1]->[0]->namespace_uri;
          my $tag_name = $token->{tag_name};
          if ($nsuri eq $SVG_NS) {
            $tag_name = {
               altglyph => 'altGlyph',
               altglyphdef => 'altGlyphDef',
               altglyphitem => 'altGlyphItem',
               animatecolor => 'animateColor',
               animatemotion => 'animateMotion',
               animatetransform => 'animateTransform',
               clippath => 'clipPath',
               feblend => 'feBlend',
               fecolormatrix => 'feColorMatrix',
               fecomponenttransfer => 'feComponentTransfer',
               fecomposite => 'feComposite',
               feconvolvematrix => 'feConvolveMatrix',
               fediffuselighting => 'feDiffuseLighting',
               fedisplacementmap => 'feDisplacementMap',
               fedistantlight => 'feDistantLight',
               feflood => 'feFlood',
               fefunca => 'feFuncA',
               fefuncb => 'feFuncB',
               fefuncg => 'feFuncG',
               fefuncr => 'feFuncR',
               fegaussianblur => 'feGaussianBlur',
               feimage => 'feImage',
               femerge => 'feMerge',
               femergenode => 'feMergeNode',
               femorphology => 'feMorphology',
               feoffset => 'feOffset',
               fepointlight => 'fePointLight',
               fespecularlighting => 'feSpecularLighting',
               fespotlight => 'feSpotLight',
               fetile => 'feTile',
               feturbulence => 'feTurbulence',
               foreignobject => 'foreignObject',
               glyphref => 'glyphRef',
               lineargradient => 'linearGradient',
               radialgradient => 'radialGradient',
               #solidcolor => 'solidColor', ## NOTE: Commented in spec (SVG1.2)
               textpath => 'textPath',  
            }->{$tag_name} || $tag_name;
          }

          ## "adjust SVG attributes" (SVG only) - done in insert-element-f

          ## "adjust foreign attributes" - done in insert-element-f

          !!!insert-element-f ($nsuri, $tag_name, $token->{attributes}, $token);

          if ($self->{self_closing}) {
            pop @{$self->{open_elements}};
            !!!ack ('t87.3');
          } else {
            !!!cp ('t87.4');
          }

          !!!next-token;
          next B;
        }
      } elsif ($token->{type} == END_TAG_TOKEN) {
        ## NOTE: "using the rules for secondary insertion mode" then "continue"
        if ($token->{tag_name} eq 'script') {
          !!!cp ('t87.41');
          #
          ## XXXscript: Execute script here.
        } else {
          !!!cp ('t87.5');
          #
        }
      } elsif ($token->{type} == END_OF_FILE_TOKEN) {
        !!!cp ('t87.6');
        !!!parse-error (type => 'not closed',
                        text => $self->{open_elements}->[-1]->[0]
                            ->manakai_local_name,
                        token => $token);

        pop @{$self->{open_elements}}
            while $self->{open_elements}->[-1]->[1] & FOREIGN_EL;

        ## NOTE: |<span><svg>| ... two parse errors, |<svg>| ... a parse error.

        $self->{insertion_mode} &= ~ IN_FOREIGN_CONTENT_IM;
        ## Reprocess.
        next B;
      } else {
        die "$0: $token->{type}: Unknown token type";        
      }
    }

    if ($self->{insertion_mode} & HEAD_IMS) {
      if ($token->{type} == CHARACTER_TOKEN) {
        if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
          unless ($self->{insertion_mode} == BEFORE_HEAD_IM) {
            if ($self->{head_element_inserted}) {
              !!!cp ('t88.3');
              $self->{open_elements}->[-1]->[0]->append_child
                ($self->{document}->create_text_node ($1));
              delete $self->{head_element_inserted};
              ## NOTE: |</head> <link> |
              #
            } else {
              !!!cp ('t88.2');
              $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
              ## NOTE: |</head> &#x20;|
              #
            }
          } else {
            !!!cp ('t88.1');
            ## Ignore the token.
            #
          }
          unless (length $token->{data}) {
            !!!cp ('t88');
            !!!next-token;
            next B;
          }
## TODO: set $token->{column} appropriately
        }

        if ($self->{insertion_mode} == BEFORE_HEAD_IM) {
          !!!cp ('t89');
          ## As if <head>
          !!!create-element ($self->{head_element}, $HTML_NS, 'head',, $token);
          $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
          push @{$self->{open_elements}},
              [$self->{head_element}, $el_category->{head}];

          ## Reprocess in the "in head" insertion mode...
          pop @{$self->{open_elements}};

          ## Reprocess in the "after head" insertion mode...
        } elsif ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
          !!!cp ('t90');
          ## As if </noscript>
          pop @{$self->{open_elements}};
          !!!parse-error (type => 'in noscript:#text', token => $token);
          
          ## Reprocess in the "in head" insertion mode...
          ## As if </head>
          pop @{$self->{open_elements}};

          ## Reprocess in the "after head" insertion mode...
        } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
          !!!cp ('t91');
          pop @{$self->{open_elements}};

          ## Reprocess in the "after head" insertion mode...
        } else {
          !!!cp ('t92');
        }

        ## "after head" insertion mode
        ## As if <body>
        !!!insert-element ('body',, $token);
        $self->{insertion_mode} = IN_BODY_IM;
        ## reprocess
        next B;
      } elsif ($token->{type} == START_TAG_TOKEN) {
        if ($token->{tag_name} eq 'head') {
          if ($self->{insertion_mode} == BEFORE_HEAD_IM) {
            !!!cp ('t93');
            !!!create-element ($self->{head_element}, $HTML_NS, $token->{tag_name}, $token->{attributes}, $token);
            $self->{open_elements}->[-1]->[0]->append_child
                ($self->{head_element});
            push @{$self->{open_elements}},
                [$self->{head_element}, $el_category->{head}];
            $self->{insertion_mode} = IN_HEAD_IM;
            !!!nack ('t93.1');
            !!!next-token;
            next B;
          } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
            !!!cp ('t93.2');
            !!!parse-error (type => 'after head', text => 'head',
                            token => $token);
            ## Ignore the token
            !!!nack ('t93.3');
            !!!next-token;
            next B;
          } else {
            !!!cp ('t95');
            !!!parse-error (type => 'in head:head',
                            token => $token); # or in head noscript
            ## Ignore the token
            !!!nack ('t95.1');
            !!!next-token;
            next B;
          }
        } elsif ($self->{insertion_mode} == BEFORE_HEAD_IM) {
          !!!cp ('t96');
          ## As if <head>
          !!!create-element ($self->{head_element}, $HTML_NS, 'head',, $token);
          $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
          push @{$self->{open_elements}},
              [$self->{head_element}, $el_category->{head}];

          $self->{insertion_mode} = IN_HEAD_IM;
          ## Reprocess in the "in head" insertion mode...
        } else {
          !!!cp ('t97');
        }

        if ($token->{tag_name} eq 'base') {
          if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
            !!!cp ('t98');
            ## As if </noscript>
            pop @{$self->{open_elements}};
            !!!parse-error (type => 'in noscript', text => 'base',
                            token => $token);
          
            $self->{insertion_mode} = IN_HEAD_IM;
            ## Reprocess in the "in head" insertion mode...
          } else {
            !!!cp ('t99');
          }

          ## NOTE: There is a "as if in head" code clone.
          if ($self->{insertion_mode} == AFTER_HEAD_IM) {
            !!!cp ('t100');
            !!!parse-error (type => 'after head',
                            text => $token->{tag_name}, token => $token);
            push @{$self->{open_elements}},
                [$self->{head_element}, $el_category->{head}];
            $self->{head_element_inserted} = 1;
          } else {
            !!!cp ('t101');
          }
          !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
          pop @{$self->{open_elements}};
          pop @{$self->{open_elements}} # <head>
              if $self->{insertion_mode} == AFTER_HEAD_IM;
          !!!nack ('t101.1');
          !!!next-token;
          next B;
        } elsif ($token->{tag_name} eq 'link') {
          ## NOTE: There is a "as if in head" code clone.
          if ($self->{insertion_mode} == AFTER_HEAD_IM) {
            !!!cp ('t102');
            !!!parse-error (type => 'after head',
                            text => $token->{tag_name}, token => $token);
            push @{$self->{open_elements}},
                [$self->{head_element}, $el_category->{head}];
            $self->{head_element_inserted} = 1;
          } else {
            !!!cp ('t103');
          }
          !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
          pop @{$self->{open_elements}};
          pop @{$self->{open_elements}} # <head>
              if $self->{insertion_mode} == AFTER_HEAD_IM;
          !!!ack ('t103.1');
          !!!next-token;
          next B;
        } elsif ($token->{tag_name} eq 'command') {
          if ($self->{insertion_mode} == IN_HEAD_IM) {
            ## NOTE: If the insertion mode at the time of the emission
            ## of the token was "before head", $self->{insertion_mode}
            ## is already changed to |IN_HEAD_IM|.

            ## NOTE: There is a "as if in head" code clone.
            !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
            pop @{$self->{open_elements}};
            pop @{$self->{open_elements}} # <head>
                if $self->{insertion_mode} == AFTER_HEAD_IM;
            !!!ack ('t103.2');
            !!!next-token;
            next B;
          } else {
            ## NOTE: "in head noscript" or "after head" insertion mode
            ## - in these cases, these tags are treated as same as
            ## normal in-body tags.
            !!!cp ('t103.3');
            #
          }
        } elsif ($token->{tag_name} eq 'meta') {
          ## NOTE: There is a "as if in head" code clone.
          if ($self->{insertion_mode} == AFTER_HEAD_IM) {
            !!!cp ('t104');
            !!!parse-error (type => 'after head',
                            text => $token->{tag_name}, token => $token);
            push @{$self->{open_elements}},
                [$self->{head_element}, $el_category->{head}];
            $self->{head_element_inserted} = 1;
          } else {
            !!!cp ('t105');
          }
          !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
          my $meta_el = pop @{$self->{open_elements}};

              unless ($self->{confident}) {
                if ($token->{attributes}->{charset}) {
                  !!!cp ('t106');
                  ## NOTE: Whether the encoding is supported or not is handled
                  ## in the {change_encoding} callback.
                  $self->{change_encoding}
                      ->($self, $token->{attributes}->{charset}->{value},
                         $token);
                  
                  $meta_el->[0]->get_attribute_node_ns (undef, 'charset')
                      ->set_user_data (manakai_has_reference =>
                                           $token->{attributes}->{charset}
                                               ->{has_reference});
                } elsif ($token->{attributes}->{content}) {
                  if ($token->{attributes}->{content}->{value}
                      =~ /[Cc][Hh][Aa][Rr][Ss][Ee][Tt]
                          [\x09\x0A\x0C\x0D\x20]*=
                          [\x09\x0A\x0C\x0D\x20]*(?>"([^"]*)"|'([^']*)'|
                          ([^"'\x09\x0A\x0C\x0D\x20]
                           [^\x09\x0A\x0C\x0D\x20\x3B]*))/x) {
                    !!!cp ('t107');
                    ## NOTE: Whether the encoding is supported or not is handled
                    ## in the {change_encoding} callback.
                    $self->{change_encoding}
                        ->($self, defined $1 ? $1 : defined $2 ? $2 : $3,
                           $token);
                    $meta_el->[0]->get_attribute_node_ns (undef, 'content')
                        ->set_user_data (manakai_has_reference =>
                                             $token->{attributes}->{content}
                                                   ->{has_reference});
                  } else {
                    !!!cp ('t108');
                  }
                }
              } else {
                if ($token->{attributes}->{charset}) {
                  !!!cp ('t109');
                  $meta_el->[0]->get_attribute_node_ns (undef, 'charset')
                      ->set_user_data (manakai_has_reference =>
                                           $token->{attributes}->{charset}
                                               ->{has_reference});
                }
                if ($token->{attributes}->{content}) {
                  !!!cp ('t110');
                  $meta_el->[0]->get_attribute_node_ns (undef, 'content')
                      ->set_user_data (manakai_has_reference =>
                                           $token->{attributes}->{content}
                                               ->{has_reference});
                }
              }

              pop @{$self->{open_elements}} # <head>
                  if $self->{insertion_mode} == AFTER_HEAD_IM;
              !!!ack ('t110.1');
              !!!next-token;
              next B;
        } elsif ($token->{tag_name} eq 'title') {
          if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
            !!!cp ('t111');
            ## As if </noscript>
            pop @{$self->{open_elements}};
            !!!parse-error (type => 'in noscript', text => 'title',
                            token => $token);
          
            $self->{insertion_mode} = IN_HEAD_IM;
            ## Reprocess in the "in head" insertion mode...
          } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
            !!!cp ('t112');
            !!!parse-error (type => 'after head',
                            text => $token->{tag_name}, token => $token);
            push @{$self->{open_elements}},
                [$self->{head_element}, $el_category->{head}];
            $self->{head_element_inserted} = 1;
          } else {
            !!!cp ('t113');
          }

          ## NOTE: There is a "as if in head" code clone.
          $parse_rcdata->(RCDATA_CONTENT_MODEL);

          ## NOTE: At this point the stack of open elements contain
          ## the |head| element (index == -2) and the |script| element
          ## (index == -1).  In the "after head" insertion mode the
          ## |head| element is inserted only for the purpose of
          ## providing the context for the |script| element, and
          ## therefore we can now and have to remove the element from
          ## the stack.
          splice @{$self->{open_elements}}, -2, 1, () # <head>
              if ($self->{insertion_mode} & IM_MASK) == AFTER_HEAD_IM;
          next B;
        } elsif ($token->{tag_name} eq 'style' or
                 $token->{tag_name} eq 'noframes') {
          ## NOTE: Or (scripting is enabled and tag_name eq 'noscript' and
          ## insertion mode IN_HEAD_IM)
          ## NOTE: There is a "as if in head" code clone.
          if ($self->{insertion_mode} == AFTER_HEAD_IM) {
            !!!cp ('t114');
            !!!parse-error (type => 'after head',
                            text => $token->{tag_name}, token => $token);
            push @{$self->{open_elements}},
                [$self->{head_element}, $el_category->{head}];
            $self->{head_element_inserted} = 1;
          } else {
            !!!cp ('t115');
          }
          $parse_rcdata->(CDATA_CONTENT_MODEL);
          ## ISSUE: A spec bug [Bug 6038]
          splice @{$self->{open_elements}}, -2, 1, () # <head>
              if ($self->{insertion_mode} & IM_MASK) == AFTER_HEAD_IM;
          next B;
        } elsif ($token->{tag_name} eq 'noscript') {
              if ($self->{insertion_mode} == IN_HEAD_IM) {
                !!!cp ('t116');
                ## NOTE: and scripting is disalbed
                !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
                $self->{insertion_mode} = IN_HEAD_NOSCRIPT_IM;
                !!!nack ('t116.1');
                !!!next-token;
                next B;
              } elsif ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
                !!!cp ('t117');
                !!!parse-error (type => 'in noscript', text => 'noscript',
                                token => $token);
                ## Ignore the token
                !!!nack ('t117.1');
                !!!next-token;
                next B;
              } else {
                !!!cp ('t118');
                #
              }
        } elsif ($token->{tag_name} eq 'script') {
          if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
            !!!cp ('t119');
            ## As if </noscript>
            pop @{$self->{open_elements}};
            !!!parse-error (type => 'in noscript', text => 'script',
                            token => $token);
          
            $self->{insertion_mode} = IN_HEAD_IM;
            ## Reprocess in the "in head" insertion mode...
          } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
            !!!cp ('t120');
            !!!parse-error (type => 'after head',
                            text => $token->{tag_name}, token => $token);
            push @{$self->{open_elements}},
                [$self->{head_element}, $el_category->{head}];
            $self->{head_element_inserted} = 1;
          } else {
            !!!cp ('t121');
          }

          ## NOTE: There is a "as if in head" code clone.
          $script_start_tag->();
          ## ISSUE: A spec bug  [Bug 6038]
          splice @{$self->{open_elements}}, -2, 1 # <head>
              if ($self->{insertion_mode} & IM_MASK) == AFTER_HEAD_IM;
          next B;
        } elsif ($token->{tag_name} eq 'body' or
                 $token->{tag_name} eq 'frameset') {
              if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
                !!!cp ('t122');
                ## As if </noscript>
                pop @{$self->{open_elements}};
                !!!parse-error (type => 'in noscript',
                                text => $token->{tag_name}, token => $token);
                
                ## Reprocess in the "in head" insertion mode...
                ## As if </head>
                pop @{$self->{open_elements}};
                
                ## Reprocess in the "after head" insertion mode...
              } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
                !!!cp ('t124');
                pop @{$self->{open_elements}};
                
                ## Reprocess in the "after head" insertion mode...
              } else {
                !!!cp ('t125');
              }

              ## "after head" insertion mode
              !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
              if ($token->{tag_name} eq 'body') {
                !!!cp ('t126');
                $self->{insertion_mode} = IN_BODY_IM;
              } elsif ($token->{tag_name} eq 'frameset') {
                !!!cp ('t127');
                $self->{insertion_mode} = IN_FRAMESET_IM;
              } else {
                die "$0: tag name: $self->{tag_name}";
              }
              !!!nack ('t127.1');
              !!!next-token;
              next B;
            } else {
              !!!cp ('t128');
              #
            }

            if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
              !!!cp ('t129');
              ## As if </noscript>
              pop @{$self->{open_elements}};
              !!!parse-error (type => 'in noscript:/',
                              text => $token->{tag_name}, token => $token);
              
              ## Reprocess in the "in head" insertion mode...
              ## As if </head>
              pop @{$self->{open_elements}};

              ## Reprocess in the "after head" insertion mode...
            } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
              !!!cp ('t130');
              ## As if </head>
              pop @{$self->{open_elements}};

              ## Reprocess in the "after head" insertion mode...
            } else {
              !!!cp ('t131');
            }

            ## "after head" insertion mode
            ## As if <body>
            !!!insert-element ('body',, $token);
            $self->{insertion_mode} = IN_BODY_IM;
            ## reprocess
            !!!ack-later;
            next B;
      } elsif ($token->{type} == END_TAG_TOKEN) {
        ## "Before head", "in head", and "after head" insertion modes
        ## ignore most of end tags.  Exceptions are "body", "html",
        ## and "br" end tags.  "Before head" and "in head" insertion
        ## modes also recognize "head" end tag.  "In head noscript"
        ## insertion modes ignore end tags except for "noscript" and
        ## "br".

        if ($token->{tag_name} eq 'head') {
          if ($self->{insertion_mode} == BEFORE_HEAD_IM) {
            !!!cp ('t132');
            ## As if <head>
            !!!create-element ($self->{head_element}, $HTML_NS, 'head',, $token);
            $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
            push @{$self->{open_elements}},
                [$self->{head_element}, $el_category->{head}];

            ## Reprocess in the "in head" insertion mode...
            pop @{$self->{open_elements}};
            $self->{insertion_mode} = AFTER_HEAD_IM;
            !!!next-token;
            next B;
          } elsif ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
            !!!cp ('t133');
            #
          } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
            !!!cp ('t134');
            pop @{$self->{open_elements}};
            $self->{insertion_mode} = AFTER_HEAD_IM;
            !!!next-token;
            next B;
          } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
            !!!cp ('t134.1');
            #
          } else {
            die "$0: $self->{insertion_mode}: Unknown insertion mode";
          }
        } elsif ($token->{tag_name} eq 'noscript') {
          if ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
            !!!cp ('t136');
            pop @{$self->{open_elements}};
            $self->{insertion_mode} = IN_HEAD_IM;
            !!!next-token;
            next B;
          } else {
            !!!cp ('t138');
            #
          }
        } elsif ({
            body => ($self->{insertion_mode} != IN_HEAD_NOSCRIPT_IM),
            html => ($self->{insertion_mode} != IN_HEAD_NOSCRIPT_IM),
            br => 1,
        }->{$token->{tag_name}}) {
          if ($self->{insertion_mode} == BEFORE_HEAD_IM) {
            !!!cp ('t142.2');
            ## (before head) as if <head>, (in head) as if </head>
            !!!create-element ($self->{head_element}, $HTML_NS, 'head',, $token);
            $self->{open_elements}->[-1]->[0]->append_child ($self->{head_element});
            $self->{insertion_mode} = AFTER_HEAD_IM;
  
            ## Reprocess in the "after head" insertion mode...
          } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
            !!!cp ('t143.2');
            ## As if </head>
            pop @{$self->{open_elements}};
            $self->{insertion_mode} = AFTER_HEAD_IM;
  
            ## Reprocess in the "after head" insertion mode...
          } elsif ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
            !!!cp ('t143.3');
            ## NOTE: Two parse errors for <head><noscript></br>
            !!!parse-error (type => 'unmatched end tag',
                            text => $token->{tag_name}, token => $token);
            ## As if </noscript>
            pop @{$self->{open_elements}};
            $self->{insertion_mode} = IN_HEAD_IM;

            ## Reprocess in the "in head" insertion mode...
            ## As if </head>
            pop @{$self->{open_elements}};
            $self->{insertion_mode} = AFTER_HEAD_IM;

            ## Reprocess in the "after head" insertion mode...
          } elsif ($self->{insertion_mode} == AFTER_HEAD_IM) {
            !!!cp ('t143.4');
            #
          } else {
            die "$0: $self->{insertion_mode}: Unknown insertion mode";
          }

          ## "after head" insertion mode
          ## As if <body>
          !!!insert-element ('body',, $token);
          $self->{insertion_mode} = IN_BODY_IM;
          ## Reprocess.
          next B;
        }

        ## End tags are ignored by default.
        !!!cp ('t145');
        !!!parse-error (type => 'unmatched end tag',
                        text => $token->{tag_name}, token => $token);
        ## Ignore the token.
        !!!next-token;
        next B;
      } elsif ($token->{type} == END_OF_FILE_TOKEN) {
        if ($self->{insertion_mode} == BEFORE_HEAD_IM) {
          !!!cp ('t149.1');

          ## NOTE: As if <head>
          !!!create-element ($self->{head_element}, $HTML_NS, 'head',, $token);
          $self->{open_elements}->[-1]->[0]->append_child
              ($self->{head_element});
          #push @{$self->{open_elements}},
          #    [$self->{head_element}, $el_category->{head}];
          #$self->{insertion_mode} = IN_HEAD_IM;
          ## NOTE: Reprocess.

          ## NOTE: As if </head>
          #pop @{$self->{open_elements}};
          #$self->{insertion_mode} = IN_AFTER_HEAD_IM;
          ## NOTE: Reprocess.
          
          #
        } elsif ($self->{insertion_mode} == IN_HEAD_IM) {
          !!!cp ('t149.2');

          ## NOTE: As if </head>
          pop @{$self->{open_elements}};
          #$self->{insertion_mode} = IN_AFTER_HEAD_IM;
          ## NOTE: Reprocess.

          #
        } elsif ($self->{insertion_mode} == IN_HEAD_NOSCRIPT_IM) {
          !!!cp ('t149.3');

          !!!parse-error (type => 'in noscript:#eof', token => $token);

          ## As if </noscript>
          pop @{$self->{open_elements}};
          #$self->{insertion_mode} = IN_HEAD_IM;
          ## NOTE: Reprocess.

          ## NOTE: As if </head>
          pop @{$self->{open_elements}};
          #$self->{insertion_mode} = IN_AFTER_HEAD_IM;
          ## NOTE: Reprocess.

          #
        } else {
          !!!cp ('t149.4');
          #
        }

        ## NOTE: As if <body>
        !!!insert-element ('body',, $token);
        $self->{insertion_mode} = IN_BODY_IM;
        ## NOTE: Reprocess.
        next B;
      } else {
        die "$0: $token->{type}: Unknown token type";
      }
    } elsif ($self->{insertion_mode} & BODY_IMS) {
          if ($token->{type} == CHARACTER_TOKEN) {
            !!!cp ('t150');
            ## NOTE: There is a code clone of "character in body".
            $reconstruct_active_formatting_elements->($insert_to_current);
            
            $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});

            !!!next-token;
            next B;
          } elsif ($token->{type} == START_TAG_TOKEN) {
            if ({
                 caption => 1, col => 1, colgroup => 1, tbody => 1,
                 td => 1, tfoot => 1, th => 1, thead => 1, tr => 1,
                }->{$token->{tag_name}}) {
              if (($self->{insertion_mode} & IM_MASK) == IN_CELL_IM) {
                ## have an element in table scope
                for (reverse 0..$#{$self->{open_elements}}) {
                  my $node = $self->{open_elements}->[$_];
                  if ($node->[1] == TABLE_CELL_EL) {
                    !!!cp ('t151');

                    ## Close the cell
                    !!!back-token; # <x>
                    $token = {type => END_TAG_TOKEN,
                              tag_name => $node->[0]->manakai_local_name,
                              line => $token->{line},
                              column => $token->{column}};
                    next B;
                  } elsif ($node->[1] & TABLE_SCOPING_EL) {
                    !!!cp ('t152');
                    ## ISSUE: This case can never be reached, maybe.
                    last;
                  }
                }

                !!!cp ('t153');
                !!!parse-error (type => 'start tag not allowed',
                    text => $token->{tag_name}, token => $token);
                ## Ignore the token
                !!!nack ('t153.1');
                !!!next-token;
                next B;
              } elsif (($self->{insertion_mode} & IM_MASK) == IN_CAPTION_IM) {
                !!!parse-error (type => 'not closed', text => 'caption',
                                token => $token);
                
                ## NOTE: As if </caption>.
                ## have a table element in table scope
                my $i;
                INSCOPE: {
                  for (reverse 0..$#{$self->{open_elements}}) {
                    my $node = $self->{open_elements}->[$_];
                    if ($node->[1] == CAPTION_EL) {
                      !!!cp ('t155');
                      $i = $_;
                      last INSCOPE;
                    } elsif ($node->[1] & TABLE_SCOPING_EL) {
                      !!!cp ('t156');
                      last;
                    }
                  }

                  !!!cp ('t157');
                  !!!parse-error (type => 'start tag not allowed',
                                  text => $token->{tag_name}, token => $token);
                  ## Ignore the token
                  !!!nack ('t157.1');
                  !!!next-token;
                  next B;
                } # INSCOPE
                
                ## generate implied end tags
                while ($self->{open_elements}->[-1]->[1]
                           & END_TAG_OPTIONAL_EL) {
                  !!!cp ('t158');
                  pop @{$self->{open_elements}};
                }

                unless ($self->{open_elements}->[-1]->[1] == CAPTION_EL) {
                  !!!cp ('t159');
                  !!!parse-error (type => 'not closed',
                                  text => $self->{open_elements}->[-1]->[0]
                                      ->manakai_local_name,
                                  token => $token);
                } else {
                  !!!cp ('t160');
                }
                
                splice @{$self->{open_elements}}, $i;
                
                $clear_up_to_marker->();
                
                $self->{insertion_mode} = IN_TABLE_IM;
                
                ## reprocess
                !!!ack-later;
                next B;
              } else {
                !!!cp ('t161');
                #
              }
            } else {
              !!!cp ('t162');
              #
            }
          } elsif ($token->{type} == END_TAG_TOKEN) {
            if ($token->{tag_name} eq 'td' or $token->{tag_name} eq 'th') {
              if (($self->{insertion_mode} & IM_MASK) == IN_CELL_IM) {
                ## have an element in table scope
                my $i;
                INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
                  my $node = $self->{open_elements}->[$_];
                  if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
                    !!!cp ('t163');
                    $i = $_;
                    last INSCOPE;
                  } elsif ($node->[1] & TABLE_SCOPING_EL) {
                    !!!cp ('t164');
                    last INSCOPE;
                  }
                } # INSCOPE
                  unless (defined $i) {
                    !!!cp ('t165');
                    !!!parse-error (type => 'unmatched end tag',
                                    text => $token->{tag_name},
                                    token => $token);
                    ## Ignore the token
                    !!!next-token;
                    next B;
                  }
                
                ## generate implied end tags
                while ($self->{open_elements}->[-1]->[1]
                           & END_TAG_OPTIONAL_EL) {
                  !!!cp ('t166');
                  pop @{$self->{open_elements}};
                }

                if ($self->{open_elements}->[-1]->[0]->manakai_local_name
                        ne $token->{tag_name}) {
                  !!!cp ('t167');
                  !!!parse-error (type => 'not closed',
                                  text => $self->{open_elements}->[-1]->[0]
                                      ->manakai_local_name,
                                  token => $token);
                } else {
                  !!!cp ('t168');
                }
                
                splice @{$self->{open_elements}}, $i;
                
                $clear_up_to_marker->();
                
                $self->{insertion_mode} = IN_ROW_IM;
                
                !!!next-token;
                next B;
              } elsif (($self->{insertion_mode} & IM_MASK) == IN_CAPTION_IM) {
                !!!cp ('t169');
                !!!parse-error (type => 'unmatched end tag',
                                text => $token->{tag_name}, token => $token);
                ## Ignore the token
                !!!next-token;
                next B;
              } else {
                !!!cp ('t170');
                #
              }
            } elsif ($token->{tag_name} eq 'caption') {
              if (($self->{insertion_mode} & IM_MASK) == IN_CAPTION_IM) {
                ## have a table element in table scope
                my $i;
                INSCOPE: {
                  for (reverse 0..$#{$self->{open_elements}}) {
                    my $node = $self->{open_elements}->[$_];
                    if ($node->[1] == CAPTION_EL) {
                      !!!cp ('t171');
                      $i = $_;
                      last INSCOPE;
                    } elsif ($node->[1] & TABLE_SCOPING_EL) {
                      !!!cp ('t172');
                      last;
                    }
                  }

                  !!!cp ('t173');
                  !!!parse-error (type => 'unmatched end tag',
                                  text => $token->{tag_name}, token => $token);
                  ## Ignore the token
                  !!!next-token;
                  next B;
                } # INSCOPE
                
                ## generate implied end tags
                while ($self->{open_elements}->[-1]->[1]
                           & END_TAG_OPTIONAL_EL) {
                  !!!cp ('t174');
                  pop @{$self->{open_elements}};
                }
                
                unless ($self->{open_elements}->[-1]->[1] == CAPTION_EL) {
                  !!!cp ('t175');
                  !!!parse-error (type => 'not closed',
                                  text => $self->{open_elements}->[-1]->[0]
                                      ->manakai_local_name,
                                  token => $token);
                } else {
                  !!!cp ('t176');
                }
                
                splice @{$self->{open_elements}}, $i;
                
                $clear_up_to_marker->();
                
                $self->{insertion_mode} = IN_TABLE_IM;
                
                !!!next-token;
                next B;
              } elsif (($self->{insertion_mode} & IM_MASK) == IN_CELL_IM) {
                !!!cp ('t177');
                !!!parse-error (type => 'unmatched end tag',
                                text => $token->{tag_name}, token => $token);
                ## Ignore the token
                !!!next-token;
                next B;
              } else {
                !!!cp ('t178');
                #
              }
            } elsif ({
                      table => 1, tbody => 1, tfoot => 1, 
                      thead => 1, tr => 1,
                     }->{$token->{tag_name}} and
                     ($self->{insertion_mode} & IM_MASK) == IN_CELL_IM) {
              ## have an element in table scope
              my $i;
              my $tn;
              INSCOPE: {
                for (reverse 0..$#{$self->{open_elements}}) {
                  my $node = $self->{open_elements}->[$_];
                  if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
                    !!!cp ('t179');
                    $i = $_;

                    ## Close the cell
                    !!!back-token; # </x>
                    $token = {type => END_TAG_TOKEN, tag_name => $tn,
                              line => $token->{line},
                              column => $token->{column}};
                    next B;
                  } elsif ($node->[1] == TABLE_CELL_EL) {
                    !!!cp ('t180');
                    $tn = $node->[0]->manakai_local_name;
                    ## NOTE: There is exactly one |td| or |th| element
                    ## in scope in the stack of open elements by definition.
                  } elsif ($node->[1] & TABLE_SCOPING_EL) {
                    ## ISSUE: Can this be reached?
                    !!!cp ('t181');
                    last;
                  }
                }

                !!!cp ('t182');
                !!!parse-error (type => 'unmatched end tag',
                    text => $token->{tag_name}, token => $token);
                ## Ignore the token
                !!!next-token;
                next B;
              } # INSCOPE
            } elsif ($token->{tag_name} eq 'table' and
                     ($self->{insertion_mode} & IM_MASK) == IN_CAPTION_IM) {
              !!!parse-error (type => 'not closed', text => 'caption',
                              token => $token);

              ## As if </caption>
              ## have a table element in table scope
              my $i;
              INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
                my $node = $self->{open_elements}->[$_];
                if ($node->[1] == CAPTION_EL) {
                  !!!cp ('t184');
                  $i = $_;
                  last INSCOPE;
                } elsif ($node->[1] & TABLE_SCOPING_EL) {
                  !!!cp ('t185');
                  last INSCOPE;
                }
              } # INSCOPE
              unless (defined $i) {
                !!!cp ('t186');
	## TODO: Wrong error type?
                !!!parse-error (type => 'unmatched end tag',
                                text => 'caption', token => $token);
                ## Ignore the token
                !!!next-token;
                next B;
              }
              
              ## generate implied end tags
              while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
                !!!cp ('t187');
                pop @{$self->{open_elements}};
              }

              unless ($self->{open_elements}->[-1]->[1] == CAPTION_EL) {
                !!!cp ('t188');
                !!!parse-error (type => 'not closed',
                                text => $self->{open_elements}->[-1]->[0]
                                    ->manakai_local_name,
                                token => $token);
              } else {
                !!!cp ('t189');
              }

              splice @{$self->{open_elements}}, $i;

              $clear_up_to_marker->();

              $self->{insertion_mode} = IN_TABLE_IM;

              ## reprocess
              next B;
            } elsif ({
                      body => 1, col => 1, colgroup => 1, html => 1,
                     }->{$token->{tag_name}}) {
              if ($self->{insertion_mode} & BODY_TABLE_IMS) {
                !!!cp ('t190');
                !!!parse-error (type => 'unmatched end tag',
                                text => $token->{tag_name}, token => $token);
                ## Ignore the token
                !!!next-token;
                next B;
              } else {
                !!!cp ('t191');
                #
              }
        } elsif ({
                  tbody => 1, tfoot => 1,
                  thead => 1, tr => 1,
                 }->{$token->{tag_name}} and
                 ($self->{insertion_mode} & IM_MASK) == IN_CAPTION_IM) {
          !!!cp ('t192');
          !!!parse-error (type => 'unmatched end tag',
                          text => $token->{tag_name}, token => $token);
          ## Ignore the token
          !!!next-token;
          next B;
        } else {
          !!!cp ('t193');
          #
        }
      } elsif ($token->{type} == END_OF_FILE_TOKEN) {
        for my $entry (@{$self->{open_elements}}) {
          unless ($entry->[1] & ALL_END_TAG_OPTIONAL_EL) {
            !!!cp ('t75');
            !!!parse-error (type => 'in body:#eof', token => $token);
            last;
          }
        }

        ## Stop parsing.
        last B;
      } else {
        die "$0: $token->{type}: Unknown token type";
      }

      $insert = $insert_to_current;
      #
    } elsif ($self->{insertion_mode} & TABLE_IMS) {
      if ($token->{type} == START_TAG_TOKEN) {
        if ({
             tr => (($self->{insertion_mode} & IM_MASK) != IN_ROW_IM),
             th => 1, td => 1,
            }->{$token->{tag_name}}) {
          if (($self->{insertion_mode} & IM_MASK) == IN_TABLE_IM) {
            ## Clear back to table context
            while (not ($self->{open_elements}->[-1]->[1]
                            & TABLE_SCOPING_EL)) {
              !!!cp ('t201');
              pop @{$self->{open_elements}};
            }
            
            !!!insert-element ('tbody',, $token);
            $self->{insertion_mode} = IN_TABLE_BODY_IM;
            ## reprocess in the "in table body" insertion mode...
          }
          
          if (($self->{insertion_mode} & IM_MASK) == IN_TABLE_BODY_IM) {
            unless ($token->{tag_name} eq 'tr') {
              !!!cp ('t202');
              !!!parse-error (type => 'missing start tag:tr', token => $token);
            }
                
            ## Clear back to table body context
            while (not ($self->{open_elements}->[-1]->[1]
                            & TABLE_ROWS_SCOPING_EL)) {
              !!!cp ('t203');
              ## ISSUE: Can this case be reached?
              pop @{$self->{open_elements}};
            }
                
            $self->{insertion_mode} = IN_ROW_IM;
            if ($token->{tag_name} eq 'tr') {
              !!!cp ('t204');
              !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
              $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
              !!!nack ('t204');
              !!!next-token;
              next B;
            } else {
              !!!cp ('t205');
              !!!insert-element ('tr',, $token);
              ## reprocess in the "in row" insertion mode
            }
          } else {
            !!!cp ('t206');
          }

              ## Clear back to table row context
              while (not ($self->{open_elements}->[-1]->[1]
                              & TABLE_ROW_SCOPING_EL)) {
                !!!cp ('t207');
                pop @{$self->{open_elements}};
              }
              
          !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
          $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
          $self->{insertion_mode} = IN_CELL_IM;

          push @$active_formatting_elements, ['#marker', ''];
              
          !!!nack ('t207.1');
          !!!next-token;
          next B;
        } elsif ({
                  caption => 1, col => 1, colgroup => 1,
                  tbody => 1, tfoot => 1, thead => 1,
                  tr => 1, # $self->{insertion_mode} == IN_ROW_IM
                 }->{$token->{tag_name}}) {
          if (($self->{insertion_mode} & IM_MASK) == IN_ROW_IM) {
            ## 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] == TABLE_ROW_EL) {
                !!!cp ('t208');
                $i = $_;
                last INSCOPE;
              } elsif ($node->[1] & TABLE_SCOPING_EL) {
                !!!cp ('t209');
                last INSCOPE;
              }
            } # INSCOPE
            unless (defined $i) { 
              !!!cp ('t210');
              ## TODO: This type is wrong.
              !!!parse-error (type => 'unmacthed end tag',
                              text => $token->{tag_name}, token => $token);
              ## Ignore the token
              !!!nack ('t210.1');
              !!!next-token;
              next B;
            }
                
                ## Clear back to table row context
                while (not ($self->{open_elements}->[-1]->[1]
                                & TABLE_ROW_SCOPING_EL)) {
                  !!!cp ('t211');
                  ## ISSUE: Can this case be reached?
                  pop @{$self->{open_elements}};
                }
                
                pop @{$self->{open_elements}}; # tr
                $self->{insertion_mode} = IN_TABLE_BODY_IM;
                if ($token->{tag_name} eq 'tr') {
                  !!!cp ('t212');
                  ## reprocess
                  !!!ack-later;
                  next B;
                } else {
                  !!!cp ('t213');
                  ## reprocess in the "in table body" insertion mode...
                }
              }

              if (($self->{insertion_mode} & IM_MASK) == IN_TABLE_BODY_IM) {
                ## have an element in table scope
                my $i;
                INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
                  my $node = $self->{open_elements}->[$_];
                  if ($node->[1] == TABLE_ROW_GROUP_EL) {
                    !!!cp ('t214');
                    $i = $_;
                    last INSCOPE;
                  } elsif ($node->[1] & TABLE_SCOPING_EL) {
                    !!!cp ('t215');
                    last INSCOPE;
                  }
                } # INSCOPE
                unless (defined $i) {
                  !!!cp ('t216');
## TODO: This erorr type is wrong.
                  !!!parse-error (type => 'unmatched end tag',
                                  text => $token->{tag_name}, token => $token);
                  ## Ignore the token
                  !!!nack ('t216.1');
                  !!!next-token;
                  next B;
                }

                ## Clear back to table body context
                while (not ($self->{open_elements}->[-1]->[1]
                                & TABLE_ROWS_SCOPING_EL)) {
                  !!!cp ('t217');
                  ## ISSUE: Can this state be reached?
                  pop @{$self->{open_elements}};
                }
                
                ## As if <{current node}>
                ## have an element in table scope
                ## true by definition
                
                ## Clear back to table body context
                ## nop by definition
                
                pop @{$self->{open_elements}};
                $self->{insertion_mode} = IN_TABLE_IM;
                ## reprocess in "in table" insertion mode...
              } else {
                !!!cp ('t218');
              }

          if ($token->{tag_name} eq 'col') {
            ## Clear back to table context
            while (not ($self->{open_elements}->[-1]->[1]
                            & TABLE_SCOPING_EL)) {
              !!!cp ('t219');
              ## ISSUE: Can this state be reached?
              pop @{$self->{open_elements}};
            }
            
            !!!insert-element ('colgroup',, $token);
            $self->{insertion_mode} = IN_COLUMN_GROUP_IM;
            ## reprocess
            $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
            !!!ack-later;
            next B;
          } elsif ({
                    caption => 1,
                    colgroup => 1,
                    tbody => 1, tfoot => 1, thead => 1,
                   }->{$token->{tag_name}}) {
            ## Clear back to table context
                while (not ($self->{open_elements}->[-1]->[1]
                                & TABLE_SCOPING_EL)) {
                  !!!cp ('t220');
                  ## ISSUE: Can this state be reached?
                  pop @{$self->{open_elements}};
                }
                
            push @$active_formatting_elements, ['#marker', '']
                if $token->{tag_name} eq 'caption';
                
            !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
            $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
            $self->{insertion_mode} = {
                                       caption => IN_CAPTION_IM,
                                       colgroup => IN_COLUMN_GROUP_IM,
                                       tbody => IN_TABLE_BODY_IM,
                                       tfoot => IN_TABLE_BODY_IM,
                                       thead => IN_TABLE_BODY_IM,
                                      }->{$token->{tag_name}};
            !!!next-token;
            !!!nack ('t220.1');
            next B;
          } else {
            die "$0: in table: <>: $token->{tag_name}";
          }
            } elsif ($token->{tag_name} eq 'table') {
              !!!parse-error (type => 'not closed',
                              text => $self->{open_elements}->[-1]->[0]
                                  ->manakai_local_name,
                              token => $token);

              ## As if </table>
              ## have a table element in table scope
              my $i;
              INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
                my $node = $self->{open_elements}->[$_];
                if ($node->[1] == TABLE_EL) {
                  !!!cp ('t221');
                  $i = $_;
                  last INSCOPE;
                } elsif ($node->[1] & TABLE_SCOPING_EL) {
                  !!!cp ('t222');
                  last INSCOPE;
                }
              } # INSCOPE
              unless (defined $i) {
                !!!cp ('t223');
## TODO: The following is wrong, maybe.
                !!!parse-error (type => 'unmatched end tag', text => 'table',
                                token => $token);
                ## Ignore tokens </table><table>
                !!!nack ('t223.1');
                !!!next-token;
                next B;
              }
              
## TODO: Followings are removed from the latest spec. 
              ## generate implied end tags
              while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
                !!!cp ('t224');
                pop @{$self->{open_elements}};
              }

              unless ($self->{open_elements}->[-1]->[1] == TABLE_EL) {
                !!!cp ('t225');
                ## NOTE: |<table><tr><table>|
                !!!parse-error (type => 'not closed',
                                text => $self->{open_elements}->[-1]->[0]
                                    ->manakai_local_name,
                                token => $token);
              } else {
                !!!cp ('t226');
              }

              splice @{$self->{open_elements}}, $i;
              pop @{$open_tables};

              $self->_reset_insertion_mode; 

          ## reprocess
          !!!ack-later;
          next B;
        } elsif ($token->{tag_name} eq 'style') {
          !!!cp ('t227.8');
          ## NOTE: This is a "as if in head" code clone.
          $parse_rcdata->(CDATA_CONTENT_MODEL);
          $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
          next B;
        } elsif ($token->{tag_name} eq 'script') {
          !!!cp ('t227.6');
          ## NOTE: This is a "as if in head" code clone.
          $script_start_tag->();
          $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted
          next B;
        } elsif ($token->{tag_name} eq 'input') {
          if ($token->{attributes}->{type}) {
            my $type = $token->{attributes}->{type}->{value};
            $type =~ tr/A-Z/a-z/; ## ASCII case-insensitive.
            if ($type eq 'hidden') {
              !!!cp ('t227.3');
              !!!parse-error (type => 'in table',
                              text => $token->{tag_name}, token => $token);

              !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
              $open_tables->[-1]->[2] = 0 if @$open_tables; # ~node inserted

              ## TODO: form element pointer

              pop @{$self->{open_elements}};

              !!!next-token;
              !!!ack ('t227.2.1');
              next B;
            } else {
              !!!cp ('t227.1');
              #
            }
          } else {
            !!!cp ('t227.4');
            #
          }
        } else {
          !!!cp ('t227');
          #
        }

        !!!parse-error (type => 'in table', text => $token->{tag_name},
                        token => $token);

        $insert = $insert_to_foster;
        #
      } elsif ($token->{type} == END_TAG_TOKEN) {
        if ($token->{tag_name} eq 'tr' and
            ($self->{insertion_mode} & IM_MASK) == IN_ROW_IM) {
          ## have an element in table scope
              my $i;
              INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
                my $node = $self->{open_elements}->[$_];
                if ($node->[1] == TABLE_ROW_EL) {
                  !!!cp ('t228');
                  $i = $_;
                  last INSCOPE;
                } elsif ($node->[1] & TABLE_SCOPING_EL) {
                  !!!cp ('t229');
                  last INSCOPE;
                }
              } # INSCOPE
              unless (defined $i) {
                !!!cp ('t230');
                !!!parse-error (type => 'unmatched end tag',
                                text => $token->{tag_name}, token => $token);
                ## Ignore the token
                !!!nack ('t230.1');
                !!!next-token;
                next B;
              } else {
                !!!cp ('t232');
              }

              ## Clear back to table row context
              while (not ($self->{open_elements}->[-1]->[1]
                              & TABLE_ROW_SCOPING_EL)) {
                !!!cp ('t231');
## ISSUE: Can this state be reached?
                pop @{$self->{open_elements}};
              }

              pop @{$self->{open_elements}}; # tr
              $self->{insertion_mode} = IN_TABLE_BODY_IM;
              !!!next-token;
              !!!nack ('t231.1');
              next B;
            } elsif ($token->{tag_name} eq 'table') {
              if (($self->{insertion_mode} & IM_MASK) == IN_ROW_IM) {
                ## 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] == TABLE_ROW_EL) {
                    !!!cp ('t233');
                    $i = $_;
                    last INSCOPE;
                  } elsif ($node->[1] & TABLE_SCOPING_EL) {
                    !!!cp ('t234');
                    last INSCOPE;
                  }
                } # INSCOPE
                unless (defined $i) {
                  !!!cp ('t235');
## TODO: The following is wrong.
                  !!!parse-error (type => 'unmatched end tag',
                                  text => $token->{type}, token => $token);
                  ## Ignore the token
                  !!!nack ('t236.1');
                  !!!next-token;
                  next B;
                }
                
                ## Clear back to table row context
                while (not ($self->{open_elements}->[-1]->[1]
                                & TABLE_ROW_SCOPING_EL)) {
                  !!!cp ('t236');
## ISSUE: Can this state be reached?
                  pop @{$self->{open_elements}};
                }
                
                pop @{$self->{open_elements}}; # tr
                $self->{insertion_mode} = IN_TABLE_BODY_IM;
                ## reprocess in the "in table body" insertion mode...
              }

              if (($self->{insertion_mode} & IM_MASK) == IN_TABLE_BODY_IM) {
                ## have an element in table scope
                my $i;
                INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
                  my $node = $self->{open_elements}->[$_];
                  if ($node->[1] == TABLE_ROW_GROUP_EL) {
                    !!!cp ('t237');
                    $i = $_;
                    last INSCOPE;
                  } elsif ($node->[1] & TABLE_SCOPING_EL) {
                    !!!cp ('t238');
                    last INSCOPE;
                  }
                } # INSCOPE
                unless (defined $i) {
                  !!!cp ('t239');
                  !!!parse-error (type => 'unmatched end tag',
                                  text => $token->{tag_name}, token => $token);
                  ## Ignore the token
                  !!!nack ('t239.1');
                  !!!next-token;
                  next B;
                }
                
                ## Clear back to table body context
                while (not ($self->{open_elements}->[-1]->[1]
                                & TABLE_ROWS_SCOPING_EL)) {
                  !!!cp ('t240');
                  pop @{$self->{open_elements}};
                }
                
                ## As if <{current node}>
                ## have an element in table scope
                ## true by definition
                
                ## Clear back to table body context
                ## nop by definition
                
                pop @{$self->{open_elements}};
                $self->{insertion_mode} = IN_TABLE_IM;
                ## reprocess in the "in table" insertion mode...
              }

              ## NOTE: </table> in the "in table" insertion mode.
              ## When you edit the code fragment below, please ensure that
              ## the code for <table> in the "in table" insertion mode
              ## is synced with it.

              ## have a table element in table scope
              my $i;
              INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
                my $node = $self->{open_elements}->[$_];
                if ($node->[1] == TABLE_EL) {
                  !!!cp ('t241');
                  $i = $_;
                  last INSCOPE;
                } elsif ($node->[1] & TABLE_SCOPING_EL) {
                  !!!cp ('t242');
                  last INSCOPE;
                }
              } # INSCOPE
              unless (defined $i) {
                !!!cp ('t243');
                !!!parse-error (type => 'unmatched end tag',
                                text => $token->{tag_name}, token => $token);
                ## Ignore the token
                !!!nack ('t243.1');
                !!!next-token;
                next B;
              }
                
              splice @{$self->{open_elements}}, $i;
              pop @{$open_tables};
              
              $self->_reset_insertion_mode;
              
              !!!next-token;
              next B;
            } elsif ({
                      tbody => 1, tfoot => 1, thead => 1,
                     }->{$token->{tag_name}} and
                     $self->{insertion_mode} & ROW_IMS) {
              if (($self->{insertion_mode} & IM_MASK) == IN_ROW_IM) {
                ## have an element in table scope
                my $i;
                INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
                  my $node = $self->{open_elements}->[$_];
                  if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
                    !!!cp ('t247');
                    $i = $_;
                    last INSCOPE;
                  } elsif ($node->[1] & TABLE_SCOPING_EL) {
                    !!!cp ('t248');
                    last INSCOPE;
                  }
                } # INSCOPE
                  unless (defined $i) {
                    !!!cp ('t249');
                    !!!parse-error (type => 'unmatched end tag',
                                    text => $token->{tag_name}, token => $token);
                    ## Ignore the token
                    !!!nack ('t249.1');
                    !!!next-token;
                    next B;
                  }
                
                ## 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] == TABLE_ROW_EL) {
                    !!!cp ('t250');
                    $i = $_;
                    last INSCOPE;
                  } elsif ($node->[1] & TABLE_SCOPING_EL) {
                    !!!cp ('t251');
                    last INSCOPE;
                  }
                } # INSCOPE
                  unless (defined $i) {
                    !!!cp ('t252');
                    !!!parse-error (type => 'unmatched end tag',
                                    text => 'tr', token => $token);
                    ## Ignore the token
                    !!!nack ('t252.1');
                    !!!next-token;
                    next B;
                  }
                
                ## Clear back to table row context
                while (not ($self->{open_elements}->[-1]->[1]
                                & TABLE_ROW_SCOPING_EL)) {
                  !!!cp ('t253');
## ISSUE: Can this case be reached?
                  pop @{$self->{open_elements}};
                }
                
                pop @{$self->{open_elements}}; # tr
                $self->{insertion_mode} = IN_TABLE_BODY_IM;
                ## reprocess in the "in table body" insertion mode...
              }

              ## have an element in table scope
              my $i;
              INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
                my $node = $self->{open_elements}->[$_];
                if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
                  !!!cp ('t254');
                  $i = $_;
                  last INSCOPE;
                } elsif ($node->[1] & TABLE_SCOPING_EL) {
                  !!!cp ('t255');
                  last INSCOPE;
                }
              } # INSCOPE
              unless (defined $i) {
                !!!cp ('t256');
                !!!parse-error (type => 'unmatched end tag',
                                text => $token->{tag_name}, token => $token);
                ## Ignore the token
                !!!nack ('t256.1');
                !!!next-token;
                next B;
              }

              ## Clear back to table body context
              while (not ($self->{open_elements}->[-1]->[1]
                              & TABLE_ROWS_SCOPING_EL)) {
                !!!cp ('t257');
## ISSUE: Can this case be reached?
                pop @{$self->{open_elements}};
              }

              pop @{$self->{open_elements}};
              $self->{insertion_mode} = IN_TABLE_IM;
              !!!nack ('t257.1');
              !!!next-token;
              next B;
            } elsif ({
                      body => 1, caption => 1, col => 1, colgroup => 1,
                      html => 1, td => 1, th => 1,
                      tr => 1, # $self->{insertion_mode} == IN_ROW_IM
                      tbody => 1, tfoot => 1, thead => 1, # $self->{insertion_mode} == IN_TABLE_IM
                     }->{$token->{tag_name}}) {
          !!!cp ('t258');
          !!!parse-error (type => 'unmatched end tag',
                          text => $token->{tag_name}, token => $token);
          ## Ignore the token
          !!!nack ('t258.1');
           !!!next-token;
          next B;
        } else {
          !!!cp ('t259');
          !!!parse-error (type => 'in table:/',
                          text => $token->{tag_name}, token => $token);

          $insert = $insert_to_foster;
          #
        }
      } elsif ($token->{type} == END_OF_FILE_TOKEN) {
        unless ($self->{open_elements}->[-1]->[1] == HTML_EL and
                @{$self->{open_elements}} == 1) { # redundant, maybe
          !!!parse-error (type => 'in body:#eof', token => $token);
          !!!cp ('t259.1');
          #
        } else {
          !!!cp ('t259.2');
          #
        }

        ## Stop parsing
        last B;
      } else {
        die "$0: $token->{type}: Unknown token type";
      }
    } elsif (($self->{insertion_mode} & IM_MASK) == IN_COLUMN_GROUP_IM) {
          if ($token->{type} == CHARACTER_TOKEN) {
            if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
              $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
              unless (length $token->{data}) {
                !!!cp ('t260');
                !!!next-token;
                next B;
              }
            }
            
            !!!cp ('t261');
            #
          } elsif ($token->{type} == START_TAG_TOKEN) {
            if ($token->{tag_name} eq 'col') {
              !!!cp ('t262');
              !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
              pop @{$self->{open_elements}};
              !!!ack ('t262.1');
              !!!next-token;
              next B;
            } else { 
              !!!cp ('t263');
              #
            }
          } elsif ($token->{type} == END_TAG_TOKEN) {
            if ($token->{tag_name} eq 'colgroup') {
              if ($self->{open_elements}->[-1]->[1] == HTML_EL) {
                !!!cp ('t264');
                !!!parse-error (type => 'unmatched end tag',
                                text => 'colgroup', token => $token);
                ## Ignore the token
                !!!next-token;
                next B;
              } else {
                !!!cp ('t265');
                pop @{$self->{open_elements}}; # colgroup
                $self->{insertion_mode} = IN_TABLE_IM;
                !!!next-token;
                next B;             
              }
            } elsif ($token->{tag_name} eq 'col') {
              !!!cp ('t266');
              !!!parse-error (type => 'unmatched end tag',
                              text => 'col', token => $token);
              ## Ignore the token
              !!!next-token;
              next B;
            } else {
              !!!cp ('t267');
              # 
            }
      } elsif ($token->{type} == END_OF_FILE_TOKEN) {
        if ($self->{open_elements}->[-1]->[1] == HTML_EL and
            @{$self->{open_elements}} == 1) { # redundant, maybe
          !!!cp ('t270.2');
          ## Stop parsing.
          last B;
        } else {
          ## NOTE: As if </colgroup>.
          !!!cp ('t270.1');
          pop @{$self->{open_elements}}; # colgroup
          $self->{insertion_mode} = IN_TABLE_IM;
          ## Reprocess.
          next B;
        }
      } else {
        die "$0: $token->{type}: Unknown token type";
      }

          ## As if </colgroup>
          if ($self->{open_elements}->[-1]->[1] == HTML_EL) {
            !!!cp ('t269');
## TODO: Wrong error type?
            !!!parse-error (type => 'unmatched end tag',
                            text => 'colgroup', token => $token);
            ## Ignore the token
            !!!nack ('t269.1');
            !!!next-token;
            next B;
          } else {
            !!!cp ('t270');
            pop @{$self->{open_elements}}; # colgroup
            $self->{insertion_mode} = IN_TABLE_IM;
            !!!ack-later;
            ## reprocess
            next B;
          }
    } elsif ($self->{insertion_mode} & SELECT_IMS) {
      if ($token->{type} == CHARACTER_TOKEN) {
        !!!cp ('t271');
        $self->{open_elements}->[-1]->[0]->manakai_append_text ($token->{data});
        !!!next-token;
        next B;
      } elsif ($token->{type} == START_TAG_TOKEN) {
        if ($token->{tag_name} eq 'option') {
          if ($self->{open_elements}->[-1]->[1] == OPTION_EL) {
            !!!cp ('t272');
            ## As if </option>
            pop @{$self->{open_elements}};
          } else {
            !!!cp ('t273');
          }

          !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
          !!!nack ('t273.1');
          !!!next-token;
          next B;
        } elsif ($token->{tag_name} eq 'optgroup') {
          if ($self->{open_elements}->[-1]->[1] == OPTION_EL) {
            !!!cp ('t274');
            ## As if </option>
            pop @{$self->{open_elements}};
          } else {
            !!!cp ('t275');
          }

          if ($self->{open_elements}->[-1]->[1] == OPTGROUP_EL) {
            !!!cp ('t276');
            ## As if </optgroup>
            pop @{$self->{open_elements}};
          } else {
            !!!cp ('t277');
          }

          !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
          !!!nack ('t277.1');
          !!!next-token;
          next B;
        } elsif ({
                   select => 1, input => 1, textarea => 1, keygen => 1,
                 }->{$token->{tag_name}} or
                 (($self->{insertion_mode} & IM_MASK)
                      == IN_SELECT_IN_TABLE_IM and
                  {
                   caption => 1, table => 1,
                   tbody => 1, tfoot => 1, thead => 1,
                   tr => 1, td => 1, th => 1,
                  }->{$token->{tag_name}})) {

          ## 1. Parse error.
          if ($token->{tag_name} eq 'select') {
              !!!parse-error (type => 'select in select', ## XXX: documentation
                              token => $token);
          } else {
            !!!parse-error (type => 'not closed', text => 'select',
                            token => $token);
          }

          ## 2./<select>-1. Unless "have an element in table scope" (select):
          my $i;
          INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
            my $node = $self->{open_elements}->[$_];
            if ($node->[1] == SELECT_EL) {
              !!!cp ('t278');
              $i = $_;
              last INSCOPE;
            } elsif ($node->[1] & TABLE_SCOPING_EL) {
              !!!cp ('t279');
              last INSCOPE;
            }
          } # INSCOPE
          unless (defined $i) {
            !!!cp ('t280');
            if ($token->{tag_name} eq 'select') {
              ## NOTE: This error would be raised when
              ## |select.innerHTML = '<select>'| is executed; in this
              ## case two errors, "select in select" and "unmatched
              ## end tags" are reported to the user, the latter might
              ## be confusing but this is what the spec requires.
              !!!parse-error (type => 'unmatched end tag',
                              text => 'select',
                              token => $token);
            }
            ## Ignore the token.
            !!!nack ('t280.1');
            !!!next-token;
            next B;
          }

          ## 3. Otherwise, as if there were <select>:
              
          !!!cp ('t281');
          splice @{$self->{open_elements}}, $i;

          $self->_reset_insertion_mode;

          if ($token->{tag_name} eq 'select') {
            !!!nack ('t281.2');
            !!!next-token;
            next B;
          } else {
            !!!cp ('t281.1');
            !!!ack-later;
            ## Reprocess the token.
            next B;
          }
        } elsif ($token->{tag_name} eq 'script') {
          !!!cp ('t281.3');
          ## NOTE: This is an "as if in head" code clone
          $script_start_tag->();
          next B;
        } else {
          !!!cp ('t282');
          !!!parse-error (type => 'in select',
                          text => $token->{tag_name}, token => $token);
          ## Ignore the token
          !!!nack ('t282.1');
          !!!next-token;
          next B;
        }
      } elsif ($token->{type} == END_TAG_TOKEN) {
        if ($token->{tag_name} eq 'optgroup') {
          if ($self->{open_elements}->[-1]->[1] == OPTION_EL and
              $self->{open_elements}->[-2]->[1] == OPTGROUP_EL) {
            !!!cp ('t283');
            ## As if </option>
            splice @{$self->{open_elements}}, -2;
          } elsif ($self->{open_elements}->[-1]->[1] == OPTGROUP_EL) {
            !!!cp ('t284');
            pop @{$self->{open_elements}};
          } else {
            !!!cp ('t285');
            !!!parse-error (type => 'unmatched end tag',
                            text => $token->{tag_name}, token => $token);
            ## Ignore the token
          }
          !!!nack ('t285.1');
          !!!next-token;
          next B;
        } elsif ($token->{tag_name} eq 'option') {
          if ($self->{open_elements}->[-1]->[1] == OPTION_EL) {
            !!!cp ('t286');
            pop @{$self->{open_elements}};
          } else {
            !!!cp ('t287');
            !!!parse-error (type => 'unmatched end tag',
                            text => $token->{tag_name}, token => $token);
            ## Ignore the token
          }
          !!!nack ('t287.1');
          !!!next-token;
          next 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] == SELECT_EL) {
              !!!cp ('t288');
              $i = $_;
              last INSCOPE;
            } elsif ($node->[1] & TABLE_SCOPING_EL) {
              !!!cp ('t289');
              last INSCOPE;
            }
          } # INSCOPE
          unless (defined $i) {
            !!!cp ('t290');
            !!!parse-error (type => 'unmatched end tag',
                            text => $token->{tag_name}, token => $token);
            ## Ignore the token
            !!!nack ('t290.1');
            !!!next-token;
            next B;
          }
              
          !!!cp ('t291');
          splice @{$self->{open_elements}}, $i;

          $self->_reset_insertion_mode;

          !!!nack ('t291.1');
          !!!next-token;
          next B;
        } elsif (($self->{insertion_mode} & IM_MASK)
                     == IN_SELECT_IN_TABLE_IM and
                 {
                  caption => 1, table => 1, tbody => 1,
                  tfoot => 1, thead => 1, tr => 1, td => 1, th => 1,
                 }->{$token->{tag_name}}) {
## TODO: The following is wrong?
          !!!parse-error (type => 'unmatched end tag',
                          text => $token->{tag_name}, token => $token);
              
          ## have an element in table scope
          my $i;
          INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
            my $node = $self->{open_elements}->[$_];
            if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
              !!!cp ('t292');
              $i = $_;
              last INSCOPE;
            } elsif ($node->[1] & TABLE_SCOPING_EL) {
              !!!cp ('t293');
              last INSCOPE;
            }
          } # INSCOPE
          unless (defined $i) {
            !!!cp ('t294');
            ## Ignore the token
            !!!nack ('t294.1');
            !!!next-token;
            next B;
          }
              
          ## As if </select>
          ## have an element in table scope
          undef $i;
          INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
            my $node = $self->{open_elements}->[$_];
            if ($node->[1] == SELECT_EL) {
              !!!cp ('t295');
              $i = $_;
              last INSCOPE;
            } elsif ($node->[1] & TABLE_SCOPING_EL) {
## ISSUE: Can this state be reached?
              !!!cp ('t296');
              last INSCOPE;
            }
          } # INSCOPE
          unless (defined $i) {
            !!!cp ('t297');
## TODO: The following error type is correct?
            !!!parse-error (type => 'unmatched end tag',
                            text => 'select', token => $token);
            ## Ignore the </select> token
            !!!nack ('t297.1');
            !!!next-token; ## TODO: ok?
            next B;
          }
              
          !!!cp ('t298');
          splice @{$self->{open_elements}}, $i;

          $self->_reset_insertion_mode;

          !!!ack-later;
          ## reprocess
          next B;
        } else {
          !!!cp ('t299');
          !!!parse-error (type => 'in select:/',
                          text => $token->{tag_name}, token => $token);
          ## Ignore the token
          !!!nack ('t299.3');
          !!!next-token;
          next B;
        }
      } elsif ($token->{type} == END_OF_FILE_TOKEN) {
        unless ($self->{open_elements}->[-1]->[1] == HTML_EL and
                @{$self->{open_elements}} == 1) { # redundant, maybe
          !!!cp ('t299.1');
          !!!parse-error (type => 'in body:#eof', token => $token);
        } else {
          !!!cp ('t299.2');
        }

        ## Stop parsing.
        last B;
      } else {
        die "$0: $token->{type}: Unknown token type";
      }
    } elsif ($self->{insertion_mode} & BODY_AFTER_IMS) {
      if ($token->{type} == CHARACTER_TOKEN) {
        if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
          my $data = $1;
          ## As if in body
          $reconstruct_active_formatting_elements->($insert_to_current);
              
          $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
          
          unless (length $token->{data}) {
            !!!cp ('t300');
            !!!next-token;
            next B;
          }
        }
        
        if ($self->{insertion_mode} == AFTER_HTML_BODY_IM) {
          !!!cp ('t301');
          !!!parse-error (type => 'after html:#text', token => $token);
          #
        } else {
          !!!cp ('t302');
          ## "after body" insertion mode
          !!!parse-error (type => 'after body:#text', token => $token);
          #
        }

        $self->{insertion_mode} = IN_BODY_IM;
        ## reprocess
        next B;
      } elsif ($token->{type} == START_TAG_TOKEN) {
        if ($self->{insertion_mode} == AFTER_HTML_BODY_IM) {
          !!!cp ('t303');
          !!!parse-error (type => 'after html',
                          text => $token->{tag_name}, token => $token);
          #
        } else {
          !!!cp ('t304');
          ## "after body" insertion mode
          !!!parse-error (type => 'after body',
                          text => $token->{tag_name}, token => $token);
          #
        }

        $self->{insertion_mode} = IN_BODY_IM;
        !!!ack-later;
        ## reprocess
        next B;
      } elsif ($token->{type} == END_TAG_TOKEN) {
        if ($self->{insertion_mode} == AFTER_HTML_BODY_IM) {
          !!!cp ('t305');
          !!!parse-error (type => 'after html:/',
                          text => $token->{tag_name}, token => $token);
          
          $self->{insertion_mode} = IN_BODY_IM;
          ## Reprocess.
          next B;
        } else {
          !!!cp ('t306');
        }

        ## "after body" insertion mode
        if ($token->{tag_name} eq 'html') {
          if (defined $self->{inner_html_node}) {
            !!!cp ('t307');
            !!!parse-error (type => 'unmatched end tag',
                            text => 'html', token => $token);
            ## Ignore the token
            !!!next-token;
            next B;
          } else {
            !!!cp ('t308');
            $self->{insertion_mode} = AFTER_HTML_BODY_IM;
            !!!next-token;
            next B;
          }
        } else {
          !!!cp ('t309');
          !!!parse-error (type => 'after body:/',
                          text => $token->{tag_name}, token => $token);

          $self->{insertion_mode} = IN_BODY_IM;
          ## reprocess
          next B;
        }
      } elsif ($token->{type} == END_OF_FILE_TOKEN) {
        !!!cp ('t309.2');
        ## Stop parsing
        last B;
      } else {
        die "$0: $token->{type}: Unknown token type";
      }
    } elsif ($self->{insertion_mode} & FRAME_IMS) {
      if ($token->{type} == CHARACTER_TOKEN) {
        if ($token->{data} =~ s/^([\x09\x0A\x0C\x20]+)//) {
          $self->{open_elements}->[-1]->[0]->manakai_append_text ($1);
          
          unless (length $token->{data}) {
            !!!cp ('t310');
            !!!next-token;
            next B;
          }
        }
        
        if ($token->{data} =~ s/^[^\x09\x0A\x0C\x20]+//) {
          if ($self->{insertion_mode} == IN_FRAMESET_IM) {
            !!!cp ('t311');
            !!!parse-error (type => 'in frameset:#text', token => $token);
          } elsif ($self->{insertion_mode} == AFTER_FRAMESET_IM) {
            !!!cp ('t312');
            !!!parse-error (type => 'after frameset:#text', token => $token);
          } else { # "after after frameset"
            !!!cp ('t313');
            !!!parse-error (type => 'after html:#text', token => $token);
          }
          
          ## Ignore the token.
          if (length $token->{data}) {
            !!!cp ('t314');
            ## reprocess the rest of characters
          } else {
            !!!cp ('t315');
            !!!next-token;
          }
          next B;
        }
        
        die qq[$0: Character "$token->{data}"];
      } elsif ($token->{type} == START_TAG_TOKEN) {
        if ($token->{tag_name} eq 'frameset' and
            $self->{insertion_mode} == IN_FRAMESET_IM) {
          !!!cp ('t318');
          !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
          !!!nack ('t318.1');
          !!!next-token;
          next B;
        } elsif ($token->{tag_name} eq 'frame' and
                 $self->{insertion_mode} == IN_FRAMESET_IM) {
          !!!cp ('t319');
          !!!insert-element ($token->{tag_name}, $token->{attributes}, $token);
          pop @{$self->{open_elements}};
          !!!ack ('t319.1');
          !!!next-token;
          next B;
        } elsif ($token->{tag_name} eq 'noframes') {
          !!!cp ('t320');
          ## NOTE: As if in head.
          $parse_rcdata->(CDATA_CONTENT_MODEL);
          next B;

          ## NOTE: |<!DOCTYPE HTML><frameset></frameset></html><noframes></noframes>|
          ## has no parse error.
        } else {
          if ($self->{insertion_mode} == IN_FRAMESET_IM) {
            !!!cp ('t321');
            !!!parse-error (type => 'in frameset',
                            text => $token->{tag_name}, token => $token);
          } elsif ($self->{insertion_mode} == AFTER_FRAMESET_IM) {
            !!!cp ('t322');
            !!!parse-error (type => 'after frameset',
                            text => $token->{tag_name}, token => $token);
          } else { # "after after frameset"
            !!!cp ('t322.2');
            !!!parse-error (type => 'after after frameset',
                            text => $token->{tag_name}, token => $token);
          }
          ## Ignore the token
          !!!nack ('t322.1');
          !!!next-token;
          next B;
        }
      } elsif ($token->{type} == END_TAG_TOKEN) {
        if ($token->{tag_name} eq 'frameset' and
            $self->{insertion_mode} == IN_FRAMESET_IM) {
          if ($self->{open_elements}->[-1]->[1] == HTML_EL and
              @{$self->{open_elements}} == 1) {
            !!!cp ('t325');
            !!!parse-error (type => 'unmatched end tag',
                            text => $token->{tag_name}, token => $token);
            ## Ignore the token
            !!!next-token;
          } else {
            !!!cp ('t326');
            pop @{$self->{open_elements}};
            !!!next-token;
          }

          if (not defined $self->{inner_html_node} and
              not ($self->{open_elements}->[-1]->[1] == FRAMESET_EL)) {
            !!!cp ('t327');
            $self->{insertion_mode} = AFTER_FRAMESET_IM;
          } else {
            !!!cp ('t328');
          }
          next B;
        } elsif ($token->{tag_name} eq 'html' and
                 $self->{insertion_mode} == AFTER_FRAMESET_IM) {
          !!!cp ('t329');
          $self->{insertion_mode} = AFTER_HTML_FRAMESET_IM;
          !!!next-token;
          next B;
        } else {
          if ($self->{insertion_mode} == IN_FRAMESET_IM) {
            !!!cp ('t330');
            !!!parse-error (type => 'in frameset:/',
                            text => $token->{tag_name}, token => $token);
          } elsif ($self->{insertion_mode} == AFTER_FRAMESET_IM) {
            !!!cp ('t330.1');
            !!!parse-error (type => 'after frameset:/',
                            text => $token->{tag_name}, token => $token);
          } else { # "after after html"
            !!!cp ('t331');
            !!!parse-error (type => 'after after frameset:/',
                            text => $token->{tag_name}, token => $token);
          }
          ## Ignore the token
          !!!next-token;
          next B;
        }
      } elsif ($token->{type} == END_OF_FILE_TOKEN) {
        unless ($self->{open_elements}->[-1]->[1] == HTML_EL and
                @{$self->{open_elements}} == 1) { # redundant, maybe
          !!!cp ('t331.1');
          !!!parse-error (type => 'in body:#eof', token => $token);
        } else {
          !!!cp ('t331.2');
        }
        
        ## Stop parsing
        last B;
      } else {
        die "$0: $token->{type}: Unknown token type";
      }
    } else {
      die "$0: $self->{insertion_mode}: Unknown insertion mode";
    }

    ## "in body" insertion mode
    if ($token->{type} == START_TAG_TOKEN) {
      if ($token->{tag_name} eq 'script') {
        !!!cp ('t332');
        ## NOTE: This is an "as if in head" code clone
        $script_start_tag->();
        next B;
      } elsif ($token->{tag_name} eq 'style') {
        !!!cp ('t333');
        ## NOTE: This is an "as if in head" code clone
        $parse_rcdata->(CDATA_CONTENT_MODEL);
        next B;
      } elsif ({
                base => 1, command => 1, link => 1,
               }->{$token->{tag_name}}) {
        !!!cp ('t334');
        ## NOTE: This is an "as if in head" code clone, only "-t" differs
        !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
        pop @{$self->{open_elements}};
        !!!ack ('t334.1');
        !!!next-token;
        next B;
      } elsif ($token->{tag_name} eq 'meta') {
        ## NOTE: This is an "as if in head" code clone, only "-t" differs
        !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
        my $meta_el = pop @{$self->{open_elements}};

        unless ($self->{confident}) {
          if ($token->{attributes}->{charset}) {
            !!!cp ('t335');
            ## NOTE: Whether the encoding is supported or not is handled
            ## in the {change_encoding} callback.
            $self->{change_encoding}
                ->($self, $token->{attributes}->{charset}->{value}, $token);
            
            $meta_el->[0]->get_attribute_node_ns (undef, 'charset')
                ->set_user_data (manakai_has_reference =>
                                     $token->{attributes}->{charset}
                                         ->{has_reference});
          } elsif ($token->{attributes}->{content}) {
            if ($token->{attributes}->{content}->{value}
                =~ /[Cc][Hh][Aa][Rr][Ss][Ee][Tt]
                    [\x09\x0A\x0C\x0D\x20]*=
                    [\x09\x0A\x0C\x0D\x20]*(?>"([^"]*)"|'([^']*)'|
                    ([^"'\x09\x0A\x0C\x0D\x20][^\x09\x0A\x0C\x0D\x20\x3B]*))
                   /x) {
              !!!cp ('t336');
              ## NOTE: Whether the encoding is supported or not is handled
              ## in the {change_encoding} callback.
              $self->{change_encoding}
                  ->($self, defined $1 ? $1 : defined $2 ? $2 : $3, $token);
              $meta_el->[0]->get_attribute_node_ns (undef, 'content')
                  ->set_user_data (manakai_has_reference =>
                                       $token->{attributes}->{content}
                                             ->{has_reference});
            }
          }
        } else {
          if ($token->{attributes}->{charset}) {
            !!!cp ('t337');
            $meta_el->[0]->get_attribute_node_ns (undef, 'charset')
                ->set_user_data (manakai_has_reference =>
                                     $token->{attributes}->{charset}
                                         ->{has_reference});
          }
          if ($token->{attributes}->{content}) {
            !!!cp ('t338');
            $meta_el->[0]->get_attribute_node_ns (undef, 'content')
                ->set_user_data (manakai_has_reference =>
                                     $token->{attributes}->{content}
                                         ->{has_reference});
          }
        }

        !!!ack ('t338.1');
        !!!next-token;
        next B;
      } elsif ($token->{tag_name} eq 'title') {
        !!!cp ('t341');
        ## NOTE: This is an "as if in head" code clone
        $parse_rcdata->(RCDATA_CONTENT_MODEL);
        next B;
      } elsif ($token->{tag_name} eq 'body') {
        !!!parse-error (type => 'in body', text => 'body', token => $token);
              
        if (@{$self->{open_elements}} == 1 or
            not ($self->{open_elements}->[1]->[1] == BODY_EL)) {
          !!!cp ('t342');
          ## 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)) {
              !!!cp ('t343');
              $body_el->set_attribute_ns
                (undef, [undef, $attr_name],
                 $token->{attributes}->{$attr_name}->{value});
            }
          }
        }
        !!!nack ('t343.1');
        !!!next-token;
        next B;
      } elsif ($token->{tag_nane} eq 'frameset') {
        !!!parse-error (type => 'in body', text => $token->{tag_name},
                        token => $token);

        if (@{$self->{open_elements}} == 1 or
            not ($self->{open_elements}->[1]->[1] != BODY_EL)) {
          !!!cp ('t343.2');
          ## Ignore the token.
        } elsif (not $self->{frameset_ok}) {
          !!!cp ('t343.3');
          ## Ignore the token.
        } else {
          !!!cp ('t343.4');
          
          ## 1. Remove the second element.
          my $body = $self->{open_elements}->[1]->[0];
          my $body_parent = $body->parent_node;
          $body_parent->remove_child ($body) if $body_parent;

          ## 2. Pop nodes.
          splice @{$self->{open_elements}}, 1;

          ## 3. Insert.
          !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);

          ## 4. Switch.
          $self->{insertion_mode} = IN_FRAMESET_IM;
        }

        !!!nack ('t343.5');
        !!!next-token;
        next B;
      } elsif ({
                ## NOTE: Start tags for non-phrasing flow content elements

                ## NOTE: The normal one
                address => 1, article => 1, aside => 1, blockquote => 1,
                center => 1, datagrid => 1, details => 1, dialog => 1,
                dir => 1, div => 1, dl => 1, fieldset => 1, figure => 1,
                footer => 1, h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1,
                h6 => 1, header => 1, hgroup => 1,
                menu => 1, nav => 1, ol => 1, p => 1, 
                section => 1, ul => 1,
                ## NOTE: As normal, but drops leading newline
                pre => 1, listing => 1,
                ## NOTE: As normal, but interacts with the form element pointer
                form => 1,
                
                table => 1,
                hr => 1,
               }->{$token->{tag_name}}) {

        ## 1. When there is an opening |form| element:
        if ($token->{tag_name} eq 'form' and defined $self->{form_element}) {
          !!!cp ('t350');
          !!!parse-error (type => 'in form:form', token => $token);
          ## Ignore the token
          !!!nack ('t350.1');
          !!!next-token;
          next B;
        }

        ## 2. Close the |p| element, if any.
        if ($token->{tag_name} ne 'table' or # The Hixie Quirk
            $self->{document}->manakai_compat_mode ne 'quirks') {
          ## has a p element in scope
          INSCOPE: for (reverse @{$self->{open_elements}}) {
            if ($_->[1] == P_EL) {
              !!!cp ('t344');
              !!!back-token; # <form>
              $token = {type => END_TAG_TOKEN, tag_name => 'p',
                        line => $token->{line}, column => $token->{column}};
              next B;
            } elsif ($_->[1] & SCOPING_EL) {
              !!!cp ('t345');
              last INSCOPE;
            }
          } # INSCOPE
        }

        ## 3. Close the opening <hn> element, if any.
        if ({h1 => 1, h2 => 1, h3 => 1,
             h4 => 1, h5 => 1, h6 => 1}->{$token->{tag_name}}) {
          if ($self->{open_elements}->[-1]->[1] == HEADING_EL) {
            !!!parse-error (type => 'not closed',
                            text => $self->{open_elements}->[-1]->[0]->manakai_local_name,
                            token => $token);
            pop @{$self->{open_elements}};
          }
        }

        ## 4. Insertion.
        !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
        if ($token->{tag_name} eq 'pre' or $token->{tag_name} eq 'listing') {
          !!!nack ('t346.1');
          !!!next-token;
          if ($token->{type} == CHARACTER_TOKEN) {
            $token->{data} =~ s/^\x0A//;
            unless (length $token->{data}) {
              !!!cp ('t346');
              !!!next-token;
            } else {
              !!!cp ('t349');
            }
          } else {
            !!!cp ('t348');
          }
        } elsif ($token->{tag_name} eq 'form') {
          !!!cp ('t347.1');
          $self->{form_element} = $self->{open_elements}->[-1]->[0];

          !!!nack ('t347.2');
          !!!next-token;
        } elsif ($token->{tag_name} eq 'table') {
          !!!cp ('t382');
          push @{$open_tables}, [$self->{open_elements}->[-1]->[0]];
          
          $self->{insertion_mode} = IN_TABLE_IM;

          !!!nack ('t382.1');
          !!!next-token;
        } elsif ($token->{tag_name} eq 'hr') {
          !!!cp ('t386');
          pop @{$self->{open_elements}};
        
          !!!ack ('t386.1');
          !!!next-token;
        } else {
          !!!nack ('t347.1');
          !!!next-token;
        }
        next B;
      } elsif ($token->{tag_name} eq 'li') {
        ## NOTE: As normal, but imply </li> when there's another <li> ...

        ## NOTE: Special, Scope (<li><foo><li> == <li><foo><li/></foo></li>)::
          ## Interpreted as <li><foo/></li><li/> (non-conforming):
          ## blockquote (O9.27), center (O), dd (Fx3, O, S3.1.2, IE7),
          ## dt (Fx, O, S, IE), dl (O), fieldset (O, S, IE), form (Fx, O, S),
          ## hn (O), pre (O), applet (O, S), button (O, S), marquee (Fx, O, S),
          ## object (Fx)
          ## Generate non-tree (non-conforming):
          ## basefont (IE7 (where basefont is non-void)), center (IE),
          ## form (IE), hn (IE)
        ## address, div, p (<li><foo><li> == <li><foo/></li><li/>)::
          ## Interpreted as <li><foo><li/></foo></li> (non-conforming):
          ## div (Fx, S)

        my $non_optional;
        my $i = -1;

        ## 1.
        for my $node (reverse @{$self->{open_elements}}) {
          if ($node->[1] == LI_EL) {
            ## 2. (a) As if </li>
            {
              ## If no </li> - not applied
              #

              ## Otherwise

              ## 1. generate implied end tags, except for </li>
              #

              ## 2. If current node != "li", parse error
              if ($non_optional) {
                !!!parse-error (type => 'not closed',
                                text => $non_optional->[0]->manakai_local_name,
                                token => $token);
                !!!cp ('t355');
              } else {
                !!!cp ('t356');
              }

              ## 3. Pop
              splice @{$self->{open_elements}}, $i;
            }

            last; ## 2. (b) goto 5.
          } elsif (
                   ## NOTE: not "formatting" and not "phrasing"
                   ($node->[1] & SPECIAL_EL or
                    $node->[1] & SCOPING_EL) and
                   ## NOTE: "li", "dt", and "dd" are in |SPECIAL_EL|.
                   (not $node->[1] & ADDRESS_DIV_P_EL)
                  ) {
            ## 3.
            !!!cp ('t357');
            last; ## goto 5.
          } elsif ($node->[1] & END_TAG_OPTIONAL_EL) {
            !!!cp ('t358');
            #
          } else {
            !!!cp ('t359');
            $non_optional ||= $node;
            #
          }
          ## 4.
          ## goto 2.
          $i--;
        }

        ## 5. (a) has a |p| element in scope
        INSCOPE: for (reverse @{$self->{open_elements}}) {
          if ($_->[1] == P_EL) {
            !!!cp ('t353');

            ## NOTE: |<p><li>|, for example.

            !!!back-token; # <x>
            $token = {type => END_TAG_TOKEN, tag_name => 'p',
                      line => $token->{line}, column => $token->{column}};
            next B;
          } elsif ($_->[1] & SCOPING_EL) {
            !!!cp ('t354');
            last INSCOPE;
          }
        } # INSCOPE

        ## 5. (b) insert
        !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
        !!!nack ('t359.1');
        !!!next-token;
        next B;
      } elsif ($token->{tag_name} eq 'dt' or
               $token->{tag_name} eq 'dd') {
        ## NOTE: As normal, but imply </dt> or </dd> when ...

        my $non_optional;
        my $i = -1;

        ## 1.
        for my $node (reverse @{$self->{open_elements}}) {
          if ($node->[1] == DTDD_EL) {
            ## 2. (a) As if </li>
            {
              ## If no </li> - not applied
              #

              ## Otherwise

              ## 1. generate implied end tags, except for </dt> or </dd>
              #

              ## 2. If current node != "dt"|"dd", parse error
              if ($non_optional) {
                !!!parse-error (type => 'not closed',
                                text => $non_optional->[0]->manakai_local_name,
                                token => $token);
                !!!cp ('t355.1');
              } else {
                !!!cp ('t356.1');
              }

              ## 3. Pop
              splice @{$self->{open_elements}}, $i;
            }

            last; ## 2. (b) goto 5.
          } elsif (
                   ## NOTE: not "formatting" and not "phrasing"
                   ($node->[1] & SPECIAL_EL or
                    $node->[1] & SCOPING_EL) and
                   ## NOTE: "li", "dt", and "dd" are in |SPECIAL_EL|.

                   (not $node->[1] & ADDRESS_DIV_P_EL)
                  ) {
            ## 3.
            !!!cp ('t357.1');
            last; ## goto 5.
          } elsif ($node->[1] & END_TAG_OPTIONAL_EL) {
            !!!cp ('t358.1');
            #
          } else {
            !!!cp ('t359.1');
            $non_optional ||= $node;
            #
          }
          ## 4.
          ## goto 2.
          $i--;
        }

        ## 5. (a) has a |p| element in scope
        INSCOPE: for (reverse @{$self->{open_elements}}) {
          if ($_->[1] == P_EL) {
            !!!cp ('t353.1');
            !!!back-token; # <x>
            $token = {type => END_TAG_TOKEN, tag_name => 'p',
                      line => $token->{line}, column => $token->{column}};
            next B;
          } elsif ($_->[1] & SCOPING_EL) {
            !!!cp ('t354.1');
            last INSCOPE;
          }
        } # INSCOPE

        ## 5. (b) insert
        !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
        !!!nack ('t359.2');
        !!!next-token;
        next B;
      } elsif ($token->{tag_name} eq 'plaintext') {
        ## NOTE: As normal, but effectively ends parsing

        ## has a p element in scope
        INSCOPE: for (reverse @{$self->{open_elements}}) {
          if ($_->[1] == P_EL) {
            !!!cp ('t367');
            !!!back-token; # <plaintext>
            $token = {type => END_TAG_TOKEN, tag_name => 'p',
                      line => $token->{line}, column => $token->{column}};
            next B;
          } elsif ($_->[1] & SCOPING_EL) {
            !!!cp ('t368');
            last INSCOPE;
          }
        } # INSCOPE
          
        !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
          
        $self->{content_model} = PLAINTEXT_CONTENT_MODEL;
          
        !!!nack ('t368.1');
        !!!next-token;
        next B;
      } elsif ($token->{tag_name} eq 'a') {
        AFE: for my $i (reverse 0..$#$active_formatting_elements) {
          my $node = $active_formatting_elements->[$i];
          if ($node->[1] == A_EL) {
            !!!cp ('t371');
            !!!parse-error (type => 'in a:a', token => $token);
            
            !!!back-token; # <a>
            $token = {type => END_TAG_TOKEN, tag_name => 'a',
                      line => $token->{line}, column => $token->{column}};
            $formatting_end_tag->($token);
            
            AFE2: for (reverse 0..$#$active_formatting_elements) {
              if ($active_formatting_elements->[$_]->[0] eq $node->[0]) {
                !!!cp ('t372');
                splice @$active_formatting_elements, $_, 1;
                last AFE2;
              }
            } # AFE2
            OE: for (reverse 0..$#{$self->{open_elements}}) {
              if ($self->{open_elements}->[$_]->[0] eq $node->[0]) {
                !!!cp ('t373');
                splice @{$self->{open_elements}}, $_, 1;
                last OE;
              }
            } # OE
            last AFE;
          } elsif ($node->[0] eq '#marker') {
            !!!cp ('t374');
            last AFE;
          }
        } # AFE
          
        $reconstruct_active_formatting_elements->($insert_to_current);

        !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
        push @$active_formatting_elements, $self->{open_elements}->[-1];

        !!!nack ('t374.1');
        !!!next-token;
        next B;
      } elsif ($token->{tag_name} eq 'nobr') {
        $reconstruct_active_formatting_elements->($insert_to_current);

        ## has a |nobr| element in scope
        INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
          my $node = $self->{open_elements}->[$_];
          if ($node->[1] == NOBR_EL) {
            !!!cp ('t376');
            !!!parse-error (type => 'in nobr:nobr', token => $token);
            !!!back-token; # <nobr>
            $token = {type => END_TAG_TOKEN, tag_name => 'nobr',
                      line => $token->{line}, column => $token->{column}};
            next B;
          } elsif ($node->[1] & SCOPING_EL) {
            !!!cp ('t377');
            last INSCOPE;
          }
        } # INSCOPE
        
        !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
        push @$active_formatting_elements, $self->{open_elements}->[-1];
        
        !!!nack ('t377.1');
        !!!next-token;
        next B;
      } 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] == BUTTON_EL) {
            !!!cp ('t378');
            !!!parse-error (type => 'in button:button', token => $token);
            !!!back-token; # <button>
            $token = {type => END_TAG_TOKEN, tag_name => 'button',
                      line => $token->{line}, column => $token->{column}};
            next B;
          } elsif ($node->[1] & SCOPING_EL) {
            !!!cp ('t379');
            last INSCOPE;
          }
        } # INSCOPE
          
        $reconstruct_active_formatting_elements->($insert_to_current);
          
        !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);

        ## TODO: associate with $self->{form_element} if defined

        push @$active_formatting_elements, ['#marker', ''];

        !!!nack ('t379.1');
        !!!next-token;
        next B;
      } elsif ({
                xmp => 1,
                iframe => 1,
                noembed => 1,
                noframes => 1, ## NOTE: This is an "as if in head" code clone.
                noscript => 0, ## TODO: 1 if scripting is enabled
               }->{$token->{tag_name}}) {
        if ($token->{tag_name} eq 'xmp') {
          !!!cp ('t381');
          $reconstruct_active_formatting_elements->($insert_to_current);
        } else {
          !!!cp ('t399');
        }
        ## NOTE: There is an "as if in body" code clone.
        $parse_rcdata->(CDATA_CONTENT_MODEL);
        next B;
      } elsif ($token->{tag_name} eq 'isindex') {
        !!!parse-error (type => 'isindex', token => $token);
        
        if (defined $self->{form_element}) {
          !!!cp ('t389');
          ## Ignore the token
          !!!nack ('t389'); ## NOTE: Not acknowledged.
          !!!next-token;
          next B;
        } else {
          !!!ack ('t391.1');

          my $at = $token->{attributes};
          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_TOKEN, tag_name => 'form',
                         attributes => $form_attrs,
                         line => $token->{line}, column => $token->{column}},
                        {type => START_TAG_TOKEN, tag_name => 'hr',
                         line => $token->{line}, column => $token->{column}},
                        {type => START_TAG_TOKEN, tag_name => 'label',
                         line => $token->{line}, column => $token->{column}},
                       );
          if ($prompt_attr) {
            !!!cp ('t390');
            push @tokens, {type => CHARACTER_TOKEN, data => $prompt_attr->{value},
                           #line => $token->{line}, column => $token->{column},
                          };
          } else {
            !!!cp ('t391');
            push @tokens, {type => CHARACTER_TOKEN,
                           data => 'This is a searchable index. Insert your search keywords here: ',
                           #line => $token->{line}, column => $token->{column},
                          }; # SHOULD
            ## TODO: make this configurable
          }
          push @tokens,
                        {type => START_TAG_TOKEN, tag_name => 'input', attributes => $at,
                         line => $token->{line}, column => $token->{column}},
                        #{type => CHARACTER_TOKEN, data => ''}, # SHOULD
                        {type => END_TAG_TOKEN, tag_name => 'label',
                         line => $token->{line}, column => $token->{column}},
                        {type => START_TAG_TOKEN, tag_name => 'hr',
                         line => $token->{line}, column => $token->{column}},
                        {type => END_TAG_TOKEN, tag_name => 'form',
                         line => $token->{line}, column => $token->{column}};
          !!!back-token (@tokens);
          !!!next-token;
          next B;
        }
      } elsif ($token->{tag_name} eq 'textarea') {
        ## 1. Insert
        !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
        
        ## Step 2 # XXX
        ## TODO: $self->{form_element} if defined

        ## 2. Drop U+000A LINE FEED
        $self->{ignore_newline} = 1;

        ## 3. RCDATA
        $self->{content_model} = RCDATA_CONTENT_MODEL;
        delete $self->{escape}; # MUST

        ## 4., 6. Insertion mode
        $self->{insertion_mode} |= IN_CDATA_RCDATA_IM;

        ## XXX: 5. frameset-ok flag

        !!!nack ('t392.1');
        !!!next-token;
        next B;
      } elsif ($token->{tag_name} eq 'optgroup' or
               $token->{tag_name} eq 'option') {
        ## has an |option| element in scope
        INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
          my $node = $self->{open_elements}->[$_];
          if ($node->[1] == OPTION_EL) {
            !!!cp ('t397.1');
            ## NOTE: As if </option>
            !!!back-token; # <option> or <optgroup>
            $token = {type => END_TAG_TOKEN, tag_name => 'option',
                      line => $token->{line}, column => $token->{column}};
            next B;
          } elsif ($node->[1] & SCOPING_EL) {
            !!!cp ('t397.2');
            last INSCOPE;
          }
        } # INSCOPE

        $reconstruct_active_formatting_elements->($insert_to_current);

        !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);

        !!!nack ('t397.3');
        !!!next-token;
        redo B;
      } elsif ($token->{tag_name} eq 'rt' or
               $token->{tag_name} eq 'rp') {
        ## has a |ruby| element in scope
        INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
          my $node = $self->{open_elements}->[$_];
          if ($node->[1] == RUBY_EL) {
            !!!cp ('t398.1');
            ## generate implied end tags
            while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
              !!!cp ('t398.2');
              pop @{$self->{open_elements}};
            }
            unless ($self->{open_elements}->[-1]->[1] == RUBY_EL) {
              !!!cp ('t398.3');
              !!!parse-error (type => 'not closed',
                              text => $self->{open_elements}->[-1]->[0]
                                  ->manakai_local_name,
                              token => $token);
              pop @{$self->{open_elements}}
                  while not $self->{open_elements}->[-1]->[1] == RUBY_EL;
            }
            last INSCOPE;
          } elsif ($node->[1] & SCOPING_EL) {
            !!!cp ('t398.4');
            last INSCOPE;
          }
        } # INSCOPE
          
        ## TODO: <non-ruby><rt> is not allowed.

        !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);

        !!!nack ('t398.5');
        !!!next-token;
        redo B;
      } elsif ($token->{tag_name} eq 'math' or
               $token->{tag_name} eq 'svg') {
        $reconstruct_active_formatting_elements->($insert_to_current);

        ## "Adjust MathML attributes" ('math' only) - done in insert-element-f

        ## "adjust SVG attributes" ('svg' only) - done in insert-element-f

        ## "adjust foreign attributes" - done in insert-element-f
        
        !!!insert-element-f ($token->{tag_name} eq 'math' ? $MML_NS : $SVG_NS, $token->{tag_name}, $token->{attributes}, $token);
        
        if ($self->{self_closing}) {
          pop @{$self->{open_elements}};
          !!!ack ('t398.6');
        } else {
          !!!cp ('t398.7');
          $self->{insertion_mode} |= IN_FOREIGN_CONTENT_IM;
          ## NOTE: |<body><math><mi><svg>| -> "in foreign content" insertion
          ## mode, "in body" (not "in foreign content") secondary insertion
          ## mode, maybe.
        }

        !!!next-token;
        next B;
      } elsif ({
                caption => 1, col => 1, colgroup => 1, frame => 1,
                frameset => 1, head => 1,
                tbody => 1, td => 1, tfoot => 1, th => 1,
                thead => 1, tr => 1,
               }->{$token->{tag_name}}) {
        !!!cp ('t401');
        !!!parse-error (type => 'in body',
                        text => $token->{tag_name}, token => $token);
        ## Ignore the token
        !!!nack ('t401.1'); ## NOTE: |<col/>| or |<frame/>| here is an error.
        !!!next-token;
        next B;
      } elsif ($token->{tag_name} eq 'param' or
               $token->{tag_name} eq 'source') {
        !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);
        pop @{$self->{open_elements}};

        !!!ack ('t398.5');
        !!!next-token;
        redo B;
      } else {
        if ($token->{tag_name} eq 'image') {
          !!!cp ('t384');
          !!!parse-error (type => 'image', token => $token);
          $token->{tag_name} = 'img';
        } else {
          !!!cp ('t385');
        }

        ## NOTE: There is an "as if <br>" code clone.
        $reconstruct_active_formatting_elements->($insert_to_current);
        
        !!!insert-element-t ($token->{tag_name}, $token->{attributes}, $token);

        if ({
             applet => 1, marquee => 1, object => 1,
            }->{$token->{tag_name}}) {
          !!!cp ('t380');
          push @$active_formatting_elements, ['#marker', ''];
          !!!nack ('t380.1');
        } elsif ({
                  b => 1, big => 1, em => 1, font => 1, i => 1,
                  s => 1, small => 1, strike => 1,
                  strong => 1, tt => 1, u => 1,
                 }->{$token->{tag_name}}) {
          !!!cp ('t375');
          push @$active_formatting_elements, $self->{open_elements}->[-1];
          !!!nack ('t375.1');
        } elsif ($token->{tag_name} eq 'input') {
          !!!cp ('t388');
          ## TODO: associate with $self->{form_element} if defined
          pop @{$self->{open_elements}};
          !!!ack ('t388.2');
        } elsif ({
                  area => 1, basefont => 1, bgsound => 1, br => 1,
                  embed => 1, img => 1, spacer => 1, wbr => 1,
                  keygen => 1,
                 }->{$token->{tag_name}}) {
          !!!cp ('t388.1');
          pop @{$self->{open_elements}};
          !!!ack ('t388.3');
        } elsif ($token->{tag_name} eq 'select') {
          ## TODO: associate with $self->{form_element} if defined
        
          if ($self->{insertion_mode} & TABLE_IMS or
              $self->{insertion_mode} & BODY_TABLE_IMS or
              ($self->{insertion_mode} & IM_MASK) == IN_COLUMN_GROUP_IM) {
            !!!cp ('t400.1');
            $self->{insertion_mode} = IN_SELECT_IN_TABLE_IM;
          } else {
            !!!cp ('t400.2');
            $self->{insertion_mode} = IN_SELECT_IM;
          }
          !!!nack ('t400.3');
        } else {
          !!!nack ('t402');
        }
        
        !!!next-token;
        next B;
      }
    } elsif ($token->{type} == END_TAG_TOKEN) {
      if ($token->{tag_name} eq 'body' or $token->{tag_name} eq 'html') {

        ## 1. If not "have an element in scope":
        ## "has a |body| element in scope"
        my $i;
        INSCOPE: {
          for (reverse @{$self->{open_elements}}) {
            if ($_->[1] == BODY_EL) {
              !!!cp ('t405');
              $i = $_;
              last INSCOPE;
            } elsif ($_->[1] & SCOPING_EL) {
              !!!cp ('t405.1');
              last;
            }
          }

          ## NOTE: |<marquee></body>|, |<svg><foreignobject></body>|,
          ## and fragment cases.

          !!!parse-error (type => 'unmatched end tag',
                          text => $token->{tag_name}, token => $token);
          ## Ignore the token.  (</body> or </html>)
          !!!next-token;
          next B;
        } # INSCOPE

        ## 2. If unclosed elements:
        for (@{$self->{open_elements}}) {
          unless ($_->[1] & ALL_END_TAG_OPTIONAL_EL ||
                  $_->[1] == OPTGROUP_EL ||
                  $_->[1] == OPTION_EL ||
                  $_->[1] == RUBY_COMPONENT_EL) {
            !!!cp ('t403');
            !!!parse-error (type => 'not closed',
                            text => $_->[0]->manakai_local_name,
                            token => $token);
            last;
          } else {
            !!!cp ('t404');
          }
        }

        ## 3. Switch the insertion mode.
        $self->{insertion_mode} = AFTER_BODY_IM;
        if ($token->{tag_name} eq 'body') {
          !!!next-token;
        } else { # html
          ## Reprocess.
        }
        next B;
      } elsif ({
                ## NOTE: End tags for non-phrasing flow content elements

                ## NOTE: The normal ones
                address => 1, article => 1, aside => 1, blockquote => 1,
                center => 1, datagrid => 1, details => 1, dialog => 1,
                dir => 1, div => 1, dl => 1, fieldset => 1, figure => 1,
                footer => 1, header => 1, hgroup => 1,
                listing => 1, menu => 1, nav => 1,
                ol => 1, pre => 1, section => 1, ul => 1,

                ## NOTE: As normal, but ... optional tags
                dd => 1, dt => 1, li => 1,

                applet => 1, button => 1, marquee => 1, object => 1,
               }->{$token->{tag_name}}) {
        ## NOTE: Code for <li> start tags includes "as if </li>" code.
        ## Code for <dt> or <dd> start tags includes "as if </dt> or
        ## </dd>" code.

        ## has an element in scope
        my $i;
        INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
          my $node = $self->{open_elements}->[$_];
          if ($node->[0]->manakai_local_name eq $token->{tag_name}) {
            !!!cp ('t410');
            $i = $_;
            last INSCOPE;
          } elsif ($node->[1] & SCOPING_EL) {
            !!!cp ('t411');
            last INSCOPE;
          }
        } # INSCOPE

        unless (defined $i) { # has an element in scope
          !!!cp ('t413');
          !!!parse-error (type => 'unmatched end tag',
                          text => $token->{tag_name}, token => $token);
          ## NOTE: Ignore the token.
        } else {
          ## Step 1. generate implied end tags
          while ({
                  ## END_TAG_OPTIONAL_EL
                  dd => ($token->{tag_name} ne 'dd'),
                  dt => ($token->{tag_name} ne 'dt'),
                  li => ($token->{tag_name} ne 'li'),
                  option => 1,
                  optgroup => 1,
                  p => 1,
                  rt => 1,
                  rp => 1,
                 }->{$self->{open_elements}->[-1]->[0]->manakai_local_name}) {
            !!!cp ('t409');
            pop @{$self->{open_elements}};
          }

          ## Step 2.
          if ($self->{open_elements}->[-1]->[0]->manakai_local_name
                  ne $token->{tag_name}) {
            !!!cp ('t412');
            !!!parse-error (type => 'not closed',
                            text => $self->{open_elements}->[-1]->[0]
                                ->manakai_local_name,
                            token => $token);
          } else {
            !!!cp ('t414');
          }

          ## Step 3.
          splice @{$self->{open_elements}}, $i;

          ## Step 4.
          $clear_up_to_marker->()
              if {
                applet => 1, button => 1, marquee => 1, object => 1,
              }->{$token->{tag_name}};
        }
        !!!next-token;
        next B;
      } elsif ($token->{tag_name} eq 'form') {
        ## NOTE: As normal, but interacts with the form element pointer

        undef $self->{form_element};

        ## has an element in scope
        my $i;
        INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
          my $node = $self->{open_elements}->[$_];
          if ($node->[1] == FORM_EL) {
            !!!cp ('t418');
            $i = $_;
            last INSCOPE;
          } elsif ($node->[1] & SCOPING_EL) {
            !!!cp ('t419');
            last INSCOPE;
          }
        } # INSCOPE

        unless (defined $i) { # has an element in scope
          !!!cp ('t421');
          !!!parse-error (type => 'unmatched end tag',
                          text => $token->{tag_name}, token => $token);
          ## NOTE: Ignore the token.
        } else {
          ## Step 1. generate implied end tags
          while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
            !!!cp ('t417');
            pop @{$self->{open_elements}};
          }
          
          ## Step 2. 
          if ($self->{open_elements}->[-1]->[0]->manakai_local_name
                  ne $token->{tag_name}) {
            !!!cp ('t417.1');
            !!!parse-error (type => 'not closed',
                            text => $self->{open_elements}->[-1]->[0]
                                ->manakai_local_name,
                            token => $token);
          } else {
            !!!cp ('t420');
          }  
          
          ## Step 3.
          splice @{$self->{open_elements}}, $i;
        }

        !!!next-token;
        next B;
      } elsif ({
                ## NOTE: As normal, except acts as a closer for any ...
                h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1,
               }->{$token->{tag_name}}) {
        ## has an element in scope
        my $i;
        INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
          my $node = $self->{open_elements}->[$_];
          if ($node->[1] == HEADING_EL) {
            !!!cp ('t423');
            $i = $_;
            last INSCOPE;
          } elsif ($node->[1] & SCOPING_EL) {
            !!!cp ('t424');
            last INSCOPE;
          }
        } # INSCOPE

        unless (defined $i) { # has an element in scope
          !!!cp ('t425.1');
          !!!parse-error (type => 'unmatched end tag',
                          text => $token->{tag_name}, token => $token);
          ## NOTE: Ignore the token.
        } else {
          ## Step 1. generate implied end tags
          while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
            !!!cp ('t422');
            pop @{$self->{open_elements}};
          }
          
          ## Step 2.
          if ($self->{open_elements}->[-1]->[0]->manakai_local_name
                  ne $token->{tag_name}) {
            !!!cp ('t425');
            !!!parse-error (type => 'unmatched end tag',
                            text => $token->{tag_name}, token => $token);
          } else {
            !!!cp ('t426');
          }

          ## Step 3.
          splice @{$self->{open_elements}}, $i;
        }
        
        !!!next-token;
        next B;
      } elsif ($token->{tag_name} eq 'p') {
        ## NOTE: As normal, except </p> implies <p> and ...

        ## has an element in scope
        my $non_optional;
        my $i;
        INSCOPE: for (reverse 0..$#{$self->{open_elements}}) {
          my $node = $self->{open_elements}->[$_];
          if ($node->[1] == P_EL) {
            !!!cp ('t410.1');
            $i = $_;
            last INSCOPE;
          } elsif ($node->[1] & SCOPING_EL) {
            !!!cp ('t411.1');
            last INSCOPE;
          } elsif ($node->[1] & END_TAG_OPTIONAL_EL) {
            ## NOTE: |END_TAG_OPTIONAL_EL| includes "p"
            !!!cp ('t411.2');
            #
          } else {
            !!!cp ('t411.3');
            $non_optional ||= $node;
            #
          }
        } # INSCOPE

        if (defined $i) {
          ## 1. Generate implied end tags
          #

          ## 2. If current node != "p", parse error
          if ($non_optional) {
            !!!cp ('t412.1');
            !!!parse-error (type => 'not closed',
                            text => $non_optional->[0]->manakai_local_name,
                            token => $token);
          } else {
            !!!cp ('t414.1');
          }

          ## 3. Pop
          splice @{$self->{open_elements}}, $i;
        } else {
          !!!cp ('t413.1');
          !!!parse-error (type => 'unmatched end tag',
                          text => $token->{tag_name}, token => $token);

          !!!cp ('t415.1');
          ## As if <p>, then reprocess the current token
          my $el;
          !!!create-element ($el, $HTML_NS, 'p',, $token);
          $insert->($el);
          ## NOTE: Not inserted into |$self->{open_elements}|.
        }

        !!!next-token;
        next B;
      } elsif ({
                a => 1,
                b => 1, big => 1, em => 1, font => 1, i => 1,
                nobr => 1, s => 1, small => 1, strike => 1,
                strong => 1, tt => 1, u => 1,
               }->{$token->{tag_name}}) {
        !!!cp ('t427');
        $formatting_end_tag->($token);
        next B;
      } elsif ($token->{tag_name} eq 'br') {
        !!!cp ('t428');
        !!!parse-error (type => 'unmatched end tag',
                        text => 'br', token => $token);

        ## As if <br>
        $reconstruct_active_formatting_elements->($insert_to_current);
        
        my $el;
        !!!create-element ($el, $HTML_NS, 'br',, $token);
        $insert->($el);
        
        ## Ignore the token.
        !!!next-token;
        next B;
      } else {
        if ($token->{tag_name} eq 'sarcasm') {
          sleep 0.001; # take a deep breath
        }

        ## Step 1
        my $node_i = -1;
        my $node = $self->{open_elements}->[$node_i];

        ## Step 2
        S2: {
          my $node_tag_name = $node->[0]->manakai_local_name;
          $node_tag_name =~ tr/A-Z/a-z/; # for SVG camelCase tag names
          if ($node_tag_name eq $token->{tag_name}) {
            ## Step 1
            ## generate implied end tags
            while ($self->{open_elements}->[-1]->[1] & END_TAG_OPTIONAL_EL) {
              !!!cp ('t430');
              ## NOTE: |<ruby><rt></ruby>|.
              ## ISSUE: <ruby><rt></rt> will also take this code path,
              ## which seems wrong.
              pop @{$self->{open_elements}};
              $node_i++;
            }
        
            ## Step 2
            my $current_tag_name
                = $self->{open_elements}->[-1]->[0]->manakai_local_name;
            $current_tag_name =~ tr/A-Z/a-z/;
            if ($current_tag_name ne $token->{tag_name}) {
              !!!cp ('t431');
              ## NOTE: <x><y></x>
              !!!parse-error (type => 'not closed',
                              text => $self->{open_elements}->[-1]->[0]
                                  ->manakai_local_name,
                              token => $token);
            } else {
              !!!cp ('t432');
            }
            
            ## Step 3
            splice @{$self->{open_elements}}, $node_i if $node_i < 0;

            !!!next-token;
            last S2;
          } else {
            ## Step 3
            if (not ($node->[1] & FORMATTING_EL) and
                #not $phrasing_category->{$node->[1]} and
                ($node->[1] & SPECIAL_EL or
                 $node->[1] & SCOPING_EL)) {
              !!!cp ('t433');
              !!!parse-error (type => 'unmatched end tag',
                              text => $token->{tag_name}, token => $token);
              ## Ignore the token
              !!!next-token;
              last S2;

              ## NOTE: |<span><dd></span>a|: In Safari 3.1.2 and Opera
              ## 9.27, "a" is a child of <dd> (conforming).  In
              ## Firefox 3.0.2, "a" is a child of <body>.  In WinIE 7,
              ## "a" is a child of both <body> and <dd>.
            }
            
            !!!cp ('t434');
          }
          
          ## Step 4
          $node_i--;
          $node = $self->{open_elements}->[$node_i];
          
          ## Step 5;
          redo S2;
        } # S2
	next B;
      }
    }
    next B;
  } continue { # B
    if ($self->{insertion_mode} & IN_FOREIGN_CONTENT_IM) {
      ## NOTE: The code below is executed in cases where it does not have
      ## to be, but it it is harmless even in those cases.
      ## has an element in scope
      INSCOPE: {
        for (reverse 0..$#{$self->{open_elements}}) {
          my $node = $self->{open_elements}->[$_];
          if ($node->[1] & FOREIGN_EL) {
            last INSCOPE;
          } elsif ($node->[1] & SCOPING_EL) {
            last;
          }
        }
        
        ## NOTE: No foreign element in scope.
        $self->{insertion_mode} &= ~ IN_FOREIGN_CONTENT_IM;
      } # INSCOPE
    }
  } # B

  ## Stop parsing # MUST
  
  ## TODO: script stuffs
} # _tree_construct_main

## XXX: How this method is organized is somewhat out of date, although
## it still does what the current spec documents.
sub set_inner_html ($$$$;$) {
  my $class = shift;
  my $node = shift; # /context/
  #my $s = \$_[0];
  my $onerror = $_[1];
  my $get_wrapper = $_[2] || sub ($) { return $_[0] };

  my $nt = $node->node_type;
  if ($nt == 9) { # Document (invoke the algorithm with no /context/ element)
    # MUST
    
    ## Step 1 # MUST
    ## TODO: If the document has an active parser, ...
    ## ISSUE: There is an issue in the spec.
    
    ## Step 2 # MUST
    my @cn = @{$node->child_nodes};
    for (@cn) {
      $node->remove_child ($_);
    }

    ## Step 3, 4, 5 # MUST
    $class->parse_char_string ($_[0] => $node, $onerror, $get_wrapper);
  } elsif ($nt == 1) { # Element (invoke the algorithm with /context/ element)
    ## TODO: If non-html element

    ## NOTE: Most of this code is copied from |parse_string|

## TODO: Support for $get_wrapper

    ## F1. Create an HTML document.
    my $this_doc = $node->owner_document;
    my $doc = $this_doc->implementation->create_document;
    $doc->manakai_is_html (1);

    ## F2. Propagate quirkness flag
    my $node_doc = $node->owner_document;
    $doc->manakai_compat_mode ($node_doc->manakai_compat_mode);

    ## F3. Create an HTML parser
    my $p = $class->new;
    $p->{document} = $doc;

    ## Step 8 # MUST
    my $i = 0;
    $p->{line_prev} = $p->{line} = 1;
    $p->{column_prev} = $p->{column} = 0;
    require Whatpm::Charset::DecodeHandle;
    my $input = Whatpm::Charset::DecodeHandle::CharString->new (\($_[0]));
    $input = $get_wrapper->($input);
    $p->{set_nc} = sub {
      my $self = shift;

      my $char = '';
      if (defined $self->{next_nc}) {
        $char = $self->{next_nc};
        delete $self->{next_nc};
        $self->{nc} = ord $char;
      } else {
        $self->{char_buffer} = '';
        $self->{char_buffer_pos} = 0;
        
        my $count = $input->manakai_read_until
            ($self->{char_buffer}, qr/[^\x00\x0A\x0D]/,
             $self->{char_buffer_pos});
        if ($count) {
          $self->{line_prev} = $self->{line};
          $self->{column_prev} = $self->{column};
          $self->{column}++;
          $self->{nc}
              = ord substr ($self->{char_buffer},
                            $self->{char_buffer_pos}++, 1);
          return;
        }
        
        if ($input->read ($char, 1)) {
          $self->{nc} = ord $char;
        } else {
          $self->{nc} = -1;
          return;
        }
      }

      ($p->{line_prev}, $p->{column_prev}) = ($p->{line}, $p->{column});
      $p->{column}++;

      if ($self->{nc} == 0x000A) { # LF
        $p->{line}++;
        $p->{column} = 0;
        !!!cp ('i1');
      } elsif ($self->{nc} == 0x000D) { # CR
## TODO: support for abort/streaming
        my $next = '';
        if ($input->read ($next, 1) and $next ne "\x0A") {
          $self->{next_nc} = $next;
        }
        $self->{nc} = 0x000A; # LF # MUST
        $p->{line}++;
        $p->{column} = 0;
        !!!cp ('i2');
      } elsif ($self->{nc} == 0x0000) { # NULL
        !!!cp ('i4');
        !!!parse-error (type => 'NULL');
        $self->{nc} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
      }
    };

    $p->{read_until} = sub {
      #my ($scalar, $specials_range, $offset) = @_;
      return 0 if defined $p->{next_nc};

      my $pattern = qr/[^$_[1]\x00\x0A\x0D]/;
      my $offset = $_[2] || 0;
      
      if ($p->{char_buffer_pos} < length $p->{char_buffer}) {
        pos ($p->{char_buffer}) = $p->{char_buffer_pos};
        if ($p->{char_buffer} =~ /\G(?>$pattern)+/) {
          substr ($_[0], $offset)
              = substr ($p->{char_buffer}, $-[0], $+[0] - $-[0]);
          my $count = $+[0] - $-[0];
          if ($count) {
            $p->{column} += $count;
            $p->{char_buffer_pos} += $count;
            $p->{line_prev} = $p->{line};
            $p->{column_prev} = $p->{column} - 1;
            $p->{nc} = -1;
          }
          return $count;
        } else {
          return 0;
        }
      } else {
        my $count = $input->manakai_read_until ($_[0], $pattern, $_[2]);
        if ($count) {
          $p->{column} += $count;
          $p->{column_prev} += $count;
          $p->{nc} = -1;
        }
        return $count;
      }
    }; # $p->{read_until}

    my $ponerror = $onerror || sub {
      my (%opt) = @_;
      my $line = $opt{line};
      my $column = $opt{column};
      if (defined $opt{token} and defined $opt{token}->{line}) {
        $line = $opt{token}->{line};
        $column = $opt{token}->{column};
      }
      warn "Parse error ($opt{type}) at line $line column $column\n";
    };
    $p->{parse_error} = sub {
      $ponerror->(line => $p->{line}, column => $p->{column}, @_);
    };
    
    my $char_onerror = sub {
      my (undef, $type, %opt) = @_;
      $ponerror->(layer => 'encode',
                  line => $p->{line}, column => $p->{column} + 1,
                  %opt, type => $type);
    }; # $char_onerror
    $input->onerror ($char_onerror);

    $p->_initialize_tokenizer;
    $p->_initialize_tree_constructor;

    ## F4. If /context/ is not undef...

    ## F4.1. content model flag
    my $node_ln = $node->manakai_local_name;
    $p->{content_model} = {
      title => RCDATA_CONTENT_MODEL,
      textarea => RCDATA_CONTENT_MODEL,
      style => CDATA_CONTENT_MODEL,
      script => CDATA_CONTENT_MODEL,
      xmp => CDATA_CONTENT_MODEL,
      iframe => CDATA_CONTENT_MODEL,
      noembed => CDATA_CONTENT_MODEL,
      noframes => CDATA_CONTENT_MODEL,
      noscript => CDATA_CONTENT_MODEL,
      plaintext => PLAINTEXT_CONTENT_MODEL,
    }->{$node_ln};
    $p->{content_model} = PCDATA_CONTENT_MODEL
        unless defined $p->{content_model};

    $p->{inner_html_node} = [$node, $el_category->{$node_ln}];
      ## TODO: Foreign element OK?

    ## F4.2. Root |html| element
    my $root = $doc->create_element_ns
      ('http://www.w3.org/1999/xhtml', [undef, 'html']);

    ## F4.3.
    $doc->append_child ($root);

    ## F4.4.
    push @{$p->{open_elements}}, [$root, $el_category->{html}];

    undef $p->{head_element};
    undef $p->{head_element_inserted};

    ## F4.5.
    $p->_reset_insertion_mode;

    ## F4.6.
    my $anode = $node;
    AN: while (defined $anode) {
      if ($anode->node_type == 1) {
        my $nsuri = $anode->namespace_uri;
        if (defined $nsuri and $nsuri eq 'http://www.w3.org/1999/xhtml') {
          if ($anode->manakai_local_name eq 'form') {
            !!!cp ('i5');
            $p->{form_element} = $anode;
            last AN;
          }
        }
      }
      $anode = $anode->parent_node;
    } # AN

    ## F.5. Set the input stream.
    $p->{confident} = 1; ## Confident: irrelevant.

    ## F.6. Start the parser.
    {
      my $self = $p;
      !!!next-token;
    }
    $p->_tree_construction_main;

    ## F.7.
    my @cn = @{$node->child_nodes};
    for (@cn) {
      $node->remove_child ($_);
    }
    ## ISSUE: mutation events? read-only?

    ## Step 11 # MUST
    @cn = @{$root->child_nodes};
    for (@cn) {
      $this_doc->adopt_node ($_);
      $node->append_child ($_);
    }
    ## ISSUE: mutation events?

    $p->_terminate_tree_constructor;

    delete $p->{parse_error}; # delete loop
  } else {
    die "$0: |set_inner_html| is not defined for node of type $nt";
  }
} # set_inner_html

} # tree construction stage

package Whatpm::HTML::RestartParser;
push our @ISA, 'Error';

1;
# $Date: 2009/09/06 12:53:19 $
