Source file src/pkg/html/template/context.go
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/the-end.html#parsing-html-fragments
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 returns 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://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
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 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
298 var elementNames = [...]string{
299 elementNone: "elementNone",
300 elementScript: "elementScript",
301 elementStyle: "elementStyle",
302 elementTextarea: "elementTextarea",
303 elementTitle: "elementTitle",
304 }
305
306 func (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.
314 type attr uint8
315
316 const (
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
327 var attrNames = [...]string{
328 attrNone: "attrNone",
329 attrScript: "attrScript",
330 attrStyle: "attrStyle",
331 attrURL: "attrURL",
332 }
333
334 func (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 }