Home | History | Annotate | Download | only in template
      1 // Copyright 2011 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package template
      6 
      7 import (
      8 	"fmt"
      9 )
     10 
     11 // context describes the state an HTML parser must be in when it reaches the
     12 // portion of HTML produced by evaluating a particular template node.
     13 //
     14 // The zero value of type context is the start context for a template that
     15 // produces an HTML fragment as defined at
     16 // http://www.w3.org/TR/html5/syntax.html#the-end
     17 // where the context element is null.
     18 type context struct {
     19 	state   state
     20 	delim   delim
     21 	urlPart urlPart
     22 	jsCtx   jsCtx
     23 	attr    attr
     24 	element element
     25 	err     *Error
     26 }
     27 
     28 func (c context) String() string {
     29 	return fmt.Sprintf("{%v %v %v %v %v %v %v}", c.state, c.delim, c.urlPart, c.jsCtx, c.attr, c.element, c.err)
     30 }
     31 
     32 // eq reports whether two contexts are equal.
     33 func (c context) eq(d context) bool {
     34 	return c.state == d.state &&
     35 		c.delim == d.delim &&
     36 		c.urlPart == d.urlPart &&
     37 		c.jsCtx == d.jsCtx &&
     38 		c.attr == d.attr &&
     39 		c.element == d.element &&
     40 		c.err == d.err
     41 }
     42 
     43 // mangle produces an identifier that includes a suffix that distinguishes it
     44 // from template names mangled with different contexts.
     45 func (c context) mangle(templateName string) string {
     46 	// The mangled name for the default context is the input templateName.
     47 	if c.state == stateText {
     48 		return templateName
     49 	}
     50 	s := templateName + "$htmltemplate_" + c.state.String()
     51 	if c.delim != 0 {
     52 		s += "_" + c.delim.String()
     53 	}
     54 	if c.urlPart != 0 {
     55 		s += "_" + c.urlPart.String()
     56 	}
     57 	if c.jsCtx != 0 {
     58 		s += "_" + c.jsCtx.String()
     59 	}
     60 	if c.attr != 0 {
     61 		s += "_" + c.attr.String()
     62 	}
     63 	if c.element != 0 {
     64 		s += "_" + c.element.String()
     65 	}
     66 	return s
     67 }
     68 
     69 // state describes a high-level HTML parser state.
     70 //
     71 // It bounds the top of the element stack, and by extension the HTML insertion
     72 // mode, but also contains state that does not correspond to anything in the
     73 // HTML5 parsing algorithm because a single token production in the HTML
     74 // grammar may contain embedded actions in a template. For instance, the quoted
     75 // HTML attribute produced by
     76 //     <div title="Hello {{.World}}">
     77 // is a single token in HTML's grammar but in a template spans several nodes.
     78 type state uint8
     79 
     80 const (
     81 	// stateText is parsed character data. An HTML parser is in
     82 	// this state when its parse position is outside an HTML tag,
     83 	// directive, comment, and special element body.
     84 	stateText state = iota
     85 	// stateTag occurs before an HTML attribute or the end of a tag.
     86 	stateTag
     87 	// stateAttrName occurs inside an attribute name.
     88 	// It occurs between the ^'s in ` ^name^ = value`.
     89 	stateAttrName
     90 	// stateAfterName occurs after an attr name has ended but before any
     91 	// equals sign. It occurs between the ^'s in ` name^ ^= value`.
     92 	stateAfterName
     93 	// stateBeforeValue occurs after the equals sign but before the value.
     94 	// It occurs between the ^'s in ` name =^ ^value`.
     95 	stateBeforeValue
     96 	// stateHTMLCmt occurs inside an <!-- HTML comment -->.
     97 	stateHTMLCmt
     98 	// stateRCDATA occurs inside an RCDATA element (<textarea> or <title>)
     99 	// as described at http://www.w3.org/TR/html5/syntax.html#elements-0
    100 	stateRCDATA
    101 	// stateAttr occurs inside an HTML attribute whose content is text.
    102 	stateAttr
    103 	// stateURL occurs inside an HTML attribute whose content is a URL.
    104 	stateURL
    105 	// stateJS occurs inside an event handler or script element.
    106 	stateJS
    107 	// stateJSDqStr occurs inside a JavaScript double quoted string.
    108 	stateJSDqStr
    109 	// stateJSSqStr occurs inside a JavaScript single quoted string.
    110 	stateJSSqStr
    111 	// stateJSRegexp occurs inside a JavaScript regexp literal.
    112 	stateJSRegexp
    113 	// stateJSBlockCmt occurs inside a JavaScript /* block comment */.
    114 	stateJSBlockCmt
    115 	// stateJSLineCmt occurs inside a JavaScript // line comment.
    116 	stateJSLineCmt
    117 	// stateCSS occurs inside a <style> element or style attribute.
    118 	stateCSS
    119 	// stateCSSDqStr occurs inside a CSS double quoted string.
    120 	stateCSSDqStr
    121 	// stateCSSSqStr occurs inside a CSS single quoted string.
    122 	stateCSSSqStr
    123 	// stateCSSDqURL occurs inside a CSS double quoted url("...").
    124 	stateCSSDqURL
    125 	// stateCSSSqURL occurs inside a CSS single quoted url('...').
    126 	stateCSSSqURL
    127 	// stateCSSURL occurs inside a CSS unquoted url(...).
    128 	stateCSSURL
    129 	// stateCSSBlockCmt occurs inside a CSS /* block comment */.
    130 	stateCSSBlockCmt
    131 	// stateCSSLineCmt occurs inside a CSS // line comment.
    132 	stateCSSLineCmt
    133 	// stateError is an infectious error state outside any valid
    134 	// HTML/CSS/JS construct.
    135 	stateError
    136 )
    137 
    138 var stateNames = [...]string{
    139 	stateText:        "stateText",
    140 	stateTag:         "stateTag",
    141 	stateAttrName:    "stateAttrName",
    142 	stateAfterName:   "stateAfterName",
    143 	stateBeforeValue: "stateBeforeValue",
    144 	stateHTMLCmt:     "stateHTMLCmt",
    145 	stateRCDATA:      "stateRCDATA",
    146 	stateAttr:        "stateAttr",
    147 	stateURL:         "stateURL",
    148 	stateJS:          "stateJS",
    149 	stateJSDqStr:     "stateJSDqStr",
    150 	stateJSSqStr:     "stateJSSqStr",
    151 	stateJSRegexp:    "stateJSRegexp",
    152 	stateJSBlockCmt:  "stateJSBlockCmt",
    153 	stateJSLineCmt:   "stateJSLineCmt",
    154 	stateCSS:         "stateCSS",
    155 	stateCSSDqStr:    "stateCSSDqStr",
    156 	stateCSSSqStr:    "stateCSSSqStr",
    157 	stateCSSDqURL:    "stateCSSDqURL",
    158 	stateCSSSqURL:    "stateCSSSqURL",
    159 	stateCSSURL:      "stateCSSURL",
    160 	stateCSSBlockCmt: "stateCSSBlockCmt",
    161 	stateCSSLineCmt:  "stateCSSLineCmt",
    162 	stateError:       "stateError",
    163 }
    164 
    165 func (s state) String() string {
    166 	if int(s) < len(stateNames) {
    167 		return stateNames[s]
    168 	}
    169 	return fmt.Sprintf("illegal state %d", int(s))
    170 }
    171 
    172 // isComment is true for any state that contains content meant for template
    173 // authors & maintainers, not for end-users or machines.
    174 func isComment(s state) bool {
    175 	switch s {
    176 	case stateHTMLCmt, stateJSBlockCmt, stateJSLineCmt, stateCSSBlockCmt, stateCSSLineCmt:
    177 		return true
    178 	}
    179 	return false
    180 }
    181 
    182 // isInTag return whether s occurs solely inside an HTML tag.
    183 func isInTag(s state) bool {
    184 	switch s {
    185 	case stateTag, stateAttrName, stateAfterName, stateBeforeValue, stateAttr:
    186 		return true
    187 	}
    188 	return false
    189 }
    190 
    191 // delim is the delimiter that will end the current HTML attribute.
    192 type delim uint8
    193 
    194 const (
    195 	// delimNone occurs outside any attribute.
    196 	delimNone delim = iota
    197 	// delimDoubleQuote occurs when a double quote (") closes the attribute.
    198 	delimDoubleQuote
    199 	// delimSingleQuote occurs when a single quote (') closes the attribute.
    200 	delimSingleQuote
    201 	// delimSpaceOrTagEnd occurs when a space or right angle bracket (>)
    202 	// closes the attribute.
    203 	delimSpaceOrTagEnd
    204 )
    205 
    206 var delimNames = [...]string{
    207 	delimNone:          "delimNone",
    208 	delimDoubleQuote:   "delimDoubleQuote",
    209 	delimSingleQuote:   "delimSingleQuote",
    210 	delimSpaceOrTagEnd: "delimSpaceOrTagEnd",
    211 }
    212 
    213 func (d delim) String() string {
    214 	if int(d) < len(delimNames) {
    215 		return delimNames[d]
    216 	}
    217 	return fmt.Sprintf("illegal delim %d", int(d))
    218 }
    219 
    220 // urlPart identifies a part in an RFC 3986 hierarchical URL to allow different
    221 // encoding strategies.
    222 type urlPart uint8
    223 
    224 const (
    225 	// urlPartNone occurs when not in a URL, or possibly at the start:
    226 	// ^ in "^http://auth/path?k=v#frag".
    227 	urlPartNone urlPart = iota
    228 	// urlPartPreQuery occurs in the scheme, authority, or path; between the
    229 	// ^s in "h^ttp://auth/path^?k=v#frag".
    230 	urlPartPreQuery
    231 	// urlPartQueryOrFrag occurs in the query portion between the ^s in
    232 	// "http://auth/path?^k=v#frag^".
    233 	urlPartQueryOrFrag
    234 	// urlPartUnknown occurs due to joining of contexts both before and
    235 	// after the query separator.
    236 	urlPartUnknown
    237 )
    238 
    239 var urlPartNames = [...]string{
    240 	urlPartNone:        "urlPartNone",
    241 	urlPartPreQuery:    "urlPartPreQuery",
    242 	urlPartQueryOrFrag: "urlPartQueryOrFrag",
    243 	urlPartUnknown:     "urlPartUnknown",
    244 }
    245 
    246 func (u urlPart) String() string {
    247 	if int(u) < len(urlPartNames) {
    248 		return urlPartNames[u]
    249 	}
    250 	return fmt.Sprintf("illegal urlPart %d", int(u))
    251 }
    252 
    253 // jsCtx determines whether a '/' starts a regular expression literal or a
    254 // division operator.
    255 type jsCtx uint8
    256 
    257 const (
    258 	// jsCtxRegexp occurs where a '/' would start a regexp literal.
    259 	jsCtxRegexp jsCtx = iota
    260 	// jsCtxDivOp occurs where a '/' would start a division operator.
    261 	jsCtxDivOp
    262 	// jsCtxUnknown occurs where a '/' is ambiguous due to context joining.
    263 	jsCtxUnknown
    264 )
    265 
    266 func (c jsCtx) String() string {
    267 	switch c {
    268 	case jsCtxRegexp:
    269 		return "jsCtxRegexp"
    270 	case jsCtxDivOp:
    271 		return "jsCtxDivOp"
    272 	case jsCtxUnknown:
    273 		return "jsCtxUnknown"
    274 	}
    275 	return fmt.Sprintf("illegal jsCtx %d", int(c))
    276 }
    277 
    278 // element identifies the HTML element when inside a start tag or special body.
    279 // Certain HTML element (for example <script> and <style>) have bodies that are
    280 // treated differently from stateText so the element type is necessary to
    281 // transition into the correct context at the end of a tag and to identify the
    282 // end delimiter for the body.
    283 type element uint8
    284 
    285 const (
    286 	// elementNone occurs outside a special tag or special element body.
    287 	elementNone element = iota
    288 	// elementScript corresponds to the raw text <script> element
    289 	// with JS MIME type or no type attribute.
    290 	elementScript
    291 	// elementStyle corresponds to the raw text <style> element.
    292 	elementStyle
    293 	// elementTextarea corresponds to the RCDATA <textarea> element.
    294 	elementTextarea
    295 	// elementTitle corresponds to the RCDATA <title> element.
    296 	elementTitle
    297 )
    298 
    299 var elementNames = [...]string{
    300 	elementNone:     "elementNone",
    301 	elementScript:   "elementScript",
    302 	elementStyle:    "elementStyle",
    303 	elementTextarea: "elementTextarea",
    304 	elementTitle:    "elementTitle",
    305 }
    306 
    307 func (e element) String() string {
    308 	if int(e) < len(elementNames) {
    309 		return elementNames[e]
    310 	}
    311 	return fmt.Sprintf("illegal element %d", int(e))
    312 }
    313 
    314 // attr identifies the current HTML attribute when inside the attribute,
    315 // that is, starting from stateAttrName until stateTag/stateText (exclusive).
    316 type attr uint8
    317 
    318 const (
    319 	// attrNone corresponds to a normal attribute or no attribute.
    320 	attrNone attr = iota
    321 	// attrScript corresponds to an event handler attribute.
    322 	attrScript
    323 	// attrScriptType corresponds to the type attribute in script HTML element
    324 	attrScriptType
    325 	// attrStyle corresponds to the style attribute whose value is CSS.
    326 	attrStyle
    327 	// attrURL corresponds to an attribute whose value is a URL.
    328 	attrURL
    329 )
    330 
    331 var attrNames = [...]string{
    332 	attrNone:       "attrNone",
    333 	attrScript:     "attrScript",
    334 	attrScriptType: "attrScriptType",
    335 	attrStyle:      "attrStyle",
    336 	attrURL:        "attrURL",
    337 }
    338 
    339 func (a attr) String() string {
    340 	if int(a) < len(attrNames) {
    341 		return attrNames[a]
    342 	}
    343 	return fmt.Sprintf("illegal attr %d", int(a))
    344 }
    345