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
5package template
6
7import (
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/the-end.html#parsing-html-fragments
17// where the context element is null.
18type 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
28func (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 returns whether two contexts are equal.
33func (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.
45func (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.
78type state uint8
79
80const (
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://dev.w3.org/html5/spec/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
138var 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
165func (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.
174func 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.
183func 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.
192type delim uint8
193
194const (
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
206var delimNames = [...]string{
207	delimNone:          "delimNone",
208	delimDoubleQuote:   "delimDoubleQuote",
209	delimSingleQuote:   "delimSingleQuote",
210	delimSpaceOrTagEnd: "delimSpaceOrTagEnd",
211}
212
213func (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.
222type urlPart uint8
223
224const (
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
239var urlPartNames = [...]string{
240	urlPartNone:        "urlPartNone",
241	urlPartPreQuery:    "urlPartPreQuery",
242	urlPartQueryOrFrag: "urlPartQueryOrFrag",
243	urlPartUnknown:     "urlPartUnknown",
244}
245
246func (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.
255type jsCtx uint8
256
257const (
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
266func (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.
283type element uint8
284
285const (
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	elementScript
290	// elementStyle corresponds to the raw text <style> element.
291	elementStyle
292	// elementTextarea corresponds to the RCDATA <textarea> element.
293	elementTextarea
294	// elementTitle corresponds to the RCDATA <title> element.
295	elementTitle
296)
297
298var elementNames = [...]string{
299	elementNone:     "elementNone",
300	elementScript:   "elementScript",
301	elementStyle:    "elementStyle",
302	elementTextarea: "elementTextarea",
303	elementTitle:    "elementTitle",
304}
305
306func (e element) String() string {
307	if int(e) < len(elementNames) {
308		return elementNames[e]
309	}
310	return fmt.Sprintf("illegal element %d", int(e))
311}
312
313// attr identifies the most recent HTML attribute when inside a start tag.
314type attr uint8
315
316const (
317	// attrNone corresponds to a normal attribute or no attribute.
318	attrNone attr = iota
319	// attrScript corresponds to an event handler attribute.
320	attrScript
321	// attrStyle corresponds to the style attribute whose value is CSS.
322	attrStyle
323	// attrURL corresponds to an attribute whose value is a URL.
324	attrURL
325)
326
327var attrNames = [...]string{
328	attrNone:   "attrNone",
329	attrScript: "attrScript",
330	attrStyle:  "attrStyle",
331	attrURL:    "attrURL",
332}
333
334func (a attr) String() string {
335	if int(a) < len(attrNames) {
336		return attrNames[a]
337	}
338	return fmt.Sprintf("illegal attr %d", int(a))
339}
340