JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
parse tables, reset_insertion_mode, foster_parenting
authorJason Woofenden <jason@jasonwoof.com>
Fri, 18 Dec 2015 23:02:31 +0000 (18:02 -0500)
committerJason Woofenden <jason@jasonwoof.com>
Fri, 18 Dec 2015 23:02:31 +0000 (18:02 -0500)
parse-html.coffee

index f0f5e91..c71567d 100644 (file)
@@ -38,9 +38,9 @@
 #
 # "grows downwards" means it's visualized like this: (index: el, names)
 #
-#   6: g "start of the list", "topmost"
+#   6: g "start of the list", "topmost", "first"
 #   5: f
-#   4: e "previous" (to d), "above"
+#   4: e "previous" (to d), "above", "before"
 #   3: d   (previous/next are relative to this element)
 #   2: c "next", "after", "lower", "below"
 #   1: b
@@ -54,10 +54,10 @@ TYPE_TEXT = 1 # "text"
 TYPE_COMMENT = 2
 TYPE_DOCTYPE = 3
 # the following types are emited by the tokenizer, but shouldn't end up in the tree:
-TYPE_OPEN_TAG = 4 # name, [attributes ([key,value]...) in reverse order], [children]
+TYPE_START_TAG = 4 # name, [attributes ([key,value]...) in reverse order], [children]
 TYPE_END_TAG = 5 # name
 TYPE_EOF = 6
-TYPE_MARKER = 7 # http://www.w3.org/TR/html5/syntax.html#reconstruct-the-active-formatting-elements
+TYPE_AFE_MARKER = 7 # http://www.w3.org/TR/html5/syntax.html#reconstruct-the-active-formatting-elements
 TYPE_AAA_BOOKMARK = 8 # http://www.w3.org/TR/html5/syntax.html#adoption-agency-algorithm
 
 # namespace constants
@@ -81,7 +81,7 @@ class Node
                @name = args.name ? '' # tag name
                @text = args.text ? '' # contents for text/comment nodes
                @attrs = args.attrs ? {}
-               @attrs_a = args.attr_k ? [] # attrs in progress, TYPE_OPEN_TAG only
+               @attrs_a = args.attr_k ? [] # attrs in progress, TYPE_START_TAG only
                @children = args.children ? []
                @namespace = args.namespace ? NS_HTML
                @parent = args.parent ? null
@@ -122,7 +122,7 @@ class Node
                        when TYPE_DOCTYPE
                                ret += 'doctype'
                                # FIXME
-                       when TYPE_MARKER
+                       when TYPE_AFE_MARKER
                                ret += 'marker'
                        when TYPE_AAA_BOOKMARK
                                ret += 'aaa_bookmark'
@@ -133,7 +133,7 @@ class Node
 
 # helpers: (only take args that are normally known when parser creates nodes)
 new_open_tag = (name) ->
-       return new Node TYPE_OPEN_TAG, name: name
+       return new Node TYPE_START_TAG, name: name
 new_end_tag = (name) ->
        return new Node TYPE_END_TAG, name: name
 new_element = (name) ->
@@ -144,6 +144,8 @@ new_comment_node = (txt) ->
        return new Node TYPE_COMMENT, text: txt
 new_eof_token = ->
        return new Node TYPE_EOF
+new_afe_marker = ->
+       return new Node TYPE_AFE_MARKER
 new_aaa_bookmark = ->
        return new Node TYPE_AAA_BOOKMARK
 
@@ -276,6 +278,14 @@ formatting_elements = {
         u: true
 }
 
