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