class Gammo::Parser::InBody

Section 12.2.6.4.6.

Public Instance Methods

adoption_agency_for_end_tag_formatting(tag, tagname) click to toggle source

Implements “adoption agency” algorithm. html.spec.whatwg.org/multipage/syntax.html#adoptionAgency @!visibility private

# File lib/gammo/parser/insertion_mode/in_body.rb, line 316
def adoption_agency_for_end_tag_formatting(tag, tagname)
  # Step 1-2.
  current = parser.open_elements.last
  if current.data == tagname && parser.active_formatting_elements.index(current) == -1
    parser.open_elements.pop
    return
  end

  # Step 3-5. The outer loop
  8.times do |n|
    # Step 6: Find the formatting element.
    formatting_element = nil
    parser.active_formatting_elements.reverse_each do |afe|
      break if afe.instance_of? Node::ScopeMarker
      if afe.tag == tag
        formatting_element = afe
        break
      end
    end
    unless formatting_element
      adoption_agency_for_end_tag_other(tag, tagname)
      return
    end
    # Step 7. Ignore the tag if formatting element is not in the stack of
    # open elements.
    index = parser.open_elements.index(formatting_element)
    unless index
      parser.active_formatting_elements.delete(formatting_element)
      return
    end
    # Step 8. Ignore the tag if formatting element is not in the scope.
    return unless parser.element_in_scope?(DEFAULT_SCOPE, tag)

    # Step 9. This step is omitted because it's just a parse error but no
    # need to return.

    # Step 10-11. Find the furthest block.
    furthest_block = parser.open_elements.slice(index..-1).find(&parser.method(:special_element?))
    unless furthest_block
      element = parser.open_elements.pop
      element = parser.open_elements.pop while element != formatting_element
      parser.active_formatting_elements.delete(element)
      return
    end

    # Step 12-13. Find the common ancestor and bookmark node.
    common_ancestor = parser.open_elements[index - 1]
    bookmark = parser.active_formatting_elements.index(formatting_element)

    # Step 14. The inner loop. find the last node to reparent.
    last_node = furthest_block
    node = furthest_block
    x = parser.open_elements.index(node)
    # Step 14.1.
    j = 0
    loop do
      # Step 14.2.
      j += 1
      # Step 14.3.
      x -= 1
      node = parser.open_elements[x]
      # Step 14.4.
      break if node == formatting_element

      # Step 14.5. Remove node from the list of active formatting elements if
      # inner loop counter is greater than three and node is in the list of
      # active formatting elements.
      ni = parser.active_formatting_elements.index(node)
      if ni && j > 3
        parser.active_formatting_elements.delete(node)
        # If any element of the list of active formatting elements is removed,
        # we need to take care whether bookmark should be decremented or not.
        # This is because the value of bookmark may exceed the size of the
        # list by removing elements from the list.
        bookmark -= 1 if ni <= bookmark
        next
      end
      # Step 14.6. Continue the next inner loop if node is not in the list of
      # active formatting elements.
      unless parser.active_formatting_elements.include?(node)
        parser.open_elements.delete(node)
        next
      end
      # Step 14.7
      clone = node.clone
      afei = parser.active_formatting_elements.index(node)
      oei = parser.open_elements.index(node)
      raise ParseError, 'bad parser state: expected elements are not found' if !(afei && oei)
      parser.active_formatting_elements[afei] = clone
      parser.open_elements[oei] = clone
      node = clone
      # Step 14.8
      bookmark = (parser.active_formatting_elements.index(node) + 1) || 0 if last_node == furthest_block
      # Step 14.9
      last_node.parent.remove_child(last_node) if last_node.parent
      node.append_child(last_node)
      # Step 14.10
      last_node = node
    end
    # Step 15. Reparent last_node to the common ancestor,
    # or for misnested table nodes, to the foster parent.
    last_node.parent.remove_child(last_node) if last_node.parent
    case common_ancestor.tag
    when Tags::Table, Tags::Tbody, Tags::Tfoot, Tags::Thead, Tags::Tr
      parser.foster_parent(last_node)
    else
      common_ancestor.append_child(last_node)
    end

    # Steps 16-18. Reparent nodes from the furthest block's children
    # to a clone of the formatting element.
    clone = formatting_element.clone
    reparent_children(clone, furthest_block)
    furthest_block.append_child(clone)

    # Step 19. Fix up the list of active formatting elements.
    old_loc = parser.active_formatting_elements.index(formatting_element)
    bookmark -= 1 if old_loc && old_loc < bookmark
    parser.active_formatting_elements.delete(formatting_element)
    parser.active_formatting_elements.insert(bookmark, clone)

    # Step 20. Fix up the stack of open elements.
    parser.open_elements.delete(formatting_element)
    parser.open_elements.insert(parser.open_elements.index(furthest_block) + 1, clone)
  end
