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