| // Copyright 2011 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package template | |
| import ( | |
| "fmt" | |
| "text/template/parse" | |
| ) | |
| // context describes the state an HTML parser must be in when it reaches the | |
| // portion of HTML produced by evaluating a particular template node. | |
| // | |
| // The zero value of type context is the start context for a template that | |
| // produces an HTML fragment as defined at | |
| // https://www.w3.org/TR/html5/syntax.html#the-end | |
| // where the context element is null. | |
| type context struct { | |
| state state | |
| delim delim | |
| urlPart urlPart | |
| jsCtx jsCtx | |
| // jsBraceDepth contains the current depth, for each JS template literal | |
| // string interpolation expression, of braces we've seen. This is used to | |
| // determine if the next } will close a JS template literal string | |
| // interpolation expression or not. | |
| jsBraceDepth []int | |
| attr attr | |
| element element | |
| n parse.Node // for range break/continue | |
| err *Error | |
| } | |
| func (c context) String() string { | |
| var err error | |
| if c.err != nil { | |
| err = c.err | |
| } | |
| return fmt.Sprintf("{%v %v %v %v %v %v %v}", c.state, c.delim, c.urlPart, c.jsCtx, c.attr, c.element, err) | |
| } | |
| // eq reports whether two contexts are equal. | |
| func (c context) eq(d context) bool { | |
| return c.state == d.state && | |
| c.delim == d.delim && | |
| c.urlPart == d.urlPart && | |
| c.jsCtx == d.jsCtx && | |
| c.attr == d.attr && | |
| c.element == d.element && | |
| c.err == d.err | |
| } | |
| // mangle produces an identifier that includes a suffix that distinguishes it | |
| // from template names mangled with different contexts. | |
| func (c context) mangle(templateName string) string { | |
| // The mangled name for the default context is the input templateName. | |
| if c.state == stateText { | |
| return templateName | |
| } | |
| s := templateName + "$htmltemplate_" + c.state.String() | |
| if c.delim != delimNone { | |
| s += "_" + c.delim.String() | |
| } | |
| if c.urlPart != urlPartNone { | |
| s += "_" + c.urlPart.String() | |
| } | |
| if c.jsCtx != jsCtxRegexp { | |
| s += "_" + c.jsCtx.String() | |
| } | |
| if c.attr != attrNone { | |
| s += "_" + c.attr.String() | |
| } | |
| if c.element != elementNone { | |
| s += "_" + c.element.String() | |
| } | |
| return s | |
| } | |
| // state describes a high-level HTML parser state. | |
| // | |
| // It bounds the top of the element stack, and by extension the HTML insertion | |
| // mode, but also contains state that does not correspond to anything in the | |
| // HTML5 parsing algorithm because a single token production in the HTML | |
| // grammar may contain embedded actions in a template. For instance, the quoted | |
| // HTML attribute produced by | |
| // | |
| // <div title="Hello {{.World}}"> | |
| // | |
| // is a single token in HTML's grammar but in a template spans several nodes. | |
| type state uint8 | |
| //go:generate stringer -type state | |
| const ( | |
| // stateText is parsed character data. An HTML parser is in | |
| // this state when its parse position is outside an HTML tag, | |
| // directive, comment, and special element body. | |
| stateText state = iota | |
| // stateTag occurs before an HTML attribute or the end of a tag. | |
| stateTag | |
| // stateAttrName occurs inside an attribute name. | |
| // It occurs between the ^'s in ` ^name^ = value`. | |
| stateAttrName | |
| // stateAfterName occurs after an attr name has ended but before any | |
| // equals sign. It occurs between the ^'s in ` name^ ^= value`. | |
| stateAfterName | |
| // stateBeforeValue occurs after the equals sign but before the value. | |
| // It occurs between the ^'s in ` name =^ ^value`. | |
| stateBeforeValue | |
| // stateHTMLCmt occurs inside an <!-- HTML comment -->. | |
| stateHTMLCmt | |
| // stateRCDATA occurs inside an RCDATA element (<textarea> or <title>) | |
| // as described at https://www.w3.org/TR/html5/syntax.html#elements-0 | |
| stateRCDATA | |
| // stateAttr occurs inside an HTML attribute whose content is text. | |
| stateAttr | |
| // stateURL occurs inside an HTML attribute whose content is a URL. | |
| stateURL | |
| // stateSrcset occurs inside an HTML srcset attribute. | |
| stateSrcset | |
| // stateJS occurs inside an event handler or script element. | |
| stateJS | |
| // stateJSDqStr occurs inside a JavaScript double quoted string. | |
| stateJSDqStr | |
| // stateJSSqStr occurs inside a JavaScript single quoted string. | |
| stateJSSqStr | |
| // stateJSTmplLit occurs inside a JavaScript back quoted string. | |
| stateJSTmplLit | |
| // stateJSRegexp occurs inside a JavaScript regexp literal. | |
| stateJSRegexp | |
| // stateJSBlockCmt occurs inside a JavaScript /* block comment */. | |
| stateJSBlockCmt | |
| // stateJSLineCmt occurs inside a JavaScript // line comment. | |
| stateJSLineCmt | |
| // stateJSHTMLOpenCmt occurs inside a JavaScript <!-- HTML-like comment. | |
| stateJSHTMLOpenCmt | |
| // stateJSHTMLCloseCmt occurs inside a JavaScript --> HTML-like comment. | |
| stateJSHTMLCloseCmt | |
| // stateCSS occurs inside a <style> element or style attribute. | |
| stateCSS | |
| // stateCSSDqStr occurs inside a CSS double quoted string. | |
| stateCSSDqStr | |
| // stateCSSSqStr occurs inside a CSS single quoted string. | |
| stateCSSSqStr | |
| // stateCSSDqURL occurs inside a CSS double quoted url("..."). | |
| stateCSSDqURL | |
| // stateCSSSqURL occurs inside a CSS single quoted url('...'). | |
| stateCSSSqURL | |
| // stateCSSURL occurs inside a CSS unquoted url(...). | |
| stateCSSURL | |
| // stateCSSBlockCmt occurs inside a CSS /* block comment */. | |
| stateCSSBlockCmt | |
| // stateCSSLineCmt occurs inside a CSS // line comment. | |
| stateCSSLineCmt | |
| // stateError is an infectious error state outside any valid | |
| // HTML/CSS/JS construct. | |
| stateError | |
| // stateDead marks unreachable code after a {{break}} or {{continue}}. | |
| stateDead | |
| ) | |
| // isComment is true for any state that contains content meant for template | |
| // authors & maintainers, not for end-users or machines. | |
| func isComment(s state) bool { | |
| switch s { | |
| case stateHTMLCmt, stateJSBlockCmt, stateJSLineCmt, stateJSHTMLOpenCmt, stateJSHTMLCloseCmt, stateCSSBlockCmt, stateCSSLineCmt: | |
| return true | |
| } | |
| return false | |
| } | |
| // isInTag return whether s occurs solely inside an HTML tag. | |
| func isInTag(s state) bool { | |
| switch s { | |
| case stateTag, stateAttrName, stateAfterName, stateBeforeValue, stateAttr: | |
| return true | |
| } | |
| return false | |
| } | |
| // isInScriptLiteral returns true if s is one of the literal states within a | |
| // <script> tag, and as such occurrences of "<!--", "<script", and "</script" | |
| // need to be treated specially. | |
| func isInScriptLiteral(s state) bool { | |
| // Ignore the comment states (stateJSBlockCmt, stateJSLineCmt, | |
| // stateJSHTMLOpenCmt, stateJSHTMLCloseCmt) because their content is already | |
| // omitted from the output. | |
| switch s { | |
| case stateJSDqStr, stateJSSqStr, stateJSTmplLit, stateJSRegexp: | |
| return true | |
| } | |
| return false | |
| } | |
| // delim is the delimiter that will end the current HTML attribute. | |
| type delim uint8 | |
| //go:generate stringer -type delim | |
| const ( | |
| // delimNone occurs outside any attribute. | |
| delimNone delim = iota | |
| // delimDoubleQuote occurs when a double quote (") closes the attribute. | |
| delimDoubleQuote | |
| // delimSingleQuote occurs when a single quote (') closes the attribute. | |
| delimSingleQuote | |
| // delimSpaceOrTagEnd occurs when a space or right angle bracket (>) | |
| // closes the attribute. | |
| delimSpaceOrTagEnd | |
| ) | |
| // urlPart identifies a part in an RFC 3986 hierarchical URL to allow different | |
| // encoding strategies. | |
| type urlPart uint8 | |
| //go:generate stringer -type urlPart | |
| const ( | |
| // urlPartNone occurs when not in a URL, or possibly at the start: | |
| // ^ in "^http://auth/path?k=v#frag". | |
| urlPartNone urlPart = iota | |
| // urlPartPreQuery occurs in the scheme, authority, or path; between the | |
| // ^s in "h^ttp://auth/path^?k=v#frag". | |
| urlPartPreQuery | |
| // urlPartQueryOrFrag occurs in the query portion between the ^s in | |
| // "http://auth/path?^k=v#frag^". | |
| urlPartQueryOrFrag | |
| // urlPartUnknown occurs due to joining of contexts both before and | |
| // after the query separator. | |
| urlPartUnknown | |
| ) | |
| // jsCtx determines whether a '/' starts a regular expression literal or a | |
| // division operator. | |
| type jsCtx uint8 | |
| //go:generate stringer -type jsCtx | |
| const ( | |
| // jsCtxRegexp occurs where a '/' would start a regexp literal. | |
| jsCtxRegexp jsCtx = iota | |
| // jsCtxDivOp occurs where a '/' would start a division operator. | |
| jsCtxDivOp | |
| // jsCtxUnknown occurs where a '/' is ambiguous due to context joining. | |
| jsCtxUnknown | |
| ) | |
| // element identifies the HTML element when inside a start tag or special body. | |
| // Certain HTML element (for example <script> and <style>) have bodies that are | |
| // treated differently from stateText so the element type is necessary to | |
| // transition into the correct context at the end of a tag and to identify the | |
| // end delimiter for the body. | |
| type element uint8 | |
| //go:generate stringer -type element | |
| const ( | |
| // elementNone occurs outside a special tag or special element body. | |
| elementNone element = iota | |
| // elementScript corresponds to the raw text <script> element | |
| // with JS MIME type or no type attribute. | |
| elementScript | |
| // elementStyle corresponds to the raw text <style> element. | |
| elementStyle | |
| // elementTextarea corresponds to the RCDATA <textarea> element. | |
| elementTextarea | |
| // elementTitle corresponds to the RCDATA <title> element. | |
| elementTitle | |
| ) | |
| //go:generate stringer -type attr | |
| // attr identifies the current HTML attribute when inside the attribute, | |
| // that is, starting from stateAttrName until stateTag/stateText (exclusive). | |
| type attr uint8 | |
| const ( | |
| // attrNone corresponds to a normal attribute or no attribute. | |
| attrNone attr = iota | |
| // attrScript corresponds to an event handler attribute. | |
| attrScript | |
| // attrScriptType corresponds to the type attribute in script HTML element | |
| attrScriptType | |
| // attrStyle corresponds to the style attribute whose value is CSS. | |
| attrStyle | |
| // attrURL corresponds to an attribute whose value is a URL. | |
| attrURL | |
| // attrSrcset corresponds to a srcset attribute. | |
| attrSrcset | |
| ) | |