+ clear_to_table_row_stopers = {
+ 'tr': true
+ 'template': true
+ 'html': true
+ }
+ clear_stack_to_table_row_context = ->
+ loop
+ if clear_to_table_row_stopers[open_els[0].name]?
+ break
+ open_els.shift()
+ return
+ clear_afe_to_marker = ->
+ loop
+ return unless afe.length > 0 # this happens in fragment case, ?spec error
+ el = afe.shift()
+ if el.type is TYPE_AFE_MARKER
+ return
+ return
+
+ # 8.2.3.1 ...
+ # http://www.w3.org/TR/html5/syntax.html#reset-the-insertion-mode-appropriately
+ reset_ins_mode = ->
+ # 1. Let last be false.
+ last = false
+ # 2. Let node be the last node in the stack of open elements.
+ node_i = 0
+ node = open_els[node_i]
+ # 3. Loop: If node is the first node in the stack of open elements,
+ # then set last to true, and, if the parser was originally created as
+ # part of the HTML fragment parsing algorithm (fragment case) set node
+ # to the context element.
+ loop
+ if node_i is open_els.length - 1
+ last = true
+ # fixfull (fragment case)
+
+ # 4. If node is a select element, run these substeps:
+ if node.name is 'select' and node.namespace is NS_HTML
+ # 1. If last is true, jump to the step below labeled done.
+ unless last
+ # 2. Let ancestor be node.
+ ancestor_i = node_i
+ ancestor = node
+ # 3. Loop: If ancestor is the first node in the stack of
+ # open elements, jump to the step below labeled done.
+ loop
+ if ancestor_i is open_els.length - 1
+ break
+ # 4. Let ancestor be the node before ancestor in the stack
+ # of open elements.
+ ancestor_i += 1
+ ancestor = open_els[ancestor_i]
+ # 5. If ancestor is a template node, jump to the step below
+ # labeled done.
+ if ancestor.name is 'template' and ancestor.namespace is NS_HTML
+ break
+ # 6. If ancestor is a table node, switch the insertion mode
+ # to "in select in table" and abort these steps.
+ if ancestor.name is 'table' and ancestor.namespace is NS_HTML
+ ins_mode = ins_mode_in_select_in_table
+ return
+ # 7. Jump back to the step labeled loop.
+ # 8. Done: Switch the insertion mode to "in select" and abort
+ # these steps.
+ ins_mode = ins_mode_in_select
+ return
+ # 5. If node is a td or th element and last is false, then switch
+ # the insertion mode to "in cell" and abort these steps.
+ if (node.name is 'td' or node.name is 'th') and node.namespace is NS_HTML and last is false
+ ins_mode = ins_mode_in_cell
+ return
+ # 6. If node is a tr element, then switch the insertion mode to "in
+ # row" and abort these steps.
+ if node.name is 'tr' and node.namespace is NS_HTML
+ ins_mode = ins_mode_in_row
+ return
+ # 7. If node is a tbody, thead, or tfoot element, then switch the
+ # insertion mode to "in table body" and abort these steps.
+ if (node.name is 'tbody' or node.name is 'thead' or node.name is 'tfoot') and node.namespace is NS_HTML
+ ins_mode = ins_mode_in_table_body
+ return
+ # 8. If node is a caption element, then switch the insertion mode
+ # to "in caption" and abort these steps.
+ if node.name is 'caption' and node.namespace is NS_HTML
+ ins_mode = ins_mode_in_caption
+ return
+ # 9. If node is a colgroup element, then switch the insertion mode
+ # to "in column group" and abort these steps.
+ if node.name is 'colgroup' and node.namespace is NS_HTML
+ ins_mode = ins_mode_in_column_group
+ return
+ # 10. If node is a table element, then switch the insertion mode to
+ # "in table" and abort these steps.
+ if node.name is 'table' and node.namespace is NS_HTML
+ ins_mode = ins_mode_in_table
+ return
+ # 11. If node is a template element, then switch the insertion mode
+ # to the current template insertion mode and abort these steps.
+ if node.name is 'template' and node.namespace is NS_HTML
+ ins_mode = template_ins_modes[0]
+ return
+ # 12. If node is a head element and last is true, then switch the
+ # insertion mode to "in body" ("in body"! not "in head"!) and abort
+ # these steps. (fragment case)
+ if node.name is 'head' and node.namespace is NS_HTML and last
+ ins_mode = ins_mode_in_body
+ return
+ # 13. If node is a head element and last is false, then switch the
+ # insertion mode to "in head" and abort these steps.
+ if node.name is 'head' and node.namespace is NS_HTML and last is false
+ ins_mode = ins_mode_in_head
+ return
+ # 14. If node is a body element, then switch the insertion mode to
+ # "in body" and abort these steps.
+ if node.name is 'body' and node.namespace is NS_HTML
+ ins_mode = ins_mode_in_body
+ return
+ # 15. If node is a frameset element, then switch the insertion mode
+ # to "in frameset" and abort these steps. (fragment case)
+ if node.name is 'frameset' and node.namespace is NS_HTML
+ ins_mode = ins_mode_in_frameset
+ return
+ # 16. If node is an html element, run these substeps:
+ if node.name is 'html' and node.namespace is NS_HTML
+ # 1. If the head element pointer is null, switch the insertion
+ # mode to "before head" and abort these steps. (fragment case)
+ if head_element_pointer is null
+ ins_mode = ins_mode_before_head
+ else
+ # 2. Otherwise, the head element pointer is not null,
+ # switch the insertion mode to "after head" and abort these
+ # steps.
+ ins_mode = ins_mode_after_head
+ return
+ # 17. If last is true, then switch the insertion mode to "in body"
+ # and abort these steps. (fragment case)
+ if last
+ ins_mode = ins_mode_in_body
+ return
+ # 18. Let node now be the node before node in the stack of open
+ # elements.
+ node_i += 1
+ node = open_els[node_i]
+ # 19. Return to the step labeled loop.
+
+ # 8.2.3.2
+
+ # http://www.w3.org/TR/html5/syntax.html#adjusted-current-node
+ adjusted_current_node = ->
+ if open_els.length is 1 and flag_fragment_parsing
+ return context_element
+ return open_els[0]
+
+ # http://www.w3.org/TR/html5/syntax.html#reconstruct-the-active-formatting-elements
+ # this implementation is structured (mostly) as described at the link above.
+ # capitalized comments are the "labels" described at the link above.
+ reconstruct_afe = ->
+ return if afe.length is 0
+ if afe[0].type is TYPE_AFE_MARKER or afe[0] in open_els
+ return
+ # Rewind
+ i = 0
+ loop
+ if i is afe.length - 1
+ break
+ i += 1
+ if afe[i].type is TYPE_AFE_MARKER or afe[i] in open_els
+ i -= 1 # Advance
+ break
+ # Create
+ loop
+ el = insert_html_element afe[i].token
+ afe[i] = el
+ break if i is 0
+ i -= 1 # Advance
+
+ # http://www.w3.org/TR/html5/syntax.html#adoption-agency-algorithm
+ # adoption agency algorithm
+ # overview here:
+ # http://www.w3.org/TR/html5/syntax.html#misnested-tags:-b-i-/b-/i
+ # http://www.w3.org/TR/html5/syntax.html#misnested-tags:-b-p-/b-/p
+ # http://www.w3.org/TR/html5/syntax.html#unclosed-formatting-elements
+ adoption_agency = (subject) ->
+ debug_log "adoption_agency()"
+ debug_log "tree: #{serialize_els doc.children, false, true}"
+ debug_log "open_els: #{serialize_els open_els, true, true}"
+ debug_log "afe: #{serialize_els afe, true, true}"
+# this block implements tha W3C spec
+# # 1. If the current node is an HTML element whose tag name is subject,
+# # then run these substeps:
+# #
+# # 1. Let element be the current node.
+# #
+# # 2. Pop element off the stack of open elements.
+# #
+# # 3. If element is also in the list of active formatting elements,
+# # remove the element from the list.
+# #
+# # 4. Abort the adoption agency algorithm.
+# if open_els[0].name is subject and open_els[0].namespace is NS_HTML
+# el = open_els.shift()
+# # remove it from the list of active formatting elements (if found)
+# for t, i in afe
+# if t is el
+# afe.splice i, 1
+# break
+# debug_log "aaa: starting off with subject on top of stack, exiting"
+# return
+# WHATWG: https://html.spec.whatwg.org/multipage/syntax.html#adoption-agency-algorithm
+ # If the current node is an HTML element whose tag name is subject, and
+ # the current node is not in the list of active formatting elements,
+ # then pop the current node off the stack of open elements, and abort
+ # these steps.
+ if open_els[0].name is subject and open_els[0].namespace is NS_HTML
+ debug_log "aaa: starting off with subject on top of stack, exiting"
+ # remove it from the list of active formatting elements (if found)
+ in_afe = false
+ for el, i in afe
+ if el is open_els[0]
+ in_afe = true
+ break
+ unless in_afe
+ debug_log "aaa: ...and not in afe, aaa done"
+ open_els.shift()
+ return
+ # fall through
+# END WHATWG
+ outer = 0
+ loop
+ if outer >= 8
+ return
+ outer += 1
+ # 5. Let formatting element be the last element in the list of
+ # active formatting elements that: is between the end of the list
+ # and the last scope marker in the list, if any, or the start of
+ # the list otherwise, and has the tag name subject.
+ fe = null
+ for t, fe_of_afe in afe
+ if t.type is TYPE_AFE_MARKER
+ break
+ if t.name is subject
+ fe = t
+ break
+ # If there is no such element, then abort these steps and instead
+ # act as described in the "any other end tag" entry above.
+ if fe is null
+ debug_log "aaa: fe not found in afe"
+ in_body_any_other_end_tag subject
+ return
+ # 6. If formatting element is not in the stack of open elements,
+ # then this is a parse error; remove the element from the list, and
+ # abort these steps.
+ in_open_els = false
+ for t, fe_of_open_els in open_els
+ if t is fe
+ in_open_els = true
+ break
+ unless in_open_els
+ debug_log "aaa: fe not found in open_els"
+ parse_error()
+ # "remove it from the list" must mean afe, since it's not in open_els
+ afe.splice fe_of_afe, 1
+ return
+ # 7. If formatting element is in the stack of open elements, but
+ # the element is not in scope, then this is a parse error; abort
+ # these steps.
+ unless el_is_in_scope fe
+ debug_log "aaa: fe not in scope"
+ parse_error()
+ return
+ # 8. If formatting element is not the current node, this is a parse
+ # error. (But do not abort these steps.)
+ unless open_els[0] is fe
+ parse_error()
+ # continue
+ # 9. Let furthest block be the topmost node in the stack of open
+ # elements that is lower in the stack than formatting element, and
+ # is an element in the special category. There might not be one.
+ fb = null
+ fb_of_open_els = null
+ for t, i in open_els
+ if t is fe
+ break
+ if el_is_special t
+ fb = t
+ fb_of_open_els = i
+ # and continue, to see if there's one that's more "topmost"
+ # 10. If there is no furthest block, then the UA must first pop all
+ # the nodes from the bottom of the stack of open elements, from the
+ # current node up to and including formatting element, then remove
+ # formatting element from the list of active formatting elements,
+ # and finally abort these steps.
+ if fb is null
+ debug_log "aaa: no fb"
+ loop
+ t = open_els.shift()
+ if t is fe
+ afe.splice fe_of_afe, 1
+ return
+ # 11. Let common ancestor be the element immediately above
+ # formatting element in the stack of open elements.
+ ca = open_els[fe_of_open_els + 1] # common ancestor
+
+ node_above = open_els[fb_of_open_els + 1] # next node if node isn't in open_els anymore
+ # 12. Let a bookmark note the position of formatting element in the list of active formatting elements relative to the elements on either side of it in the list.
+ bookmark = new_aaa_bookmark()
+ for t, i in afe
+ if t is fe
+ afe.splice i, 0, bookmark
+ break
+ node = last_node = fb
+ inner = 0
+ loop
+ inner += 1
+ # 3. Let node be the element immediately above node in the
+ # stack of open elements, or if node is no longer in the stack
+ # of open elements (e.g. because it got removed by this
+ # algorithm), the element that was immediately above node in
+ # the stack of open elements before node was removed.
+ node_next = null
+ for t, i in open_els
+ if t is node
+ node_next = open_els[i + 1]
+ break
+ node = node_next ? node_above
+ debug_log "inner loop #{inner}"
+ debug_log "tree: #{serialize_els doc.children, false, true}"
+ debug_log "open_els: #{serialize_els open_els, true, true}"
+ debug_log "afe: #{serialize_els afe, true, true}"
+ debug_log "ca: #{ca.name}##{ca.id} children: #{serialize_els ca.children, true, true}"
+ debug_log "fe: #{fe.name}##{fe.id} children: #{serialize_els fe.children, true, true}"
+ debug_log "fb: #{fb.name}##{fb.id} children: #{serialize_els fb.children, true, true}"
+ debug_log "node: #{node.serialize true, true}"
+ # TODO make sure node_above gets re-set if/when node is removed from open_els
+
+ # 4. If node is formatting element, then go to the next step in
+ # the overall algorithm.
+ if node is fe
+ break
+ debug_log "the meat"
+ # 5. If inner loop counter is greater than three and node is in
+ # the list of active formatting elements, then remove node from
+ # the list of active formatting elements.
+ node_in_afe = false
+ for t, i in afe
+ if t is node
+ if inner > 3
+ afe.splice i, 1
+ debug_log "max out inner"
+ else
+ node_in_afe = true
+ debug_log "in afe"
+ break
+ # 6. If node is not in the list of active formatting elements,
+ # then remove node from the stack of open elements and then go
+ # back to the step labeled inner loop.
+ unless node_in_afe
+ debug_log "not in afe"
+ for t, i in open_els
+ if t is node
+ node_above = open_els[i + 1]
+ open_els.splice i, 1
+ break
+ continue
+ debug_log "the bones"
+ # 7. create an element for the token for which the element node
+ # was created, in the HTML namespace, with common ancestor as
+ # the intended parent; replace the entry for node in the list
+ # of active formatting elements with an entry for the new
+ # element, replace the entry for node in the stack of open
+ # elements with an entry for the new element, and let node be
+ # the new element.
+ new_node = token_to_element node.token, NS_HTML, ca
+ for t, i in afe
+ if t is node
+ afe[i] = new_node
+ debug_log "replaced in afe"
+ break
+ for t, i in open_els
+ if t is node
+ node_above = open_els[i + 1]
+ open_els[i] = new_node
+ debug_log "replaced in open_els"
+ break
+ node = new_node
+ # 8. If last node is furthest block, then move the
+ # aforementioned bookmark to be immediately after the new node
+ # in the list of active formatting elements.
+ if last_node is fb
+ for t, i in afe
+ if t is bookmark
+ afe.splice i, 1
+ debug_log "removed bookmark"
+ break
+ for t, i in afe
+ if t is node
+ # "after" means lower
+ afe.splice i, 0, bookmark # "after as <-
+ debug_log "placed bookmark after node"
+ debug_log "node: #{node.id} afe: #{serialize_els afe, true, true}"
+ break
+ # 9. Insert last node into node, first removing it from its
+ # previous parent node if any.
+ if last_node.parent?
+ debug_log "last_node has parent"
+ for c, i in last_node.parent.children
+ if c is last_node
+ debug_log "removing last_node from parent"
+ last_node.parent.children.splice i, 1
+ break
+ node.children.push last_node
+ last_node.parent = node
+ # 10. Let last node be node.
+ last_node = node
+ debug_log "at last"
+ # 11. Return to the step labeled inner loop.
+ # 14. Insert whatever last node ended up being in the previous step
+ # at the appropriate place for inserting a node, but using common
+ # ancestor as the override target.
+
+ # In the case where fe is immediately followed by fb:
+ # * inner loop exits out early (node==fe)
+ # * last_node is fb
+ # * last_node is still in the tree (not a duplicate)
+ if last_node.parent?
+ debug_log "FEFIRST? last_node has parent"
+ for c, i in last_node.parent.children
+ if c is last_node
+ debug_log "removing last_node from parent"
+ last_node.parent.children.splice i, 1
+ break
+
+ debug_log "after aaa inner loop"
+ debug_log "ca: #{ca.name}##{ca.id} children: #{serialize_els ca.children, true, true}"
+ debug_log "fe: #{fe.name}##{fe.id} children: #{serialize_els fe.children, true, true}"
+ debug_log "fb: #{fb.name}##{fb.id} children: #{serialize_els fb.children, true, true}"
+ debug_log "last_node: #{last_node.name}##{last_node.id} children: #{serialize_els last_node.children, true, true}"
+ debug_log "tree: #{serialize_els doc.children, false, true}"
+
+ debug_log "insert"
+
+
+ # can't use standard insert token thing, because it's already in
+ # open_els and must stay at it's current position in open_els
+ dest = adjusted_insertion_location ca
+ dest[0].children.splice dest[1], 0, last_node
+ last_node.parent = dest[0]
+