JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
fix tests: serialize namespaces
[peach-html5-editor.git] / parse-html.coffee
index c5ae0e7..ca5a804 100644 (file)
@@ -92,6 +92,7 @@ class Node
                @namespace = args.namespace ? NS_HTML
                @parent = args.parent ? null
                @token = args.token ? null
+               @flags = args.flags ? {}
                if args.id?
                        @id = "#{args.id}+"
                else
@@ -101,8 +102,11 @@ class Node
                        @token.flag 'did_self_close'
                else
                        @flag 'did_self_close', true
-       flag: ->
-               # fixfull
+       flag: (key, value = null) ->
+               if value?
+                       @flags[key] = value
+               else
+                       return @flags[key]
        serialize: (shallow = false, show_ids = false) -> # for unit tests
                ret = ''
                switch @type
@@ -320,6 +324,7 @@ h_tags = {
        h1:NS_HTML, h2:NS_HTML, h3:NS_HTML, h4:NS_HTML, h5:NS_HTML, h6:NS_HTML
 }
 
+# FIXME namespacify
 foster_parenting_targets = {
        table: true
        tbody: true
@@ -328,6 +333,7 @@ foster_parenting_targets = {
        tr: true
 }
 
+# FIXME namespacify
 # all html I presume
 end_tag_implied = {
        dd: true
@@ -349,6 +355,84 @@ adp_els = { address: NS_HTML, div: NS_HTML, p: NS_HTML }
 el_is_special_not_adp = (el) ->
        return special_elements[el.name] is el.namespace and adp_els[el.name] isnt el.namespace
 
+svg_attribute_fixes = {
+       attributename: 'attributeName'
+       attributetype: 'attributeType'
+       basefrequency: 'baseFrequency'
+       baseprofile: 'baseProfile'
+       calcmode: 'calcMode'
+       clippathunits: 'clipPathUnits'
+       contentscripttype: 'contentScriptType'
+       contentstyletype: 'contentStyleType'
+       diffuseconstant: 'diffuseConstant'
+       edgemode: 'edgeMode'
+       externalresourcesrequired: 'externalResourcesRequired'
+       filterres: 'filterRes'
+       filterunits: 'filterUnits'
+       glyphref: 'glyphRef'
+       gradienttransform: 'gradientTransform'
+       gradientunits: 'gradientUnits'
+       kernelmatrix: 'kernelMatrix'
+       kernelunitlength: 'kernelUnitLength'
+       keypoints: 'keyPoints'
+       keysplines: 'keySplines'
+       keytimes: 'keyTimes'
+       lengthadjust: 'lengthAdjust'
+       limitingconeangle: 'limitingConeAngle'
+       markerheight: 'markerHeight'
+       markerunits: 'markerUnits'
+       markerwidth: 'markerWidth'
+       maskcontentunits: 'maskContentUnits'
+       maskunits: 'maskUnits'
+       numoctaves: 'numOctaves'
+       pathlength: 'pathLength'
+       patterncontentunits: 'patternContentUnits'
+       patterntransform: 'patternTransform'
+       patternunits: 'patternUnits'
+       pointsatx: 'pointsAtX'
+       pointsaty: 'pointsAtY'
+       pointsatz: 'pointsAtZ'
+       preservealpha: 'preserveAlpha'
+       preserveaspectratio: 'preserveAspectRatio'
+       primitiveunits: 'primitiveUnits'
+       refx: 'refX'
+       refy: 'refY'
+       repeatcount: 'repeatCount'
+       repeatdur: 'repeatDur'
+       requiredextensions: 'requiredExtensions'
+       requiredfeatures: 'requiredFeatures'
+       specularconstant: 'specularConstant'
+       specularexponent: 'specularExponent'
+       spreadmethod: 'spreadMethod'
+       startoffset: 'startOffset'
+       stddeviation: 'stdDeviation'
+       stitchtiles: 'stitchTiles'
+       surfacescale: 'surfaceScale'
+       systemlanguage: 'systemLanguage'
+       tablevalues: 'tableValues'
+       targetx: 'targetX'
+       targety: 'targetY'
+       textlength: 'textLength'
+       viewbox: 'viewBox'
+       viewtarget: 'viewTarget'
+       xchannelselector: 'xChannelSelector'
+       ychannelselector: 'yChannelSelector'
+       zoomandpan: 'zoomAndPan'
+}
+adjust_mathml_attributes = (t) ->
+       for a in t.attrs_a
+               if a[0] is 'definitionurl'
+                       a[0] = 'definitionURL'
+       return
+adjust_svg_attributes = (t) ->
+       for a in t.attrs_a
+               if svg_attribute_fixes[a[0]]?
+                       a[0] = svg_attribute_fixes[a[0]]
+       return
+adjust_foreign_attributes = (t) ->
+       # fixfull
+       return
+
 # decode_named_char_ref()
 #
 # The list of named character references is _huge_ so ask the browser to decode
@@ -462,6 +546,9 @@ parse_html = (txt, parse_error_cb = 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
+       # aka is_in_list_item_scope
+       is_in_li_scope = (tag_name, namespace = null) ->
+               return is_in_scope_x_y tag_name, standard_scopers, li_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)
@@ -657,7 +744,7 @@ parse_html = (txt, parse_error_cb = null) ->
        # 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 = ->
+       reconstruct_afe = ->
                return if afe.length is 0
                if afe[0].type is TYPE_AFE_MARKER or afe[0] in open_els
                        return
@@ -1117,7 +1204,8 @@ parse_html = (txt, parse_error_cb = null) ->
                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) ->
+       insert_html_element = (token) ->
+               insert_foreign_element token, NS_HTML
 
        # http://www.w3.org/TR/html5/syntax.html#insert-a-comment
        # position should be [node, index_within_children]
@@ -1417,11 +1505,11 @@ parse_html = (txt, parse_error_cb = null) ->
                        parse_error()
                        return
                if is_space_tok t
-                       reconstruct_active_formatting_elements()
+                       reconstruct_afe()
                        insert_character t
                        return
                if t.type is TYPE_TEXT
-                       reconstruct_active_formatting_elements()
+                       reconstruct_afe()
                        insert_character t
                        flag_frameset_ok = false
                        return
@@ -1540,7 +1628,7 @@ parse_html = (txt, parse_error_cb = null) ->
                        # spec: If the next token is a "LF" (U+000A) character token, then
                        # ignore that token and move on to the next one. (Newlines at the
                        # start of pre blocks are ignored as an authoring convenience.)
-                       if txt.charAt(cur) is "\u000a"
+                       if txt.charAt(cur) is "\u000a" # FIXME check for crlf?
                                cur += 1
                        flag_frameset_ok = false
                        return
@@ -1596,8 +1684,111 @@ parse_html = (txt, parse_error_cb = null) ->
                        close_p_if_in_button_scope()
                        insert_html_element t
                        return
-               # FIXME CONTINUE
-
+               if t.type is TYPE_START_TAG and t.name is 'plaintext'
+                       close_p_if_in_button_scope()
+                       insert_html_element t
+                       tok_state = tok_state_plaintext
+                       return
+               if t.type is TYPE_START_TAG and t.name is 'button'
+                       if is_in_scope 'button', NS_HTML
+                               parse_error()
+                               generate_implied_end_tags()
+                               loop
+                                       el = open_els.shift()
+                                       if el.name is 'button' and el.namespace is NS_HTML
+                                               break
+                       reconstruct_afe()
+                       insert_html_element t
+                       flag_frameset_ok = false
+                       return
+               if t.type is TYPE_END_TAG and (t.name is 'address' or t.name is 'article' or t.name is 'aside' or t.name is 'blockquote' or t.name is 'button' or t.name is 'center' or t.name is 'details' or t.name is 'dialog' or t.name is 'dir' or t.name is 'div' or t.name is 'dl' or t.name is 'fieldset' or t.name is 'figcaption' or t.name is 'figure' or t.name is 'footer' or t.name is 'header' or t.name is 'hgroup' or t.name is 'listing' or t.name is 'main' or t.name is 'nav' or t.name is 'ol' or t.name is 'pre' or t.name is 'section' or t.name is 'summary' or t.name is '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
+                       return
+               if t.type is TYPE_END_TAG and t.name is 'form'
+                       unless template_tag_is_open()
+                               node = form_element_pointer
+                               form_element_pointer = null
+                               if node is null or not el_is_in_scope node
+                                       parse_error()
+                                       return
+                               generate_implied_end_tags()
+                               if open_els[0] isnt node
+                                       parse_error()
+                               for el, i in open_els
+                                       if el is node
+                                               open_els.splice i, 1
+                                               break
+                       else
+                               unless is_in_scope 'form', NS_HTML
+                                       parse_error()
+                                       return
+                               generate_implied_end_tags()
+                               if open_els[0].name isnt 'form' or open_els[0].namespace isnt NS_HTML
+                                       parse_error()
+                               loop
+                                       el = open_els.shift()
+                                       if el.name is 'form' and el.namespace is NS_HTML
+                                               break
+                       return
+               if t.type is TYPE_END_TAG and t.name is 'p'
+                       unless is_in_button_scope 'p', NS_HTML
+                               parse_error()
+                               insert_html_element new_open_tag 'p'
+                       close_p_element()
+                       return
+               if t.type is TYPE_END_TAG and t.name is 'li'
+                       unless is_in_li_scope 'li', NS_HTML
+                               parse_error()
+                               return
+                       generate_implied_end_tags 'li' # arg is exception
+                       if open_els[0].name isnt 'li' or open_els[0].namespace isnt NS_HTML
+                               parse_error()
+                       loop
+                               el = open_els.shift()
+                               if el.name is 'li' and el.namespace is NS_HTML
+                                       break
+                       return
+               if t.type is TYPE_END_TAG and (t.name is 'dd' or t.name is 'dt')
+                       unless is_in_scope t.name, NS_HTML
+                               parse_error()
+                               return
+                       generate_implied_end_tags t.name # arg is exception
+                       if open_els[0].name isnt t.name or open_els[0].namespace isnt NS_HTML
+                               parse_error()
+                       loop
+                               el = open_els.shift()
+                               if el.name is t.name and el.namespace is NS_HTML
+                                       break
+                       return
+               if t.type is TYPE_END_TAG and h_tags[t.name]?
+                       h_in_scope = false
+                       for el in open_els
+                               if h_tags[el.name] is el.namespace
+                                       h_in_scope = true
+                                       break
+                               if standard_scopers[el.name] is el.namespace
+                                       break
+                       unless h_in_scope
+                               parse_error()
+                               return
+                       generate_implied_end_tags()
+                       if open_els[0].name isnt t.name or open_els[0].namespace isnt NS_HTML
+                               parse_error()
+                       loop
+                               el = open_els.shift()
+                               if h_tags[el.name] is el.namespace
+                                       break
+                       return
+               # deep breath!
                if t.type is TYPE_START_TAG and t.name is '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
@@ -1611,7 +1802,7 @@ parse_html = (txt, parse_error_cb = null) ->
                        for el in afe
                                if el.type is TYPE_AFE_MARKER
                                        break
-                               if el.name is 'a'
+                               if el.name is 'a' and el.namespace is NS_HTML
                                        found = el
                        if found?
                                parse_error()
@@ -1622,48 +1813,204 @@ parse_html = (txt, parse_error_cb = null) ->
                                for el, i in open_els
                                        if el is found
                                                open_els.splice i, 1
-                       reconstruct_active_formatting_elements()
+                       reconstruct_afe()
                        el = insert_html_element t
                        afe_push el
                        return
                if t.type is TYPE_START_TAG and (t.name is 'b' or t.name is 'big' or t.name is 'code' or t.name is 'em' or t.name is 'font' or t.name is 'i' or t.name is 's' or t.name is 'small' or t.name is 'strike' or t.name is 'strong' or t.name is 'tt' or t.name is 'u')
-                       reconstruct_active_formatting_elements()
+                       reconstruct_afe()
                        el = insert_html_element t
                        afe_push el
                        return
-               if t.type is TYPE_START_TAG and t.name is 'table'
-                       # fixfull quirksmode thing
-                       close_p_if_in_button_scope()
+               if t.type is TYPE_START_TAG and t.name is 'nobr'
+                       reconstruct_afe()
+                       el = insert_html_element t
+                       afe_push el
+                       return
+               if t.type is TYPE_END_TAG and (t.name is 'a' or t.name is 'b' or t.name is 'big' or t.name is 'code' or t.name is 'em' or t.name is 'font' or t.name is 'i' or t.name is 'nobr' or t.name is 's' or t.name is 'small' or t.name is 'strike' or t.name is 'strong' or t.name is 'tt' or t.name is 'u')
+                       adoption_agency t.name
+                       return
+               if t.type is TYPE_START_TAG and (t.name is 'applet' or t.name is 'marquee' or t.name is 'object')
+                       reconstruct_afe()
                        insert_html_element t
-                       ins_mode = ins_mode_in_table
+                       afe_push_marker()
+                       flag_frameset_ok = false
                        return
-               if t.type is TYPE_END_TAG and (t.name is 'address' or t.name is 'article' or t.name is 'aside' or t.name is 'blockquote' or t.name is 'button' or t.name is 'center' or t.name is 'details' or t.name is 'dialog' or t.name is 'dir' or t.name is 'div' or t.name is 'dl' or t.name is 'fieldset' or t.name is 'figcaption' or t.name is 'figure' or t.name is 'footer' or t.name is 'header' or t.name is 'hgroup' or t.name is 'listing' or t.name is 'main' or t.name is 'nav' or t.name is 'ol' or t.name is 'pre' or t.name is 'section' or t.name is 'summary' or t.name is 'ul')
+               if t.type is TYPE_END_TAG and (t.name is 'applet' or t.name is 'marquee' or t.name is 'object')
                        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
+                       if open_els[0].name isnt t.name or open_els[0].namespace isnt NS_HTML
                                parse_error()
                        loop
                                el = open_els.shift()
                                if el.name is t.name and el.namespace is NS_HTML
-                                       return
+                                       break
+                       clear_afe_to_marker()
                        return
-               if t.type is TYPE_END_TAG and t.name is 'p'
-                       unless is_in_button_scope 'p'
-                               parse_error()
-                               insert_html_element new_open_tag 'p'
-                       close_p_element()
+               if t.type is TYPE_START_TAG and t.name is 'table'
+                       close_p_if_in_button_scope() # fixfull quirksmode thing
+                       insert_html_element t
+                       flag_frameset_ok = false
+                       ins_mode = ins_mode_in_table
                        return
-               if t.type is TYPE_END_TAG and (t.name is 'a' or t.name is 'b' or t.name is 'big' or t.name is 'code' or t.name is 'em' or t.name is 'font' or t.name is 'i' or t.name is 'nobr' or t.name is 's' or t.name is 'small' or t.name is 'strike' or t.name is 'strong' or t.name is 'tt' or t.name is 'u')
-                       adoption_agency t.name
+               if t.type is TYPE_END_TAG and t.name is 'br'
+                       parse_error()
+                       t.type is TYPE_START_TAG
+                       # fall through
+               if t.type is TYPE_START_TAG and (t.name is 'area' or t.name is 'br' or t.name is 'embed' or t.name is 'img' or t.name is 'keygen' or t.name is 'wbr')
+                       reconstruct_afe()
+                       insert_html_element t
+                       open_els.shift()
+                       t.acknowledge_self_closing()
+                       flag_frameset_ok = false
+                       return
+               if t.type is TYPE_START_TAG and t.name is 'input'
+                       reconstruct_afe()
+                       insert_html_element t
+                       open_els.shift()
+                       t.acknowledge_self_closing()
+                       unless is_input_hidden_tok t
+                               flag_frameset_ok = false
+                       return
+               if t.type is TYPE_START_TAG and (t.name is 'param' or t.name is 'source' or t.name is 'track')
+                       insert_html_element t
+                       open_els.shift()
+                       t.acknowledge_self_closing()
+                       return
+               if t.type is TYPE_START_TAG and t.name is 'hr'
+                       close_p_if_in_button_scope()
+                       insert_html_element t
+                       open_els.shift()
+                       t.acknowledge_self_closing()
+                       flag_frameset_ok = false
+                       return
+               if t.type is TYPE_START_TAG and t.name is 'image'
+                       parse_error()
+                       t.name = 'img'
+                       ins_mode t
+                       return
+               if t.type is TYPE_START_TAG and t.name is 'isindex'
+                       parse_error()
+                       if template_tag_is_open() is false and form_element_pointer isnt null
+                               return
+                       t.acknowledge_self_closing()
+                       flag_frameset_ok = false
+                       close_p_if_in_button_scope()
+                       el = insert_html_element new_open_tag 'form'
+                       unless template_tag_is_open()
+                               form_element_pointer = el
+                       for a in t.attrs_a
+                               if a[0] is 'action'
+                                       el.attrs['action'] = a[1]
+                                       break
+                       insert_html_element new_open_tag 'hr'
+                       open_els.shift()
+                       reconstruct_afe()
+                       insert_html_element new_open_tag 'label'
+                       # note: this is a little out-of-spec-order so we only have to scan t.attrs_a once
+                       input_el = new_open_tag 'input'
+                       prompt = null
+                       for a in t.attrs_a
+                               if a[0] is 'prompt'
+                                       prompt = a[1]
+                               if a[0] isnt 'name' and a[0] isnt 'action' and a[0] isnt 'prompt'
+                                       input_el.attrs_a.push [a[0], a[1]]
+                       input_el.attrs_a.push ['name', 'isindex']
+                       # fixfull this next bit is in english... internationalize?
+                       prompt ?= "This is a searchable index. Enter search keywords: "
+                       insert_character prompt # fixfull split
+                       # TODO submit typo "balue" in spec
+                       insert_html_element input_el
+                       open_els.shift()
+                       # insert_character '' # you can put chars here if promt attr missing
+                       open_els.shift()
+                       insert_html_element new_open_tag 'hr'
+                       open_els.shift()
+                       open_els.shift()
+                       unless template_tag_is_open()
+                               form_element_pointer = null
+                       return
+               if t.type is TYPE_START_TAG and t.name is 'textarea'
+                       insert_html_element t
+                       if txt.charAt(cur) is "\u000a" # FIXME check for crlf?
+                               cur += 1
+                       tok_state = tok_state_rcdata
+                       original_ins_mode = ins_mode
+                       flag_frameset_ok = false
+                       ins_mode = ins_mode_text
+                       return
+               if t.type is TYPE_START_TAG and t.name is 'xmp'
+                       close_p_if_in_button_scope()
+                       reconstruct_afe()
+                       flag_frameset_ok = false
+                       parse_generic_raw_text t
+                       return
+               if t.type is TYPE_START_TAG and t.name is 'iframe'
+                       flag_frameset_ok = false
+                       parse_generic_raw_text t
+                       return
+               if t.type is TYPE_START_TAG and (t.name is 'noembed' or (t.name is 'noscript' and flag_scripting))
+                       parse_generic_raw_text t
+                       return
+               if t.type is TYPE_START_TAG and t.name is 'select'
+                       reconstruct_afe()
+                       insert_html_element t
+                       flag_frameset_ok = false
+                       if ins_mode is ins_mode_in_table or ins_mode is ins_mode_in_caption or ins_mode is ins_mode_in_table_body or ins_mode is ins_mode_in_row or ins_mode is ins_mode_in_cell
+                               ins_mode = ins_mode_in_select_in_table
+                       else
+                               ins_mode = ins_mode_in_select
+                       return
+               if t.type is TYPE_START_TAG and (t.name is 'optgroup' or t.name is 'option')
+                       if open_els[0].name is 'option' and open_els[0].namespace is NS_HTML
+                               open_els.shift()
+                       reconstruct_afe()
+                       insert_html_element t
+                       return
+               if t.type is TYPE_START_TAG and (t.name is 'rb' or t.name is 'rp' or t.name is 'rtc')
+                       if is_in_scope 'ruby', NS_HTML
+                               generate_implied_end_tags()
+                               unless open_els[0].name is 'ruby' and open_els[0].namespace is NS_HTML
+                                       parse_error()
+                       insert_html_element t
+                       return
+               if t.type is TYPE_START_TAG and t.name is 'rt'
+                       if is_in_scope 'ruby', NS_HTML
+                               generate_implied_end_tags 'rtc' # arg is exception
+                               unless (open_els[0].name is 'ruby' or open_els[0].name is 'rtc') and open_els[0].namespace is NS_HTML
+                                       parse_error()
+                       insert_html_element t
+                       return
+               if t.type is TYPE_START_TAG and t.name is 'math'
+                       reconstruct_afe()
+                       adjust_mathml_attributes t
+                       adjust_foreign_attributes t
+                       insert_foreign_element t, NS_MATHML
+                       if t.flag 'self-closing'
+                               open_els.shift()
+                               t.acknowledge_self_closing()
+                       return
+               if t.type is TYPE_START_TAG and t.name is 'svg'
+                       reconstruct_afe()
+                       adjust_svg_attributes t
+                       adjust_foreign_attributes t
+                       insert_foreign_element t, NS_SVG
+                       if t.flag 'self-closing'
+                               open_els.shift()
+                               t.acknowledge_self_closing()
+                       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 'frame' or t.name is 'head' 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
                if t.type is TYPE_START_TAG # any other start tag
-                       reconstruct_active_formatting_elements()
+                       reconstruct_afe()
                        insert_html_element t
                        return
                if t.type is TYPE_END_TAG # any other end tag
                        in_body_any_other_end_tag t.name
+                       return
                return
 
        ins_mode_in_table_else = (t) ->
@@ -3774,6 +4121,18 @@ parse_html = (txt, parse_error_cb = null) ->
                # Anything else
                return null
 
+       # 8.2.4.68 http://www.w3.org/TR/html5/syntax.html#cdata-section-state
+       tok_state_cdata_section = ->
+               tok_state = tok_state_data
+               next_gt = txt.indexOf ']]>', cur
+               if next_gt is -1
+                       val = txt.substr cur
+                       cur = txt.length
+               else
+                       val = txt.substr cur, (next_gt - cur)
+                       cur = next_gt + 3
+               val = val.replace "\u0000", "\ufffd" # fixfull spec doesn't say this
+               return new_character_token val # fixfull split
 
        # 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
@@ -3898,3 +4257,6 @@ module.exports.TYPE_TAG = TYPE_TAG
 module.exports.TYPE_TEXT = TYPE_TEXT
 module.exports.TYPE_COMMENT = TYPE_COMMENT
 module.exports.TYPE_DOCTYPE = TYPE_DOCTYPE
+module.exports.NS_HTML = NS_HTML
+module.exports.NS_MATHML = NS_MATHML
+module.exports.NS_SVG = NS_SVG