// Copyright 2017 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 ( "strings" ) // 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 // http://www.w3.org/TR/html5/syntax.html#the-end // where the context element is null. type context struct { state state delim delim element element attr attr err *Error // scriptType is the lowercase value of the "type" attribute inside the current "script" // element (see https://dev.w3.org/html5/spec-preview/the-script-element.html#attr-script-type). // This field will be empty if the parser is currently not in a script element, // the type attribute has not already been parsed in the current element, or if the // value of the type attribute cannot be determined at parse time. scriptType string // linkRel is the value of the "rel" attribute inside the current "link" // element (see https://html.spec.whatwg.org/multipage/semantics.html#attr-link-rel). // This value has been normalized to lowercase with exactly one space between tokens // and exactly one space at start and end, so that a lookup of any token foo can // be performed by searching for the substring " foo ". // This field will be empty if the parser is currently not in a link element, // the rel attribute has not already been parsed in the current element, or if the // value of the rel attribute cannot be determined at parse time. linkRel string } // eq returns whether Context c is equal to Context d. func (c context) eq(d context) bool { return c.state == d.state && c.delim == d.delim && c.element.eq(d.element) && c.attr.eq(d.attr) && c.err == d.err && c.scriptType == d.scriptType && c.linkRel == d.linkRel } // 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 //
// 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 // stateSpecialElementBody occurs inside a specal HTML element body. stateSpecialElementBody // 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 . stateHTMLCmt // stateAttr occurs inside an HTML attribute whose content is text. stateAttr // stateError is an infectious error state outside any valid // HTML/CSS/JS construct. stateError ) // isComment reports whether a state contains content meant for template // authors & maintainers, not for end-users or machines. func isComment(s state) bool { switch s { case stateHTMLCmt: return true } return false } // isInTag reports 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 } // 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 ) type element struct { // name is the lowercase name of the element. If context joining has occurred, name // will be arbitrarily assigned the element name from one of the joined contexts. name string // names contains all possible names the element could assume because of context joining. // For example, after joining the contexts in the "if" and "else" branches of // {{if .C}}`, // names will contain "img" and "audio". // names can also contain empty strings, which represent joined contexts with no element name. // names will be empty if no context joining occurred. names []string } // eq reports whether a and b have the same name. All other fields are ignored. func (e element) eq(d element) bool { return e.name == d.name } // String returns the string representation of the element. func (e element) String() string { return "element" + strings.Title(e.name) } // attr represents the attribute that the parser is in, that is, // starting from stateAttrName until stateTag/stateText (exclusive). type attr struct { // name is the lowercase name of the attribute. If context joining has occurred, name // will be arbitrarily assigned the attribute name from one of the joined contexts. name string // value is the value of the attribute. If context joining has occurred, value // will be arbitrarily assigned the attribute value from one of the joined contexts. // If there are multiple actions in the attribute value, value will contain the // concatenation of all values seen so far. For example, in // // value is "foo" at "{{.X}}" and "foobar" at "{{.Y}}". value string // ambiguousValue indicates whether value contains an ambiguous value due to context-joining. ambiguousValue bool // names contains all possible names the attribute could assume because of context joining. // For example, after joining the contexts in the "if" and "else" branches of // // names will contain "title" and "name". // names can also contain empty strings, which represent joined contexts with no attribute name. // names will be empty if no context joining occurred. names []string } // eq reports whether a and b have the same name. All other fields are ignored. func (a attr) eq(b attr) bool { return a.name == b.name } // String returns the string representation of the attr. func (a attr) String() string { return "attr" + strings.Title(a.name) }