context.go 10 KB

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