end
adoption_agency_for_end_tag_other(tag, tagname) click to toggle source

@!visibility private

# File lib/gammo/parser/insertion_mode/in_body.rb, line 444
def adoption_agency_for_end_tag_other(tag, tagname)
  parser.open_elements.reverse_each_with_index do |open_element, index|
    if open_element.tag == tag && open_element.data == tagname
      parser.open_elements = parser.open_elements.slice(0, index)
      break
    end
    break if parser.special_element?(open_element)
  end
end
comment_token(token) click to toggle source
# File lib/gammo/parser/insertion_mode/in_body.rb, line 287
def comment_token(token)
  parser.add_child Node::Comment.new(data: token.data)
end
default(_) click to toggle source
# File lib/gammo/parser/insertion_mode/in_body.rb, line 309
def default(_)
  halt true
end
end_tag_token(token) click to toggle source
# File lib/gammo/parser/insertion_mode/in_body.rb, line 228
def end_tag_token(token)
  case token.tag
  when Tags::Body
    parser.insertion_mode = AfterBody if parser.element_in_scope?(DEFAULT_SCOPE, Tags::Body)
  when Tags::Html
    halt true unless parser.element_in_scope?(DEFAULT_SCOPE, Tags::Body)
    parser.parse_implied_token(Tokenizer::EndTagToken, Tags::Body, Tags::Body.to_s)
    halt false
  when Tags::Address, Tags::Article, Tags::Aside, Tags::Blockquote,
    Tags::Button, Tags::Center, Tags::Dialog, Tags::Details, Tags::Dir,
    Tags::Div, Tags::Dl, Tags::Fieldset, Tags::Figcaption, Tags::Figure,
    Tags::Footer, Tags::Header, Tags::Hgroup, Tags::Listing, Tags::Main,
    Tags::Menu, Tags::Nav, Tags::Ol, Tags::Pre, Tags::Section,
    Tags::Summary, Tags::Ul
    parser.pop_until(DEFAULT_SCOPE, token.tag)
  when Tags::Form
    if parser.open_elements.any? { |oe| oe.tag == Tags::Template }
      index = parser.index_of_element_in_scope(DEFAULT_SCOPE, Tags::Form)
      # ignore the token.
      halt true if index == -1
      parser.generate_implied_end_tags
      # ignore the token.
      halt true if parser.open_elements[index].tag != Tags::Form
      parser.pop_until(DEFAULT_SCOPE, Tags::Form)
    else
      node = parser.form
      parser.form = nil
      index = parser.index_of_element_in_scope(DEFAULT_SCOPE, Tags::Form)
      # ignore the token.
      halt true if node == nil || index == -1 || parser.open_elements[index] != node
      parser.generate_implied_end_tags
      parser.open_elements.delete(node)
    end
  when Tags::P
    parser.parse_implied_token(Tokenizer::StartTagToken, Tags::P, Tags::P.to_s) unless parser.element_in_scope?(BUTTON_SCOPE, Tags::P)
    parser.pop_until(BUTTON_SCOPE, Tags::P)
  when Tags::Li
    parser.pop_until(LIST_ITEM_SCOPE, Tags::Li)
  when Tags::Dd, Tags::Dt
    parser.pop_until(DEFAULT_SCOPE, token.tag)
  when Tags::H1, Tags::H2, Tags::H3, Tags::H4, Tags::H5, Tags::H6
    parser.pop_until(DEFAULT_SCOPE, Tags::H1, Tags::H2, Tags::H3, Tags::H4, Tags::H5, Tags::H6)
  when Tags::A, Tags::B, Tags::Big, Tags::Code, Tags::Em, Tags::Font,
    Tags::I, Tags::Nobr, Tags::S, Tags::Small, Tags::Strike,
    Tags::Strong, Tags::Tt, Tags::U
    adoption_agency_for_end_tag_formatting(token.tag, token.data)
  when Tags::Applet, Tags::Marquee, Tags::Object
    parser.clear_active_formatting_elements if parser.pop_until(DEFAULT_SCOPE, token.tag)
  when Tags::Br
    # FIXME
    parser.token = Tokenizer::StartTagToken.new(token.data, tag: token.tag)
    halt false
  when Tags::Template
    halt InHead.new(parser).process
  else
    adoption_agency_for_end_tag_formatting(token.tag, token.data)
  end
