attrs = {}
attrs[k] = v for k, v of @attrs
return new Node @type, name: @name, text: @text, attrs: attrs, namespace: @namespace, id: @id
+ acknowledge_self_closing: ->
+ @flag 'did_self_close', true
+ flag: ->
+ # fixfull
serialize: (shallow = false, show_ids = false) -> # for unit tests
ret = ''
switch @type
ret += "##{@id},"
if shallow
break
- ret += JSON.stringify @attrs
- ret += ',['
+ attr_keys = []
+ for k of @attrs
+ attr_keys.push k
+ attr_keys.sort()
+ ret += '{'
+ sep = ''
+ for k in attr_keys
+ ret += sep
+ sep = ','
+ ret += "#{JSON.stringify k}:#{JSON.stringify @attrs[k]}"
+ ret += '},['
sep = ''
for c in @children
ret += sep
return new Node TYPE_TAG, name: name
new_text_node = (txt) ->
return new Node TYPE_TEXT, text: txt
+new_character_token = new_text_node
new_comment_node = (txt) ->
return new Node TYPE_COMMENT, text: txt
new_eof_token = ->
new_aaa_bookmark = ->
return new Node TYPE_AAA_BOOKMARK
-lc_alpha = "abcdefghijklmnopqrstuvwxqz"
-uc_alpha = "ABCDEFGHIJKLMNOPQRSTUVWXQZ"
+lc_alpha = "abcdefghijklmnopqrstuvwxyz"
+uc_alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
digits = "0123456789"
alnum = lc_alpha + uc_alpha + digits
hex_chars = digits + "abcdefABCDEF"
cur = 0 # index of next char in txt to be parsed
# declare tree and tokenizer variables so they're in scope below
tree = null
- open_els = [] # stack of open elements
+ open_els = null # stack of open elements
+ afe = null # active formatting elements
+ template_insertion_modes = null
insertion_mode = null
+ original_insertion_mode = null
tok_state = null
tok_cur_tag = null # partially parsed tag
+ flag_scripting = null
flag_frameset_ok = null
flag_parsing = null
flag_foster_parenting = null
form_element_pointer = null
- afe = [] # active formatting elements
+ temporary_buffer = null
+ pending_table_character_tokens = null
parse_error = ->
if parse_error_cb?
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
debug_log "AAA DONE"
# http://www.w3.org/TR/html5/syntax.html#close-a-p-element
- # FIXME test this (particularly emplied end tags)
close_p_element = ->
generate_implied_end_tags 'p' # arg is exception
if open_els[0].name isnt 'p'
return
close_p_if_in_button_scope = ->
if is_in_button_scope 'p'
- close_a_p_element()
+ close_p_element()
# http://www.w3.org/TR/html5/syntax.html#insert-a-character
- tree_insert_text = (t) ->
+ # aka insert_a_character = (t) ->
+ insert_character = (t) ->
dest = adjusted_insertion_location()
# fixfull check for Document node
if dest[1] > 0
# http://www.w3.org/TR/html5/syntax.html#insert-a-comment
# position should be [node, index_within_children]
- tree_insert_a_comment = (t, position = null) ->
+ 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 http://www.w3.org/TR/html5/syntax.html#parsing-main-inbody
+ # 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
+ 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
parse_error()
when "\t", "\u000a", "\u000c", "\u000d", ' '
reconstruct_active_formatting_elements()
- tree_insert_text t
+ insert_character t
else
reconstruct_active_formatting_elements()
- tree_insert_text t
+ insert_character t
flag_frameset_ok = false
when TYPE_COMMENT
- tree_insert_a_comment t
+ insert_comment t
when TYPE_DOCTYPE
parse_error()
when TYPE_START_TAG
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 tree_in_head t
+ return ins_mode_in_head t
when 'body'
parse_error()
# TODO
open_els.splice i, 1
reconstruct_active_formatting_elements()
el = insert_html_element t
- afe.unshift el
+ 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.unshift el
+ afe_push el
when 'table'
# fixfull quirksmode thing
close_p_if_in_button_scope()
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'
+
+ # the functions below implement the tokenizer stats described here:
+ # http://www.w3.org/TR/html5/syntax.html#tokenization
+
+ # 8.2.5.4.9 http://www.w3.org/TR/html5/syntax.html#parsing-main-intable
ins_mode_in_table = (t) ->
switch t.type
when TYPE_TEXT
else
ins_mode_in_table_else t
when TYPE_COMMENT
- tree_insert_a_comment t
+ insert_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()
+ afe_push_marker()
insert_html_element t
insertion_mode = ins_mode_in_caption
when 'colgroup'
ins_mode_in_table_else t
else
parse_error()
- insert_html_element t
+ el = insert_html_element t
open_els.shift()
- # fixfull acknowledge sef-closing flag
+ el.acknowledge_self_closing()
when 'form'
parse_error()
if form_element_pointer?
ins_mode_in_table_else t
+ # 8.2.5.4.10 http://www.w3.org/TR/html5/syntax.html#parsing-main-intabletext
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
+ if t.type is TYPE_TEXT and t.text is "\u0000"
+ # huh? I thought the tokenizer didn't emit these
+ parse_error()
+ return
+ if t.type is TYPE_TEXT
+ pending_table_character_tokens.push t
+ return
+ # Anything else
+ all_space = true
+ for old in pending_table_character_tokens
+ unless space_chars.indexOf(old.text) > -1
+ all_space = false
+ break
+ if all_space
+ for old in pending_table_character_tokens
+ insert_character old
+ else
+ for old in pending_table_character_tokens
+ ins_mode_table_else old
+ pending_table_character_tokens = [] # FIXME test (spec doesn't say this)
+ insertion_mode = original_insertion_mode
+ insertion_mode t
+
+ # 8.2.5.4.11 http://www.w3.org/TR/html5/syntax.html#parsing-main-incaption
+ ins_mode_in_caption = (t) ->
+ if t.type is TYPE_END_TAG and t.name is 'caption'
+ if is_in_table_scope 'caption'
+ generate_implied_end_tags()
+ if open_els[0].name isnt 'caption'
+ parse_error()
+ loop
+ el = open_els.shift()
+ if el.name is 'caption'
+ break
+ clear_afe_to_marker()
+ insertion_mode = in_table
+ else
+ parse_error()
+ # fragment case
+ 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')) or t.type is TYPE_END_TAG and t.name is 'table'
+ parse_error()
+ if is_in_table_scope 'caption'
+ loop
+ el = open_els.shift()
+ if el.name is 'caption'
+ break
+ clear_afe_to_marker()
+ insertion_mode = in_table
+ insertion_mode t
+ # else fragment case
+ return
+ if t.type is TYPE_END_TAG and (t.name is 'body' or t.name is 'col' or t.name is 'colgroup' or t.name is 'html' 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')
+ parse_error()
+ return
+ # Anything else
+ ins_mode_in_body t
+ # 8.2.5.4.12 http://www.w3.org/TR/html5/syntax.html#parsing-main-incolgroup
+ ins_mode_in_column_group = (t) ->
+ if t.type is TYPE_TEXT and space_chars.indexOf(t.text) > -1
+ insert_character t
+ return
+ if t.type is TYPE_COMMENT
+ 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 'col'
+ el = insert_html_element t
+ open_els.shift()
+ el.acknowledge_self_closing()
+ return
+ if t.type is TYPE_END_TAG and t.name is 'colgroup'
+ if open_els[0].name is 'colgroup'
+ open_els[0].shift()
+ insertion_mode = ins_mode_in_table
+ else
+ parse_error()
+ return
+ if t.type is TYPE_END_TAG and t.name is 'col'
+ parse_error()
+ return
+ if (t.type is TYPE_START_TAG or t.type is TYPE_END_TAG) and t.name is 'template'
+ ins_mode_in_head t
+ return
+ if t.type is TYPE_EOF
+ ins_mode_in_body t
+ return
+ # Anything else
+ if open_els[0].name isnt 'colgroup'
+ parse_error()
+ return
+ open_els.shift()
+ insertion_mode = ins_mode_in_table
+ insertion_mode t
+ return
+
+ # 8.2.5.4.13 http://www.w3.org/TR/html5/syntax.html#parsing-main-intbody
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
+ insertion_mode = ins_mode_in_row
return
if t.type is TYPE_START_TAG and (t.name is 'th' or t.name is 'td')
parse_error()
# Anything else
ins_mode_in_table t
+ # 8.2.5.4.14 http://www.w3.org/TR/html5/syntax.html#parsing-main-intr
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()
+ afe_push_marker()
return
if t.type is TYPE_END_TAG and t.name is 'tr'
if is_in_table_scope 'tr'
clear_afe_to_marker()
insertion_mode = ins_mode_in_row
- # http://www.w3.org/TR/html5/syntax.html#parsing-main-intd
+ # 8.2.5.4.15 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
# 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
-
# 8.2.4.1 http://www.w3.org/TR/html5/syntax.html#data-state
tok_state_data = ->
switch c = txt.charAt(cur++)
when '&'
- return new_text_node tokenize_character_reference()
+ return new_text_node parse_character_reference()
when '<'
tok_state = tok_state_tag_open
when "\u0000"
# 8.2.4.2 http://www.w3.org/TR/html5/syntax.html#character-reference-in-data-state
# not needed: tok_state_character_reference_in_data = ->
- # just call tok_state_character_reference_in_data()
+ # just call parse_character_reference()
+
+ # 8.2.4.3 http://www.w3.org/TR/html5/syntax.html#rcdata-state
+ tok_state_rcdata = ->
+ switch c = txt.charAt(cur++)
+ when '&'
+ return new_text_node parse_character_reference()
+ when '<'
+ tok_state = tok_state_rcdata_less_than_sign
+ when "\u0000"
+ parse_error()
+ return new_character_token "\ufffd"
+ when '' # EOF
+ return new_eof_token()
+ else
+ return new_character_token c
+ return null
+
+ # 8.2.4.4 http://www.w3.org/TR/html5/syntax.html#character-reference-in-rcdata-state
+ # not needed: tok_state_character_reference_in_rcdata = ->
+ # just call parse_character_reference()
+
+ # 8.2.4.5 http://www.w3.org/TR/html5/syntax.html#rawtext-state
+ tok_state_rawtext = ->
+ switch c = txt.charAt(cur++)
+ when '<'
+ tok_state = tok_state_rawtext_less_than_sign
+ when "\u0000"
+ parse_error()
+ return new_character_token "\ufffd"
+ when '' # EOF
+ return new_eof_token()
+ else
+ return new_character_token c
+ return null
+
+ # 8.2.4.6 http://www.w3.org/TR/html5/syntax.html#script-data-state
+ tok_state_script_data = ->
+ switch c = txt.charAt(cur++)
+ when '<'
+ tok_state = tok_state_script_data_less_than_sign
+ when "\u0000"
+ parse_error()
+ return new_character_token "\ufffd"
+ when '' # EOF
+ return new_eof_token()
+ else
+ return new_character_token c
+ return null
+
+ # 8.2.4.7 http://www.w3.org/TR/html5/syntax.html#plaintext-state
+ tok_state_plaintext = ->
+ switch c = txt.charAt(cur++)
+ when "\u0000"
+ parse_error()
+ return new_character_token "\ufffd"
+ when '' # EOF
+ return new_eof_token()
+ else
+ return new_character_token c
+ return null
+
# 8.2.4.8 http://www.w3.org/TR/html5/syntax.html#tag-open-state
tok_state_tag_open = ->
tok_cur_tag.name += c
return null
+ # 8.2.4.11 http://www.w3.org/TR/html5/syntax.html#rcdata-less-than-sign-state
+ tok_state_rcdata_less_than_sign = ->
+ c = txt.charAt(cur++)
+ if c is '/'
+ temporary_buffer = ''
+ tok_state = tok_state_rcdata_end_tag_open
+ return null
+ # Anything else
+ tok_state = tok_state_rcdata
+ cur -= 1 # reconsume the input character
+ return new_character_token '<'
+
+ # 8.2.4.12 http://www.w3.org/TR/html5/syntax.html#rcdata-end-tag-open-state
+ tok_state_rcdata_end_tag_open = ->
+ c = txt.charAt(cur++)
+ if uc_alpha.indexOf(c) > -1
+ tok_cur_tag = new_end_tag c.toLowerCase()
+ temporary_buffer += c
+ tok_state = tok_state_rcdata_end_tag_name
+ return null
+ if lc_alpha.indexOf(c) > -1
+ tok_cur_tag = new_end_tag c
+ temporary_buffer += c
+ tok_state = tok_state_rcdata_end_tag_name
+ return null
+ # Anything else
+ tok_state = tok_state_rcdata
+ cur -= 1 # reconsume the input character
+ return new_character_token "</" # fixfull separate these
+
+ # http://www.w3.org/TR/html5/syntax.html#appropriate-end-tag-token
+ is_appropriate_end_tag = (t) ->
+ # spec says to check against "the tag name of the last start tag to
+ # have been emitted from this tokenizer", but this is only called from
+ # the various "raw" states, which I'm pretty sure all push the start
+ # token onto open_els. TODO: verify this after the script data states
+ # are implemented
+ debug_log "#{t.type}, #{t.name} open_els: #{serialize_els open_els, true, true}"
+ return t.type is TYPE_END_TAG and t.name is open_els[0].name
+
+ # 8.2.4.13 http://www.w3.org/TR/html5/syntax.html#rcdata-end-tag-name-state
+ tok_state_rcdata_end_tag_name = ->
+ c = txt.charAt(cur++)
+ if c is "\t" or c is "\n" or c is "\u000c" or c is ' '
+ if is_appropriate_end_tag tok_cur_tag
+ tok_state = tok_state_before_attribute_name
+ return
+ # else fall through to "Anything else"
+ if c is '/'
+ if is_appropriate_end_tag tok_cur_tag
+ tok_state = tok_state_self_closing_start_tag # FIXME spec typo?
+ return
+ # else fall through to "Anything else"
+ if c is '>'
+ if is_appropriate_end_tag tok_cur_tag
+ tok_state = tok_state_data
+ return tok_cur_tag
+ # else fall through to "Anything else"
+ if uc_alpha.indexOf(c) > -1
+ tok_cur_tag.name += c.toLowerCase()
+ temporary_buffer += c
+ return null
+ if lc_alpha.indexOf(c) > -1
+ tok_cur_tag.name += c
+ temporary_buffer += c
+ return null
+ # Anything else
+ tok_state = tok_state_rcdata
+ cur -= 1 # reconsume the input character
+ return new_character_token '</' + temporary_buffer # fixfull separate these
+
+ # 8.2.4.14 http://www.w3.org/TR/html5/syntax.html#rawtext-less-than-sign-state
+ tok_state_rawtext_less_than_sign = ->
+ c = txt.charAt(cur++)
+ if c is '/'
+ temporary_buffer = ''
+ tok_state = tok_state_rawtext_end_tag_open
+ return null
+ # Anything else
+ tok_state = tok_state_rawtext
+ cur -= 1 # reconsume the input character
+ return new_character_token '<'
+
+ # 8.2.4.15 http://www.w3.org/TR/html5/syntax.html#rawtext-end-tag-open-state
+ tok_state_rawtext_end_tag_open = ->
+ c = txt.charAt(cur++)
+ if uc_alpha.indexOf(c) > -1
+ tok_cur_tag = new_end_tag c.toLowerCase()
+ temporary_buffer += c
+ tok_state = tok_state_rawtext_end_tag_name
+ return null
+ if lc_alpha.indexOf(c) > -1
+ tok_cur_tag = new_end_tag c
+ temporary_buffer += c
+ tok_state = tok_state_rawtext_end_tag_name
+ return null
+ # Anything else
+ tok_state = tok_state_rawtext
+ cur -= 1 # reconsume the input character
+ return new_character_token "</" # fixfull separate these
+
+ # 8.2.4.16 http://www.w3.org/TR/html5/syntax.html#rawtext-end-tag-name-state
+ tok_state_rawtext_end_tag_name = ->
+ c = txt.charAt(cur++)
+ if c is "\t" or c is "\n" or c is "\u000c" or c is ' '
+ if is_appropriate_end_tag tok_cur_tag
+ tok_state = tok_state_before_attribute_name
+ return
+ # else fall through to "Anything else"
+ if c is '/'
+ if is_appropriate_end_tag tok_cur_tag
+ tok_state = tok_state_self_closing_start_tag
+ return
+ # else fall through to "Anything else"
+ if c is '>'
+ if is_appropriate_end_tag tok_cur_tag
+ tok_state = tok_state_data
+ return tok_cur_tag
+ # else fall through to "Anything else"
+ if uc_alpha.indexOf(c) > -1
+ tok_cur_tag.name += c.toLowerCase()
+ temporary_buffer += c
+ return null
+ if lc_alpha.indexOf(c) > -1
+ tok_cur_tag.name += c
+ temporary_buffer += c
+ return null
+ # Anything else
+ tok_state = tok_state_rawtext
+ cur -= 1 # reconsume the input character
+ return new_character_token '</' + temporary_buffer # fixfull separate these
+
+ # TODO _all_ of the missing states here (17-33) are for parsing script tags
+
# 8.2.4.34 http://www.w3.org/TR/html5/syntax.html#before-attribute-name-state
tok_state_before_attribute_name = ->
attr_name = null
tok_cur_tag.attrs_a[0][0] += c
return null
+ # 8.2.4.36 http://www.w3.org/TR/html5/syntax.html#after-attribute-name-state
+ tok_state_after_attribute_name = ->
+ c = txt.charAt(cur++)
+ if c is "\t" or c is "\n" or c is "\u000c" or c is ' '
+ return
+ if c is '/'
+ tok_state = tok_state_self_closing_start_tag
+ return
+ if c is '='
+ tok_state = tok_state_before_attribute_value
+ return
+ if c is '>'
+ tok_state = tok_state_data
+ return
+ if uc_alpha.indexOf(c) > -1
+ tok_cur_tag.attrs_a.unshift [c.toLowerCase(), '']
+ tok_state = tok_state_attribute_name
+ return
+ if c is "\u0000"
+ parse_error()
+ tok_cur_tag.attrs_a.unshift ["\ufffd", '']
+ tok_state = tok_state_attribute_name
+ return
+ if c is '' # EOF
+ parse_error()
+ tok_state = tok_state_data
+ cur -= 1 # reconsume
+ return
+ if c is '"' or c is "'" or c is '<'
+ parse_error()
+ # fall through to Anything else
+ # Anything else
+ tok_cur_tag.attrs_a.unshift [c, '']
+ tok_state = tok_state_attribute_name
+
# 8.2.4.37 http://www.w3.org/TR/html5/syntax.html#before-attribute-value-state
tok_state_before_attribute_value = ->
switch c = txt.charAt(cur++)
when '"'
tok_state = tok_state_after_attribute_value_quoted
when '&'
- tok_cur_tag.attrs_a[0][1] += tokenize_character_reference '"', true
+ tok_cur_tag.attrs_a[0][1] += parse_character_reference '"', true
when "\u0000"
# Parse error
tok_cur_tag.attrs_a[0][1] += "\ufffd"
when "'"
tok_state = tok_state_after_attribute_value_quoted
when '&'
- tok_cur_tag.attrs_a[0][1] += tokenize_character_reference "'", true
+ tok_cur_tag.attrs_a[0][1] += parse_character_reference "'", true
when "\u0000"
# Parse error
tok_cur_tag.attrs_a[0][1] += "\ufffd"
when "\t", "\n", "\u000c", ' '
tok_state = tok_state_before_attribute_name
when '&'
- tok_cur_tag.attrs_a[0][1] += tokenize_character_reference '>', true
+ tok_cur_tag.attrs_a[0][1] += parse_character_reference '>', true
when '>'
tok_state = tok_state_data
tmp = tok_cur_tag
# 8.2.4.69 http://www.w3.org/TR/html5/syntax.html#consume-a-character-reference
# Don't set this as a state, just call it
# returns a string (NOT a text node)
- tokenize_character_reference = (allowed_char = null, in_attr = false) ->
+ parse_character_reference = (allowed_char = null, in_attr = false) ->
if cur >= txt.length
return '&'
switch c = txt.charAt(cur)
# 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]
+ afe = [] # active formatting elements
+ template_insertion_modes = []
insertion_mode = ins_mode_in_body
+ original_insertion_mode = insertion_mode # TODO check spec
+ flag_scripting = true # TODO might need an extra flag to get <noscript> to parse correctly
flag_frameset_ok = true
flag_parsing = true
flag_foster_parenting = false
form_element_pointer = null
- afe = [] # active formatting elements
+ temporary_buffer = null
+ pending_table_character_tokens = []
# tokenizer initialization
tok_state = tok_state_data
expected: 'text:"foo",tag:"span",{"style":"foo: bar","title":"hi"},[text:"bar"]'
test_parser name: "open tag with attributes of various quotings", \
html: "foo<span abc=\"def\" g=hij klm='nopqrstuv\"' autofocus>bar",
- expected: 'text:"foo",tag:"span",{"abc":"def","g":"hij","klm":"nopqrstuv\\"","autofocus":""},[text:"bar"]'
+ expected: 'text:"foo",tag:"span",{"abc":"def","autofocus":"","g":"hij","klm":"nopqrstuv\\""},[text:"bar"]'
test_parser name: "attribute entity exceptions dq", \
html: "foo<a href=\"foo?t=1&=2&o=3&lt=foo\">bar",
expected: 'text:"foo",tag:"a",{"href":"foo?t=1&=2&o=3<=foo"},[text:"bar"]'
test_parser name: "html5lib aaa 15 (deep ?inner aaa)", \
html: '<div><a><b><u><i><code><div></a>',
expected: 'tag:"div",{},[tag:"a",{},[tag:"b",{},[tag:"u",{},[tag:"i",{},[tag:"code",{},[]]]]],tag:"u",{},[tag:"i",{},[tag:"code",{},[tag:"div",{},[tag:"a",{},[]]]]]]'
+test_parser name: "html5lib aaa 16 (correctly nested 4b)", \
+ html: '<b><b><b><b>x</b></b></b></b>y',
+ expected: 'tag:"b",{},[tag:"b",{},[tag:"b",{},[tag:"b",{},[text:"x"]]]],text:"y"'
+test_parser name: "html5lib aaa 17 (formatting, implied /p, noah's ark)", \
+ html: '<p><b><b><b><b><p>x',
+ expected: 'tag:"p",{},[tag:"b",{},[tag:"b",{},[tag:"b",{},[tag:"b",{},[]]]]],tag:"p",{},[tag:"b",{},[tag:"b",{},[tag:"b",{},[text:"x"]]]]'
+test_parser name: "variation on html5lib aaa 17 (with attributes in various orders)", \
+ html: '<p><b c="d" e="f"><b e="f" c="d"><b e="f" c="d"><b c="d" e="f"><p>x',
+ expected: 'tag:"p",{},[tag:"b",{"c":"d","e":"f"},[tag:"b",{"c":"d","e":"f"},[tag:"b",{"c":"d","e":"f"},[tag:"b",{"c":"d","e":"f"},[]]]]],tag:"p",{},[tag:"b",{"c":"d","e":"f"},[tag:"b",{"c":"d","e":"f"},[tag:"b",{"c":"d","e":"f"},[text:"x"]]]]'
+test_parser name: "junk after attribute close-quote", \
+ html: '<p><b c="d", e="f">foo<p>x',
+ expected: 'tag:"p",{},[tag:"b",{",":"","c":"d","e":"f"},[text:"foo"]],tag:"p",{},[tag:"b",{",":"","c":"d","e":"f"},[text:"x"]]'
+test_parser name: "html5lib aaa02 1", \
+ html: '<b>1<i>2<p>3</b>4',
+ expected: 'tag:"b",{},[text:"1",tag:"i",{},[text:"2"]],tag:"i",{},[tag:"p",{},[tag:"b",{},[text:"3"],text:"4"]]'
+test_parser name: "html5lib aaa02 2", \
+ html: '<a><div><style></style><address><a>',
+ expected: 'tag:"a",{},[],tag:"div",{},[tag:"a",{},[tag:"style",{},[]],tag:"address",{},[tag:"a",{},[],tag:"a",{},[]]]'
+test_parser name: "html5lib tables 1", \
+ html: '<table><th>',
+ expected: 'tag:"table",{},[tag:"tbody",{},[tag:"tr",{},[tag:"th",{},[]]]]'
+test_parser name: "html5lib tables 2", \
+ html: '<table><td>',
+ expected: 'tag:"table",{},[tag:"tbody",{},[tag:"tr",{},[tag:"td",{},[]]]]'
+test_parser name: "html5lib tables 3", \
+ html: "<table><col foo='bar'>",
+ expected: 'tag:"table",{},[tag:"colgroup",{},[tag:"col",{"foo":"bar"},[]]]'
+test_parser name: "html5lib tables 4", \
+ html: '<table><colgroup></html>foo',
+ expected: 'text:"foo",tag:"table",{},[tag:"colgroup",{},[]]'
+test_parser name: "html5lib tables 5", \
+ html: '<table></table><p>foo',
+ expected: 'tag:"table",{},[],tag:"p",{},[text:"foo"]'
+test_parser name: "html5lib tables 6", \
+ html: '<table></body></caption></col></colgroup></html></tbody></td></tfoot></th></thead></tr><td>',
+ expected: 'tag:"table",{},[tag:"tbody",{},[tag:"tr",{},[tag:"td",{},[]]]]'
+test_parser name: "html5lib tables 7", \
+ html: '<table><select><option>3</select></table>',
+ expected: 'tag:"select",{},[tag:"option",{},[text:"3"]],tag:"table",{},[]'
+test_parser name: "html5lib tables 8", \
+ html: '<table><select><table></table></select></table>',
+ expected: 'tag:"select",{},[],tag:"table",{},[],tag:"table",{},[]'
+test_parser name: "html5lib tables 9", \
+ html: '<table><select></table>',
+ expected: 'tag:"select",{},[],tag:"table",{},[]'
+test_parser name: "html5lib tables 10", \
+ html: '<table><select><option>A<tr><td>B</td></tr></table>',
+ expected: 'tag:"select",{},[tag:"option",{},[text:"A"]],tag:"table",{},[tag:"tbody",{},[tag:"tr",{},[tag:"td",{},[text:"B"]]]]'
+test_parser name: "html5lib tables 11", \
+ html: '<table><td></body></caption></col></colgroup></html>foo',
+ expected: 'tag:"table",{},[tag:"tbody",{},[tag:"tr",{},[tag:"td",{},[text:"foo"]]]]'
+test_parser name: "html5lib tables 12", \
+ html: '<table><td>A</table>B',
+ expected: 'tag:"table",{},[tag:"tbody",{},[tag:"tr",{},[tag:"td",{},[text:"A"]]]],text:"B"'
+test_parser name: "html5lib tables 13", \
+ html: '<table><tr><caption>',
+ expected: 'tag:"table",{},[tag:"tbody",{},[tag:"tr",{},[]],tag:"caption",{},[]]'
+test_parser name: "html5lib tables 14", \
+ html: '<table><tr></body></caption></col></colgroup></html></td></th><td>foo',
+ expected: 'tag:"table",{},[tag:"tbody",{},[tag:"tr",{},[tag:"td",{},[text:"foo"]]]]'
+test_parser name: "html5lib tables 15", \
+ html: '<table><td><tr>',
+ expected: 'tag:"table",{},[tag:"tbody",{},[tag:"tr",{},[tag:"td",{},[]],tag:"tr",{},[]]]'
+test_parser name: "html5lib tables 16", \
+ html: '<table><td><button><td>',
+ expected: 'tag:"table",{},[tag:"tbody",{},[tag:"tr",{},[tag:"td",{},[tag:"button",{},[]],tag:"td",{},[]]]]'
+# TODO implement svg parsing
+#test_parser name: "html5lib tables 17", \
+# html: '<table><tr><td><svg><desc><td>',
+# expected: 'tag:"table",{},[tag:"tbody",{},[tag:"tr",{},[tag:"td",{},[svg:"svg",{},[svg:"desc",{},[]]],tag:"td",{},[]]]]'