+ if parse_error_cb?
+ parse_error_cb cur
+ else
+ console.log "Parse error at character #{cur} of #{txt.length}"
+
+ afe_push = (new_el) ->
+ matches = 0
+ for el, i in afe
+ if el.name is new_el.name and el.namespace is new_el.namespace
+ for k, v of el.attrs
+ continue unless new_el.attrs[k] is v
+ for k, v of new_el.attrs
+ continue unless el.attrs[k] is v
+ matches += 1
+ if matches is 3
+ afe.splice i, 1
+ break
+ afe.unshift new_el
+ afe_push_marker = ->
+ afe.unshift new_afe_marker()
+
+ # the functions below impliment the Tree Contstruction algorithm
+ # http://www.w3.org/TR/html5/syntax.html#tree-construction
+
+ # But first... the helpers
+ template_tag_is_open = ->
+ for t in open_els
+ if t.name is 'template' # maybe should also check: and t.namespace is 'html'
+ return true
+ return false
+ is_in_scope_x = (tag_name, scope, namespace) ->
+ for t in open_els
+ if t.name is tag_name and (namespace is null or namespace is t.namespace)
+ return true
+ if scope[t.name] is t.namespace
+ return false
+ return false
+ is_in_scope_x_y = (tag_name, scope, scope2, namespace) ->
+ for t in open_els
+ if t.name is tag_name and (namespace is null or namespace is t.namespace)
+ return true
+ if scope[t.name] is t.namespace
+ return false
+ if scope2[t.name] is t.namespace
+ return false
+ return false
+ standard_scopers = { # FIXME these are supposed to be namespace specific
+ applet: NS_HTML, caption: NS_HTML, html: NS_HTML, table: NS_HTML,
+ td: NS_HTML, th: NS_HTML, marquee: NS_HTML, object: NS_HTML,
+ template: NS_HTML, mi: NS_MATHML,
+
+ mo: NS_MATHML, mn: NS_MATHML, ms: NS_MATHML, mtext: NS_MATHML,
+ 'annotation-xml': NS_MATHML,
+
+ foreignObject: NS_SVG, desc: NS_SVG, title: NS_SVG
+ }
+ button_scopers = button: NS_HTML
+ li_scopers = ol: NS_HTML, ul: NS_HTML
+ table_scopers = html: NS_HTML, table: NS_HTML, template: NS_HTML
+ is_in_scope = (tag_name, namespace = null) ->
+ return is_in_scope_x tag_name, standard_scopers, namespace
+ is_in_button_scope = (tag_name, namespace = null) ->
+ return is_in_scope_x_y tag_name, standard_scopers, button_scopers, namespace
+ is_in_table_scope = (tag_name, namespace = null) ->
+ return is_in_scope_x tag_name, table_scopers, namespace
+ is_in_select_scope = (tag_name, namespace = null) ->
+ for t in open_els
+ if t.name is tag_name and (namespace is null or namespace is t.namespace)
+ return true
+ if t.ns isnt NS_HTML t.name isnt 'optgroup' and t.name isnt 'option'
+ return false
+ return false
+ # this checks for a particular element, not by name
+ el_is_in_scope = (el) ->
+ for t in open_els
+ if t is el
+ return true
+ if standard_scopers[t.name] is t.namespace
+ 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_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 = afe[i].shallow_clone()
+ tree_insert_element el
+ 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 tree.children, false, true}"
+ debug_log "open_els: #{serialize_els open_els, true, true}"
+ debug_log "afe: #{serialize_els afe, true, true}"
+ if open_els[0].name is subject
+ el = open_els[0]
+ 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
+ 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 tree.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 = node.shallow_clone()
+ 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.
+
+ # JASON: 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 tree.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]
+
+
+ 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 tree.children, false, true}"
+
+ # 15. Create an element for the token for which formatting element
+ # was created, in the HTML namespace, with furthest block as the
+ # intended parent.
+ new_element = fe.shallow_clone() # FIXME intended parent thing
+ # 16. Take all of the child nodes of furthest block and append them
+ # to the element created in the last step.
+ while fb.children.length
+ t = fb.children.shift()
+ t.parent = new_element
+ new_element.children.push t
+ # 17. Append that new element to furthest block.
+ new_element.parent = fb
+ fb.children.push new_element
+ # 18. Remove formatting element from the list of active formatting
+ # elements, and insert the new element into the list of active
+ # formatting elements at the position of the aforementioned
+ # bookmark.
+ for t, i in afe
+ if t is fe
+ afe.splice i, 1
+ break
+ for t, i in afe
+ if t is bookmark
+ afe[i] = new_element
+ break
+ # 19. Remove formatting element from the stack of open elements,
+ # and insert the new element into the stack of open elements
+ # immediately below the position of furthest block in that stack.
+ for t, i in open_els
+ if t is fe
+ open_els.splice i, 1
+ break
+ for t, i in open_els
+ if t is fb
+ open_els.splice i, 0, new_element
+ break
+ # 20. Jump back to the step labeled outer loop.
+ debug_log "done wrapping fb's children. new_element: #{new_element.name}##{new_element.id}"
+ debug_log "tree: #{serialize_els tree.children, false, true}"
+ debug_log "open_els: #{serialize_els open_els, true, true}"
+ debug_log "afe: #{serialize_els afe, true, true}"
+ debug_log "AAA DONE"
+
+ # http://www.w3.org/TR/html5/syntax.html#close-a-p-element
+ close_p_element = ->
+ generate_implied_end_tags 'p' # arg is exception
+ if open_els[0].name isnt 'p'
+ parse_error()
+ while open_els.length > 1 # just in case
+ el = open_els.shift()
+ if el.name is 'p'
+ return
+ close_p_if_in_button_scope = ->
+ if is_in_button_scope 'p'
+ close_p_element()
+
+ # http://www.w3.org/TR/html5/syntax.html#insert-a-character
+ # aka insert_a_character = (t) ->
+ insert_character = (t) ->
+ dest = adjusted_insertion_location()
+ # fixfull check for Document node
+ if dest[1] > 0
+ prev = dest[0].children[dest[1] - 1]
+ if prev.type is TYPE_TEXT
+ prev.text += t.text
+ return
+ dest[0].children.splice dest[1], 0, t
+
+ # 8.2.5.1
+ # http://www.w3.org/TR/html5/syntax.html#creating-and-inserting-nodes
+ # http://www.w3.org/TR/html5/syntax.html#appropriate-place-for-inserting-a-node
+ adjusted_insertion_location = (override_target = null) ->
+ # 1. If there was an override target specified, then let target be the
+ # override target.
+ if override_target?
+ target = override_target
+ else # Otherwise, let target be the current node.
+ target = open_els[0]
+ # 2. Determine the adjusted insertion location using the first matching
+ # steps from the following list:
+ #
+ # 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 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).
+ target_i = target.children.length
+
+ # 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).
+ # fixfull (template)
+
+ # 4. Return the adjusted insertion location.
+ return [target, target_i]
+
+ # 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_START_TAG
+ # convert attributes into a hash
+ attrs = {}
+ while t.attrs_a.length
+ a = t.attrs_a.pop()
+ attrs[a[0]] = a[1] # TODO check what to do with dupilcate attrs
+ el = new Node TYPE_TAG, name: t.name, namespace: namespace, attrs: attrs
+
+ # TODO 2. If the newly created element has an xmlns attribute in the
+ # XMLNS namespace whose value is not exactly the same as the element's
+ # namespace, that is a parse error. Similarly, if the newly created
+ # element has an xmlns:xlink attribute in the XMLNS namespace whose
+ # value is not the XLink Namespace, that is a parse error.
+
+ # fixfull: the spec says stuff about form pointers and ownerDocument
+
+ return el
+
+ # http://www.w3.org/TR/html5/syntax.html#insert-a-foreign-element
+ insert_foreign_element = (token, namespace) ->
+ ail = adjusted_insertion_location()
+ ail_el = ail[0]
+ ail_i = ail[1]
+ el = token_to_element token, namespace, ail_el
+ # TODO skip this next step if it's broken (eg ail_el is document with child already)
+ el.parent = ail_el
+ ail_el.children.splice ail_i, 0, el
+ open_els.unshift el
+ return el
+ # http://www.w3.org/TR/html5/syntax.html#insert-an-html-element
+ insert_html_element = insert_foreign_element # (token, namespace) ->
+
+ # FIXME read implement "foster parenting" part
+ # FIXME read spec, do this right
+ # FIXME implement the override target thing
+ # note: this assumes it's an open tag
+ # FIXME what part of the spec is this?
+ # TODO look through all callers of this, and see what they should really be doing.
+ # eg probably insert_html_element for tokens
+ tree_insert_element = (el, override_target = null, namespace = null) ->
+ if namespace?
+ el.namespace = namespace
+ dest = adjusted_insertion_location override_target
+ 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
+ # fixfull: Document nodes sometimes can't accept more chidren
+ dest[0].children.splice dest[1], 0, el
+ el.parent = dest[0]
+ open_els.unshift el
+ return el
+
+ # http://www.w3.org/TR/html5/syntax.html#insert-a-comment
+ # position should be [node, index_within_children]
+ tree_insert_comment = (t, position = null) ->
+ position ?= adjusted_insertion_location()
+ position[0].children.splice position[1], 0, t
+
+ # 8.2.5.2
+ # http://www.w3.org/TR/html5/syntax.html#generic-raw-text-element-parsing-algorithm
+ parse_generic_raw_text = (t) ->
+ insert_html_element t
+ tok_state = tok_state_rawtext
+ original_insertion_mode = insertion_mode
+ insertion_mode = ins_mode_text
+ parse_generic_rcdata_text = (t) ->
+ insert_html_element t
+ tok_state = tok_state_rcdata
+ original_insertion_mode = insertion_mode
+ insertion_mode = ins_mode_text
+
+ # 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
+ generate_implied_end_tags = (except = null) ->
+ while end_tag_implied[open_els[0].name] and open_els[0].name isnt except
+ open_els.shift()
+
+ # 8.2.5.4.4 http://www.w3.org/TR/html5/syntax.html#parsing-main-inhead
+ ins_mode_in_head_else = (t) -> # factored out for same-as-spec flow control
+ open_els.shift() # spec says this will be a 'head' node
+ insertion_mode = ins_mode_after_head
+ insertion_mode t
+ ins_mode_in_head = (t) ->
+ if t.type is TYPE_TEXT and (t.text is "\t" or t.text is "\n" or t.text is "\u000c" or t.text is ' ')
+ insert_character t
+ return
+ if t.type is TYPE_COMMENT
+ tree_insert_comment t
+ return
+ if t.type is TYPE_DOCTYPE
+ parse_error()
+ return
+ if t.type is TYPE_START_TAG and t.name is 'html'
+ ins_mode_in_body t
+ return
+ if t.type is TYPE_START_TAG and (t.name is 'base' or t.name is 'basefont' or t.name is 'bgsound' or t.name is 'link')
+ el = insert_html_element t
+ open_els.shift()
+ el.acknowledge_self_closing()
+ return
+ if t.type is TYPE_START_TAG and t.name is 'meta'
+ el = insert_html_element t
+ open_els.shift()
+ el.acknowledge_self_closing()
+ # fixfull encoding stuff
+ return
+ if t.type is TYPE_START_TAG and t.name is 'title'
+ parse_generic_rcdata_element t
+ return
+ if t.type is TYPE_START_TAG and ((t.name is 'noscript' and flag_scripting) or (t.name is 'noframes' or t.name is 'style'))
+ parse_generic_raw_text t
+ return
+ if t.type is TYPE_START_TAG and t.name is 'noscript' and flag_scripting is false
+ insert_html_element t
+ insertion_mode = in_head_noscript # FIXME implement
+ return
+ if t.type is TYPE_START_TAG and t.name is 'script'
+ ail = adjusted_insertion_location()
+ el = token_to_element t, NS_HTML, ail
+ el.flag_parser_inserted true # FIXME implement
+ # fixfull frament case
+ ail[0].children.splice ail[1], 0, el
+ open_els.unshift el
+ tok_state = tok_state_script_data
+ original_insertion_mode = insertion_mode # make sure orig... is defined
+ insertion_mode = ins_mode_text # FIXME implement
+ return
+ if t.type is TYPE_END_TAG and t.name is 'head'
+ open_els.shift() # will be a head element... spec says so
+ insertion_mode = ins_mode_after_head
+ return
+ if t.type is TYPE_END_TAG and (t.name is 'body' or t.name is 'html' or t.name is 'br')
+ ins_mode_in_head_else t
+ return
+ if t.type is TYPE_START_TAG and t.name is 'template'
+ insert_html_element t
+ afe_push_marker()
+ flag_frameset_ok = false
+ insertion_mode = ins_mode_in_template
+ template_insertion_modes.unshift ins_mode_in_template # FIXME implement
+ return
+ if t.type is TYPE_END_TAG and t.name is 'template'
+ if template_tag_is_open()
+ generate_implied_end_tags
+ if open_els[0].name isnt 'template'
+ parse_error()
+ loop
+ el = open_els.shift()
+ if el.name is 'template'
+ break
+ clear_afe_to_marker()
+ template_insertion_modes.shift()
+ reset_insertion_mode()
+ else
+ parse_error()
+ return
+ if (t.type is TYPE_OPEN_TAG and t.name is 'head') or t.type is TYPE_END_TAG
+ parse_error()
+ return
+ ins_mode_in_head_else t
+
+ # 8.2.5.4.7 http://www.w3.org/TR/html5/syntax.html#parsing-main-inbody
+ in_body_any_other_end_tag = (name) -> # factored out because adoption agency calls it
+ for node, i in open_els
+ if node.name is name # FIXME check namespace too
+ generate_implied_end_tags name # arg is exception
+ parse_error() unless i is 0
+ while i >= 0
+ open_els.shift()
+ i -= 1
+ return
+ if special_elements[node.name]? # FIXME check namespac too
+ parse_error()
+ return
+ ins_mode_in_body = (t) ->
+ switch t.type
+ when TYPE_TEXT
+ switch t.text
+ when "\u0000"
+ parse_error()
+ when "\t", "\u000a", "\u000c", "\u000d", ' '
+ reconstruct_active_formatting_elements()
+ insert_character t
+ else
+ reconstruct_active_formatting_elements()
+ insert_character t
+ flag_frameset_ok = false
+ when TYPE_COMMENT
+ tree_insert_comment t
+ when TYPE_DOCTYPE
+ parse_error()
+ when TYPE_START_TAG
+ switch t.name
+ when 'html'
+ parse_error()
+ return if template_tag_is_open()
+ root_attrs = open_els[open_els.length - 1].attrs
+ for k, v of t.attrs
+ root_attrs[k] = v unless root_attrs[k]?
+ when 'base', 'basefont', 'bgsound', 'link', 'meta', 'noframes', 'script', 'style', 'template', 'title'
+ # FIXME also do this for </template> (end tag)
+ return ins_mode_in_head t
+ when 'body'
+ parse_error()
+ # TODO
+ when 'frameset'
+ parse_error()
+ # TODO
+ when 'address', 'article', 'aside', 'blockquote', 'center', 'details', 'dialog', 'dir', 'div', 'dl', 'fieldset', 'figcaption', 'figure', 'footer', 'header', 'hgroup', 'main', 'nav', 'ol', 'p', 'section', 'summary', 'ul'
+ close_p_if_in_button_scope()
+ insert_html_element t
+ when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
+ close_p_if_in_button_scope()
+ if open_els[0].name in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
+ parse_error()
+ open_els.shift()
+ insert_html_element t
+ # TODO lots more to implement here
+ when 'a'
+ # If the list of active formatting elements
+ # contains an a element between the end of the list and
+ # the last marker on the list (or the start of the list
+ # if there is no marker on the list), then this is a
+ # parse error; run the adoption agency algorithm for
+ # the tag name "a", then remove that element from the
+ # list of active formatting elements and the stack of
+ # open elements if the adoption agency algorithm didn't
+ # already remove it (it might not have if the element
+ # is not in table scope).
+ found = false
+ for el in afe
+ if el.type is TYPE_AFE_MARKER
+ break
+ if el.name is 'a'
+ found = el
+ if found?
+ parse_error()
+ adoption_agency 'a'
+ for el, i in afe
+ if el is found
+ afe.splice i, 1
+ for el, i in open_els
+ if el is found
+ open_els.splice i, 1
+ reconstruct_active_formatting_elements()
+ el = insert_html_element t
+ afe_push el
+ when 'b', 'big', 'code', 'em', 'font', 'i', 's', 'small', 'strike', 'strong', 'tt', 'u'
+ reconstruct_active_formatting_elements()
+ el = insert_html_element t
+ afe_push 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()
+ insert_html_element t
+ when TYPE_EOF
+ ok_tags = {
+ dd: true, dt: true, li: true, p: true, tbody: true, td: true,
+ tfoot: true, th: true, thead: true, tr: true, body: true, html: true,
+ }
+ for t in open_els
+ unless ok_tags[t.name]?
+ parse_error()
+ break
+ # TODO stack of template insertion modes thing
+ flag_parsing = false # stop parsing
+ when TYPE_END_TAG
+ switch t.name
+ when 'body'
+ unless is_in_scope 'body'
+ parse_error()
+ return
+ # TODO implement parse error and move to tree_after_body
+ when 'html'
+ unless is_in_scope 'body' # weird, but it's what the spec says
+ parse_error()
+ return
+ # TODO implement parse error and move to tree_after_body, reprocess
+ when 'address', 'article', 'aside', 'blockquote', 'button', 'center', 'details', 'dialog', 'dir', 'div', 'dl', 'fieldset', 'figcaption', 'figure', 'footer', 'header', 'hgroup', 'listing', 'main', 'nav', 'ol', 'pre', 'section', 'summary', 'ul'
+ unless is_in_scope t.name, NS_HTML
+ parse_error()
+ return
+ generate_implied_end_tags()
+ unless open_els[0].name is t.name and open_els[0].namespace is NS_HTML
+ parse_error()
+ loop
+ el = open_els.shift()
+ if el.name is t.name and el.namespace is NS_HTML
+ return
+ # TODO lots more close tags to implement here
+ when 'p'
+ unless is_in_button_scope 'p'
+ parse_error()
+ insert_html_element new_open_tag 'p'
+ close_p_element()
+ # TODO lots more close tags to implement here
+ when 'a', 'b', 'big', 'code', 'em', 'font', 'i', 'nobr', 's', 'small', 'strike', 'strong', 'tt', 'u'
+ adoption_agency t.name
+ # TODO lots more close tags to implement here
+ else
+ 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
+
+ # 8.2.5.4.8 http://www.w3.org/TR/html5/syntax.html#parsing-main-incdata
+ ins_mode_text = (t) ->
+ if t.type is TYPE_TEXT
+ insert_character t
+ return
+ if t.type is TYPE_EOF
+ parse_error()
+ if open_els[0].name is 'script'
+ open_els[0].flag 'already started', true
+ open_els.shift()
+ insertion_mode = original_insertion_mode
+ insertion_mode t
+ return
+ if t.type is TYPE_END_TAG and t.name is 'script'
+ open_els.shift()
+ insertion_mode = original_insertion_mode
+ # fixfull the spec seems to assume that I'm going to run the script
+ # http://www.w3.org/TR/html5/syntax.html#scriptEndTag
+ return
+ if t.type is TYPE_END_TAG
+ open_els.shift()
+ insertion_mode = original_insertion_mode
+ return
+ console.log 'warning: end of ins_mode_text reached'