+foster_parenting_targets = {
+       table: true
+       tbody: true
+       tfoot: true
+       thead: true
+       tr: true
+}
+
 # all html I presume
 end_tag_implied = {
        dd: true
@@ -322,12 +332,13 @@ parse_html = (txt, parse_error_cb = null) ->
        # declare tree and tokenizer variables so they're in scope below
        tree = null
        open_els = [] # stack of open elements
-       tree_state = null
+       insertion_mode = null
        tok_state = null
        tok_cur_tag = null # partially parsed tag
        flag_frameset_ok = null
        flag_parsing = null
        flag_foster_parenting = null
+       form_element_pointer = null
        afe = [] # active formatting elements
 
        parse_error = ->
@@ -397,12 +408,135 @@ parse_html = (txt, parse_error_cb = null) ->
                                return false
                return false
 
+       # 8.2.3.1 ...
+       # http://www.w3.org/TR/html5/syntax.html#reset-the-insertion-mode-appropriately
+       reset_insertion_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'
+                               # 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'
+                                                       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'
+                                                       insertion_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.
+                               insertion_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 last is false
+                               insertion_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'
+                               insertion_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'
+                               insertion_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'
+                               insertion_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'
+                               insertion_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'
+                               insertion_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.
+                       # fixfull (template insertion mode stack)
+
+                       # 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 last
+                               insertion_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 last is false
+                               insertion_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'
+                               insertion_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'
+                               insertion_mode = ins_mode_in_frameset
+                               return
+                       # 16. If node is an html element, run these substeps:
+                       if node.name is 'html'
+                               # 1. If the head element pointer is null, switch the insertion
+                               # mode to "before head" and abort these steps. (fragment case)
+                               # fixfull (fragment case)
+
+                               # 2. Otherwise, the head element pointer is not null, switch
+                               # the insertion mode to "after head" and abort these steps.
+                               insertion_mode = ins_mode_in_body # FIXME fixfull
+                               return
+                       # 17. If last is true, then switch the insertion mode to "in body"
+                       # and abort these steps. (fragment case)
+                       if last
+                               insertion_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.
+
        # 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_active_formatting_elements = ->
                return if afe.length is 0
-               if afe[0].type is TYPE_MARKER or afe[0] in open_els
+               if afe[0].type is TYPE_AFE_MARKER or afe[0] in open_els
                        return
                # Rewind
                i = 0
@@ -410,7 +544,7 @@ parse_html = (txt, parse_error_cb = null) ->
                        if i is afe.length - 1
                                break
                        i += 1
-                       if afe[i].type is TYPE_MARKER or afe[i] in open_els
+                       if afe[i].type is TYPE_AFE_MARKER or afe[i] in open_els
                                i -= 1 # Advance
                                break
                # Create
@@ -448,7 +582,7 @@ parse_html = (txt, parse_error_cb = null) ->
                        # the list otherwise, and  has the tag name subject.
                        fe = null
                        for t, fe_of_afe in afe
-                               if t.type is TYPE_MARKER
+                               if t.type is TYPE_AFE_MARKER
                                        break
                                if t.name is subject
                                        fe = t
@@ -744,38 +878,69 @@ parse_html = (txt, parse_error_cb = null) ->
                # If foster parenting is enabled and target is a table, tbody, tfoot,
                # thead, or tr element Foster parenting happens when content is
                # misnested in tables.
-               if flag_foster_parenting and target.name in foster_parenting_targets
-                       console.log "foster parenting isn't implemented yet" # TODO
-                       # 1. Let last template be the last template element in the stack of
-                       # open elements, if any.
-                       # 2. Let last table be the last table element in the stack of open
-                       # elements, if any.
-
-                       # 3. If there is a last template and either there is no last table,
-                       # or there is one, but last template is lower (more recently added)
-                       # than last table in the stack of open elements, then: let adjusted
-                       # insertion location be inside last template's template contents,
-                       # after its last child (if any), and abort these substeps.
-
-                       # 4. If there is no last table, then let adjusted insertion
-                       # location be inside the first element in the stack of open
-                       # elements (the html element), after its last child (if any), and
-                       # abort these substeps. (fragment case)
-
-                       # 5. If last table has a parent element, then let adjusted
-                       # insertion location be inside last table's parent element,
-                       # immediately before last table, and abort these substeps.
-
-                       # 6. Let previous element be the element immediately above last
-                       # table in the stack of open elements.
-
-                       # 7. Let adjusted insertion location be inside previous element,
-                       # after its last child (if any).
-
-                       # Note: These steps are involved in part because it's possible for
-                       # elements, the table element in this case in particular, to have
-                       # been moved by a script around in the DOM, or indeed removed from
-                       # the DOM entirely, after the element was inserted by the parser.
+               if flag_foster_parenting and foster_parenting_targets[target.name]
+                       loop # once. this is here so we can ``break`` to "abort these substeps"
+                               # 1. Let last template be the last template element in the
+                               # stack of open elements, if any.
+                               last_template = null
+                               last_template_i = null
+                               for el, i in open_els
+                                       if el.name is 'template'
+                                               last_template = el
+                                               last_template_i = i
+                                               break
+                               # 2. Let last table be the last table element in the stack of
+                               # open elements, if any.
+                               last_table = null
+                               last_table_i
+                               for el, i in open_els
+                                       if el.name is 'table'
+                                               last_table = el
+                                               last_table_i = i
+                                               break
+                               # 3. If there is a last template and either there is no last
+                               # table, or there is one, but last template is lower (more
+                               # recently added) than last table in the stack of open
+                               # elements, then: let adjusted insertion location be inside
+                               # last template's template contents, after its last child (if
+                               # any), and abort these substeps.
+                               if last_template and (last_table is null or last_template_i < last_table_i)
+                                       target = template # fixfull should be it's contents
+                                       target_i = target.children.length
+                                       break
+                               # 4. If there is no last table, then let adjusted insertion
+                               # location be inside the first element in the stack of open
+                               # elements (the html element), after its last child (if any),
+                               # and abort these substeps. (fragment case)
+                               if last_table is null
+                                       # this is odd
+                                       target = open_els[open_els.length - 1]
+                                       target_i = target.children.length
+                               # 5. If last table has a parent element, then let adjusted
+                               # insertion location be inside last table's parent element,
+                               # immediately before last table, and abort these substeps.
+                               if last_table.parent?
+                                       for c, i in last_table.parent.children
+                                               if c is last_table
+                                                       target = last_table.parent
+                                                       target_i = i
+                                                       break
+                                       break
+                               # 6. Let previous element be the element immediately above last
+                               # table in the stack of open elements.
+                               #
+                               # huh? how could it not have a parent?
+                               previous_element = open_els[last_table_i + 1]
+                               # 7. Let adjusted insertion location be inside previous
+                               # element, after its last child (if any).
+                               target = previous_element
+                               target_i = target.children.length
+                               # Note: These steps are involved in part because it's possible
+                               # for elements, the table element in this case in particular,
+                               # to have been moved by a script around in the DOM, or indeed
+                               # removed from the DOM entirely, after the element was inserted
+                               # by the parser.
+                               break # don't really loop
                else
                        # Otherwise Let adjusted insertion location be inside target, after
                        # its last child (if any).
@@ -783,7 +948,8 @@ parse_html = (txt, parse_error_cb = null) ->
 
                # 3. If the adjusted insertion location is inside a template element,
                # let it instead be inside the template element's template contents,
-               # after its last child (if any). TODO
+               # after its last child (if any).
+               # fixfull (template)
 
                # 4. Return the adjusted insertion location.
                return [target, target_i]
@@ -791,7 +957,7 @@ parse_html = (txt, parse_error_cb = null) ->
        # http://www.w3.org/TR/html5/syntax.html#create-an-element-for-the-token
        # aka create_an_element_for_token
        token_to_element = (t, namespace, intended_parent) ->
-               t.type = TYPE_TAG # not TYPE_OPEN_TAG
+               t.type = TYPE_TAG # not TYPE_START_TAG
                # convert attributes into a hash
                attrs = {}
                while t.attrs_a.length
@@ -834,7 +1000,7 @@ parse_html = (txt, parse_error_cb = null) ->
                if namespace?
                        el.namespace = namespace
                dest = adjusted_insertion_location override_target
-               if el.type is TYPE_OPEN_TAG # means it's a "token"
+               if el.type is TYPE_START_TAG # means it's a "token"
                        el = token_to_element el, namespace, dest[0]
                unless el.namespace?
                        namespace = dest.namespace
@@ -845,9 +1011,10 @@ parse_html = (txt, parse_error_cb = null) ->
                return el
 
        # http://www.w3.org/TR/html5/syntax.html#insert-a-comment
-       tree_insert_a_comment = (t) ->
-               # FIXME read spec for "adjusted insertion location, etc, this might be wrong
-               open_els[0].children.push t
+       # position should be [node, index_within_children]
+       tree_insert_a_comment = (t, position = null) ->
+               position ?= adjusted_insertion_location()
+               position[0].children.splice position[1], 0, t
 
        # 8.2.5.3 http://www.w3.org/TR/html5/syntax.html#closing-elements-that-have-implied-end-tags
        # http://www.w3.org/TR/html5/syntax.html#generate-implied-end-tags
@@ -868,7 +1035,7 @@ parse_html = (txt, parse_error_cb = null) ->
                        if special_elements[node.name]? # FIXME check namespac too
                                parse_error()
                                return
-       tree_in_body = (t) ->
+       ins_mode_in_body = (t) ->
                switch t.type
                        when TYPE_TEXT
                                switch t.text
@@ -885,7 +1052,7 @@ parse_html = (txt, parse_error_cb = null) ->
                                tree_insert_a_comment t
                        when TYPE_DOCTYPE
                                parse_error()
-                       when TYPE_OPEN_TAG
+                       when TYPE_START_TAG
                                switch t.name
                                        when 'html'
                                                parse_error()
@@ -925,7 +1092,7 @@ parse_html = (txt, parse_error_cb = null) ->
                                                # is not in table scope).
                                                found = false
                                                for el in afe
-                                                       if el.type is TYPE_MARKER
+                                                       if el.type is TYPE_AFE_MARKER
                                                                break
                                                        if el.name is 'a'
                                                                found = el
@@ -945,6 +1112,11 @@ parse_html = (txt, parse_error_cb = null) ->
                                                reconstruct_active_formatting_elements()
                                                el = tree_insert_element t
                                                afe.unshift el
+                                       when 'table'
+                                               # fixfull quirksmode thing
+                                               close_p_if_in_button_scope()
+                                               insert_html_element t
+                                               insertion_mode = ins_mode_in_table
                                        # TODO lots more to implement here
                                        else # any other start tag
                                                reconstruct_active_formatting_elements()
@@ -997,6 +1169,295 @@ parse_html = (txt, parse_error_cb = null) ->
                                                in_body_any_other_end_tag t.name
                return
 
+       ins_mode_in_table_else = (t) ->
+               parse_error()
+               flag_foster_parenting = true # FIXME
+               ins_mode_in_body t
+               flag_foster_parenting = false
+       can_in_table = {
+               'table': true
+               'tbody': true
+               'tfoot': true
+               'thead': true
+               'tr': true
+       }
+       clear_to_table_stopers = {
+               'table': true
+               'template': true
+               'html': true
+       }
+       clear_stack_to_table_context = ->
+               loop
+                       if clear_to_table_stopers[open_els[0].name]?
+                               break
+                       open_els.shift()
+               return
+       clear_to_table_body_stopers = {
+               'tbody': true
+               'tfoot': true
+               'thead': true
+               'template': true
+               'html': true
+       }
+       clear_stack_to_table_body_context = ->
+               loop
+                       if clear_to_table_body_stopers[open_els[0].name]?
+                               break
+                       open_els.shift()
+               return
+       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
+                       el = afe.shift()
+                       if el.type is TYPE_AFE_MARKER
+                               return
+       ins_mode_in_table = (t) ->
+               switch t.type
+                       when TYPE_TEXT
+                               if can_in_table[t.name]
+                                       original_insertion_mode = insertion_mode
+                                       insertion_mode = ins_mode_in_table_text
+                                       insertion_mode t
+                               else
+                                       ins_mode_in_table_else t
+                       when TYPE_COMMENT
+                               tree_insert_a_comment t
+                       when TYPE_DOCTYPE
+                               parse_error()
+                       when TYPE_START_TAG
+                               switch t.name
+                                       when 'caption'
+                                               clear_stack_to_table_context()
+                                               afe.unshift new_afe_marker()
+                                               insert_html_element t
+                                               insertion_mode = ins_mode_in_caption
+                                       when 'colgroup'
+                                               clear_stack_to_table_context()
+                                               insert_html_element t
+                                               insertion_mode = ins_mode_in_column_group
+                                       when 'col'
+                                               clear_stack_to_table_context()
+                                               insert_html_element new_open_tag 'colgroup'
+                                               insertion_mode = ins_mode_in_column_group
+                                               insertion_mode t
+                                       when 'tbody', 'tfoot', 'thead'
+                                               clear_stack_to_table_context()
+                                               insert_html_element t
+                                               insertion_mode = ins_mode_in_table_body
+                                       when 'td', 'th', 'tr'
+                                               clear_stack_to_table_context()
+                                               insert_html_element new_open_tag 'tbody'
+                                               insertion_mode = ins_mode_in_table_body
+                                               insertion_mode t
+                                       when 'table'
+                                               parse_error()
+                                               if is_in_table_scope 'table'
+                                                       loop
+                                                               el = open_els.shift()
+                                                               if el.name is 'table'
+                                                                       break
+                                                       reset_insertion_mode()
+                                                       insertion_mode t
+                                       when 'style', 'script', 'template'
+                                               ins_mode_in_head t
+                                       when 'input'
+                                               if token_is_input_hidden t
+                                                       ins_mode_in_table_else t
+                                               else
+                                                       parse_error()
+                                                       insert_html_element t
+                                                       open_els.shift()
+                                                       # fixfull acknowledge sef-closing flag
+                                       when 'form'
+                                               parse_error()
+                                               if form_element_pointer?
+                                                       return
+                                               if template_tag_is_open()
+                                                       return
+                                               form_element_pointer = insert_html_element t
+                                               open_els.shift()
+                                       else
+                                               ins_mode_in_table_else t
+                       when TYPE_END_TAG
+                               switch t.name
+                                       when 'table'
+                                               if is_in_table_scope 'table'
+                                                       loop
+                                                               el = open_els.shift()
+                                                               if el.name is 'table'
+                                                                       break
+                                                       reset_insertion_mode()
+                                               else
+                                                       parse_error
+                                       when 'body', 'caption', 'col', 'colgroup', 'html', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr'
+                                               parse_error()
+                                       when 'template'
+                                               ins_mode_in_head t
+                                       else
+                                               ins_mode_in_table_else t
+                       when TYPE_EOF
+                               ins_mode_in_body t
+                       else
+                               ins_mode_in_table_else t
+
+
+       ins_mode_in_table_text = (t) ->
+               switch t.type
+                       when TYPE_TEXT
+                               switch t.text
+                                       when "\u0000"
+                                               parse_error()
+                                               return
+               console.log "unimplemented ins_mode_in_table_text"
+               # FIXME CONTINUE
+
+       ins_mode_in_table_body = (t) ->
+               if t.type is TYPE_START_TAG and t.name is 'tr'
+                       clear_stack_to_table_body_context()
+                       insert_html_element t
+                       return
+               if t.type is TYPE_START_TAG and (t.name is 'th' or t.name is 'td')
+                       parse_error()
+                       clear_stack_to_table_body_context()
+                       insert_html_element new_open_tag 'tr'
+                       insertion_mode = ins_mode_in_row
+                       insertion_mode t
+                       return
+               if t.type is TYPE_END_TAG and (t.name is 'tbody' or t.name is 'tfoot' or t.name is 'thead')
+                       unless is_in_table_scope t.name # fixfull check namespace
+                               parse_error()
+                               return
+                       clear_stack_to_table_body_context()
+                       open_els.shift()
+                       insertion_mode = ins_mode_in_table
+                       return
+               if (t.type is TYPE_START_TAG and (t.name is 'caption' or t.name is 'col' or t.name is 'colgroup' or t.name is 'tbody' or t.name is 'tfoot' or t.name is 'thead')) or (t.type is TYPE_END_TAG and t.name is 'table')
+                       has = false
+                       for el in open_els
+                               if el.name is 'tbody' or el.name is 'tfoot' or el.name is 'thead'
+                                       has = true
+                                       break
+                               if table_scopers[el.name]
+                                       break
+                       if !has
+                               parse_error()
+                               return
+                       clear_stack_to_table_body_context()
+                       open_els.shift()
+                       insertion_mode = ins_mode_in_table
+                       insertion_mode t
+                       return
+               if t.type is TYPE_END_TAG and (t.name is 'body' or t.name is 'caption' or t.name is 'col' or t.name is 'colgroup' or t.name is 'html' or t.name is 'td' or t.name is 'th' or t.name is 'tr')
+                       parse_error()
+                       return
+               # Anything else
+               ins_mode_in_table t
+
+       ins_mode_in_row = (t) ->
+               if t.type is TYPE_START_TAG and (t.name is 'th' or t.name is 'td')
+                       clear_stack_to_table_row_context()
+                       insert_html_element t
+                       insertion_mode = ins_mode_in_cell
+                       afe.unshift new_afe_marker()
+                       return
+               if t.type is TYPE_END_TAG and t.name is 'tr'
+                       if is_in_table_scope 'tr'
+                               clear_stack_to_table_row_context()
+                               open_els.shift()
+                               insertion_mode = ins_mode_in_table_body
+                       else
+                               parse_error()
+                       return
+               if (t.type is TYPE_START_TAG and (t.name is 'caption' or t.name is 'col' or t.name is 'colgroup' or t.name is 'tbody' or t.name is 'tfoot' or t.name is 'thead' or t.name is 'tr')) or t.type is TYPE_END_TAG and t.name is 'table'
+                       if is_in_table_scope 'tr'
+                               clear_stack_to_table_row_context()
+                               open_els.shift()
+                               insertion_mode = ins_mode_in_table_body
+                               insertion_mode t
+                       else
+                               parse_error()
+                       return
+               if t.type is TYPE_END_TAG and (t.name is 'tbody' or t.name is 'tfoot' or t.name is 'thead')
+                       if is_in_table_scope t.name # fixfull namespace
+                               if is_in_table_scope 'tr'
+                                       clear_stack_to_table_row_context()
+                                       open_els.shift()
+                                       insertion_mode = ins_mode_in_table_body
+                                       insertion_mode t
+                       else
+                               parse_error()
+                       return
+               if t.type is TYPE_END_TAG and (t.name is 'body' or t.name is 'caption' or t.name is 'col' or t.name is 'colgroup' or t.name is 'html' or t.name is 'td' or t.name is 'th')
+                       parse_error()
+                       return
+               # Anything else
+               ins_mode_in_table t
+
+       # http://www.w3.org/TR/html5/syntax.html#close-the-cell
+       close_the_cell = ->
+               generate_implied_end_tags()
+               unless open_els[0].name is 'td' or open_els[0] is 'th'
+                       parse_error()
+               loop
+                       el = open_els.shift()
+                       if el.name is 'td' or el.name is 'th'
+                               break
+               clear_afe_to_marker()
+               insertion_mode = ins_mode_in_row
+
+       # http://www.w3.org/TR/html5/syntax.html#parsing-main-intd
+       ins_mode_in_cell = (t) ->
+               if t.type is TYPE_END_TAG and (t.name is 'td' or t.name is 'th')
+                       if is_in_table_scope t.name
+                               generate_implied_end_tags()
+                               if open_els[0].name isnt t.name
+                                       parse_error
+                               loop
+                                       el = open_els.shift()
+                                       if el.name is t.name
+                                               break
+                               clear_afe_to_marker()
+                               insertion_mode = ins_mode_in_row
+                       else
+                               parse_error()
+                       return
+               if t.type is TYPE_START_TAG and (t.name is 'caption' or t.name is 'col' or t.name is 'colgroup' or t.name is 'tbody' or t.name is 'td' or t.name is 'tfoot' or t.name is 'th' or t.name is 'thead' or t.name is 'tr')
+                       has = false
+                       for el in open_els
+                               if el.name is 'td' or el.name is 'th'
+                                       has = true
+                                       break
+                               if table_scopers[el.name]
+                                       break
+                       if !has
+                               parse_error()
+                               return
+                       close_the_cell()
+                       insertion_mode t
+                       return
+               if t.type is TYPE_END_TAG and (t.name is 'body' or t.name is 'caption' or t.name is 'col' or t.name is 'colgroup' or t.name is 'html')
+                       parse_error()
+                       return
+               if t.type is TYPE_END_TAG and (t.name is 'table' or t.name is 'tbody' or t.name is 'tfoot' or t.name is 'thead' or t.name is 'tr')
+                       if is_in_table_scope t.name # fixfull namespace
+                               close_the_cell()
+                               insertion_mode t
+                       else
+                               parse_error()
+                       return
+               # Anything Else
+               ins_mode_in_body t
+
 
        # the functions below implement the tokenizer stats described here:
        # http://www.w3.org/TR/html5/syntax.html#tokenization
@@ -1342,10 +1803,11 @@ parse_html = (txt, parse_error_cb = null) ->
        # see comments on TYPE_TAG/etc for the structure of this data
        tree = new Node TYPE_TAG, name: 'html', namespace: NS_HTML
        open_els = [tree]
-       tree_state = tree_in_body
+       insertion_mode = ins_mode_in_body
        flag_frameset_ok = true
        flag_parsing = true
        flag_foster_parenting = false
+       form_element_pointer = null
        afe = [] # active formatting elements
 
        # tokenizer initialization
@@ -1355,7 +1817,7 @@ parse_html = (txt, parse_error_cb = null) ->
        while flag_parsing
                t = tok_state()
                if t?
-                       tree_state t
+                       insertion_mode t
        return tree.children
 
 # everything below is tests on the above
@@ -1486,7 +1948,15 @@ test_parser name: "html5lib aaa 4", \
        html: '<a>1<b>2</a>3</b>',
        expected: 'tag:"a",{},[text:"1",tag:"b",{},[text:"2"]],tag:"b",{},[text:"3"]',
        errors: 2
-test_parser name: "html5lib aaa 5", \
+test_parser name: "html5lib aaa 5 (two divs deep)", \
        html: '<a>1<div>2<div>3</a>4</div>5</div>',
        expected: 'tag:"a",{},[text:"1"],tag:"div",{},[tag:"a",{},[text:"2"],tag:"div",{},[tag:"a",{},[text:"3"],text:"4"],text:"5"]',
        errors: 3
+test_parser name: "html5lib aaa 6 (foster parenting)", \
+       html: '<table><a>1<p>2</a>3</p>',
+       expected: 'tag:"a",{},[text:"1"],tag:"p",{},[tag:"a",{},[text:"2"],text:"3"],tag:"table",{},[]',
+       errors: 10
+test_parser name: "html5lib aaa 11 (table with foster parenting, formatting el and td)", \
+       html: '<table><a>1<td>2</td>3</table>',
+       expected: 'tag:"a",{},[text:"1"],tag:"a",{},[text:"3"],tag:"table",{},[tag:"tbody",{},[tag:"tr",{},[tag:"td",{},[text:"2"]]]]',
+       errors: 10