end
error_token(token) click to toggle source
# File lib/gammo/parser/insertion_mode/in_body.rb, line 291
def error_token(token)
  if parser.template_stack.length > 0
    parser.insertion_mode = InTemplate
    halt false
  else
    parser.open_elements.any? do |oe|
      case oe.tag
      when Tags::Dd, Tags::Dt, Tags::Li, Tags::Optgroup, Tags::Option, Tags::P,
        Tags::Rb, Tags::Rp, Tags::Rt, Tags::Rtc, Tags::Tbody, Tags::Td, Tags::Tfoot,
        Tags::Th, Tags::Thead, Tags::Tr, Tags::Body, Tags::Html
      else
        halt true
      end
    end
    halt true
  end
end
reparent_children(dst, src) click to toggle source

@!visibility private

# File lib/gammo/parser/insertion_mode/in_body.rb, line 455
def reparent_children(dst, src)
  while child = src.first_child
    src.remove_child(child)
    dst.append_child(child)
  end
end
start_tag_token(token) click to toggle source
# File lib/gammo/parser/insertion_mode/in_body.rb, line 23
def start_tag_token(token)
  case token.tag
  when Tags::Html
    halt true if parser.open_elements.any? { |oe| oe.tag == Tags::Template }
    copy_attributes(parser.open_elements[0], token)
  when Tags::Base, Tags::Basefont, Tags::Bgsound, Tags::Link, Tags::Meta,
    Tags::Noframes, Tags::Script, Tags::Style, Tags::Template, Tags::Title
    halt InHead.new(parser).process
  when Tags::Body
    halt true if parser.open_elements.any? { |oe| oe.tag == Tags::Template }
    if parser.open_elements.length >= 2
      body = parser.open_elements[1]
      if body.instance_of?(Node::Element) && body.tag == Tags::Body
        parser.frameset_ok = false
        copy_attributes(body, parser.token)
      end
    end
  when Tags::Frameset
    if !parser.frameset_ok || parser.open_elements.length < 2 || parser.open_elements[1].tag != Tags::Body
      # ignore the token
      halt true
    end
    body = parser.open_elements[1]
    body.parent.remove_child(body) if body.parent
    parser.open_elements = parser.open_elements.slice(0, 1)
    parser.add_element
    parser.insertion_mode = InFrameset
    halt true
  when Tags::Address, Tags::Article, Tags::Aside, Tags::Blockquote,
    Tags::Center, Tags::Dialog, Tags::Details, Tags::Dir, Tags::Div,
    Tags::Dl, Tags::Fieldset, Tags::Figcaption, Tags::Figure,
    Tags::Footer, Tags::Header, Tags::Hgroup, Tags::Main, Tags::Menu,
    Tags::Nav, Tags::Ol, Tags::P, Tags::Section, Tags::Summary, Tags::Ul
    parser.pop_until(BUTTON_SCOPE, Tags::P)
    parser.add_element
  when Tags::H1, Tags::H2, Tags::H3, Tags::H4, Tags::H5, Tags::H6
    parser.pop_until(BUTTON_SCOPE, Tags::P)
    node = parser.top
    case node.tag
    when Tags::H1, Tags::H2, Tags::H3, Tags::H4, Tags::H5, Tags::H6
      parser.open_elements.pop
    end
    parser.add_element
  when Tags::Pre, Tags::Listing
    parser.pop_until(BUTTON_SCOPE, Tags::P)
    parser.add_element
    parser.frameset_ok = false
  when Tags::Form
    # ignore the token.
    halt true if parser.form && !parser.open_elements.any? { |oe| oe.tag == Tags::Template }
    parser.pop_until(BUTTON_SCOPE, Tags::P)
    parser.add_element
    parser.form = parser.top unless parser.open_elements.any? { |oe| oe.tag == Tags::Template }
  when Tags::Li
    parser.frameset_ok = false
    parser.open_elements.reverse_each_with_index do |open_element, index|
      case open_element.tag
      when Tags::Li then parser.open_elements = parser.open_elements.slice(0, index)
      when Tags::Address, Tags::Div, Tags::P then next
      else
        next unless parser.special_element?(open_element)
      end
      break
    end
    parser.pop_until(BUTTON_SCOPE, Tags::P)
    parser.add_element
  when Tags::Dd, Tags::Dt
    parser.frameset_ok = false
    parser.open_elements.reverse_each_with_index do |open_element, index|
      case open_element.tag
      when Tags::Dd, Tags::Dt then parser.open_elements = parser.open_elements.slice(0, index)
      when Tags::Address, Tags::Div, Tags::P then next
      else
        next unless parser.special_element?(open_element)
      end
      break
    end
    parser.pop_until(BUTTON_SCOPE, Tags::P)
    parser.add_element
  when Tags::Plaintext
    parser.pop_until BUTTON_SCOPE, Tags::P
    parser.add_element
  when Tags::Button
    parser.pop_until DEFAULT_SCOPE, Tags::Button
    parser.reconstruct_active_formatting_elements
    parser.add_element
    parser.frameset_ok = false
  when Tags::A
    parser.active_formatting_elements.reverse_each do |afe|
      break if afe.is_a?(Node::ScopeMarker)
      next unless afe.instance_of?(Node::Element) && afe.tag == Tags::A
      adoption_agency_for_end_tag_formatting(Tags::A, "a")
      parser.open_elements.delete(afe)
      parser.active_formatting_elements.delete(afe)
      break
    end
    parser.reconstruct_active_formatting_elements
    parser.add_formatting_element
  when Tags::B, Tags::Big, Tags::Code, Tags::Em, Tags::Font, Tags::I,
    Tags::S, Tags::Small, Tags::Strike, Tags::Strong, Tags::Tt, Tags::U
    parser.reconstruct_active_formatting_elements
    parser.add_formatting_element
  when Tags::Nobr
    parser.reconstruct_active_formatting_elements
    if parser.element_in_scope?(DEFAULT_SCOPE, Tags::Nobr)
      adoption_agency_for_end_tag_formatting(Tags::Nobr, "nobr")
      parser.reconstruct_active_formatting_elements
    end
    parser.add_formatting_element
  when Tags::Applet, Tags::Marquee, Tags::Object
    parser.reconstruct_active_formatting_elements
    parser.add_element
    parser.active_formatting_elements << Node::DEFAULT_SCOPE_MARKER
    parser.frameset_ok = false
  when Tags::Table
    parser.pop_until(BUTTON_SCOPE, Tags::P) unless parser.quirks
    parser.add_element
    parser.frameset_ok = false
    parser.insertion_mode = InTable
    halt true
  when Tags::Area, Tags::Br, Tags::Embed, Tags::Img, Tags::Input, Tags::Keygen, Tags::Wbr
    parser.reconstruct_active_formatting_elements
    parser.add_element
    parser.open_elements.pop
    parser.acknowledge_self_closing_tag
    token.attributes.each do |attr|
      # skip setting frameset_ok = false
      halt true if attr.key == 'type' && attr.value.downcase == 'hidden'
    end if token.tag == Tags::Input
    parser.frameset_ok = false
  when Tags::Param, Tags::Source, Tags::Track
    parser.add_element
    parser.open_elements.pop
    parser.acknowledge_self_closing_tag
  when Tags::Hr
    parser.pop_until BUTTON_SCOPE, Tags::P
    parser.add_element
    parser.open_elements.pop
    parser.acknowledge_self_closing_tag
    parser.frameset_ok = false
  when Tags::Image
    token.tag = Tags::Img
    # todo: fixme <img>
    token.data = Tags::Img.to_s
    halt false
  when Tags::Textarea
    parser.add_element
    parser.set_original_insertion_mode
    parser.frameset_ok = false
    parser.insertion_mode = Text
  when Tags::Xmp
    parser.pop_until(BUTTON_SCOPE, Tags::P)
    parser.reconstruct_active_formatting_elements
    parser.frameset_ok = false
    parser.add_element
    parser.set_original_insertion_mode
    parser.insertion_mode = Text
  when Tags::Iframe
    parser.frameset_ok = false
    parser.parse_generic_raw_text_element
  when Tags::Noembed
    parser.parse_generic_raw_text_element
  when Tags::Noscript
    if parser.scripting?
      parser.parse_generic_raw_text_element
      halt true
    end
    parser.reconstruct_active_formatting_elements
    parser.add_element
    parser.tokenizer.next_is_not_raw_text!
  when Tags::Select
    parser.reconstruct_active_formatting_elements
    parser.add_element
    parser.frameset_ok = false
    parser.insertion_mode = InSelect
    halt true
  when Tags::Optgroup, Tags::Option
    parser.open_elements.pop if parser.top.tag == Tags::Option
    parser.reconstruct_active_formatting_elements
    parser.add_element
  when Tags::Rb, Tags::Rtc
    parser.generate_implied_end_tags if parser.element_in_scope?(DEFAULT_SCOPE, Tags::Ruby)
    parser.add_element
  when Tags::Rp, Tags::Rt
    parser.generate_implied_end_tags('rtc') if parser.element_in_scope?(DEFAULT_SCOPE, Tags::Ruby)
    parser.add_element
  when Tags::Math, Tags::Svg
    parser.reconstruct_active_formatting_elements
    parser.adjust_attribute_names(token.attributes, token.tag == Tags::Math ? Parser::MATH_ML_ATTRIBUTE_ADJUSTMENTS : Parser::SVG_ATTRIBUTE_ADJUSTMENTS)
    parser.adjust_foreign_attributes(token.attributes)
    parser.add_element
    parser.top.namespace = token.data
    if parser.has_self_closing_token
      parser.open_elements.pop
      parser.acknowledge_self_closing_tag
    end
    halt true
  when Tags::Caption, Tags::Col, Tags::Colgroup, Tags::Frame, Tags::Head, Tags::Tbody, Tags::Td, Tags::Tfoot, Tags::Th, Tags::Thead, Tags::Tr
    # ignore the token.
  else
    parser.reconstruct_active_formatting_elements
    parser.add_element
  end
end
text_token(token) click to toggle source
# File lib/gammo/parser/insertion_mode/in_body.rb, line 5
def text_token(token)
  data = token.data
  node = parser.open_elements.last
  case node.tag
  when Tags::Pre, Tags::Listing
    unless node.first_child
      # ignore a newline at the start of the <pre> block.
      data = data.slice(1..-1) if !data.empty? && data[0] == ?\r
      data = data.slice(1..-1) if !data.empty? && data[0] == ?\n
    end
  end
  data = data.gsub("\x00", '')
  halt true if data.empty?
  parser.reconstruct_active_formatting_elements
  parser.frameset_ok = false if parser.frameset_ok && !data.lstrip.empty?
  parser.add_text(data)
end