package template
import (
"bytes"
"strings"
)
var transitionFunc = [...]func (context , []byte ) (context , int ){
stateText : tText ,
stateTag : tTag ,
stateAttrName : tAttrName ,
stateAfterName : tAfterName ,
stateBeforeValue : tBeforeValue ,
stateHTMLCmt : tHTMLCmt ,
stateRCDATA : tSpecialTagEnd ,
stateAttr : tAttr ,
stateURL : tURL ,
stateSrcset : tURL ,
stateJS : tJS ,
stateJSDqStr : tJSDelimited ,
stateJSSqStr : tJSDelimited ,
stateJSRegexp : tJSDelimited ,
stateJSTmplLit : tJSTmpl ,
stateJSBlockCmt : tBlockCmt ,
stateJSLineCmt : tLineCmt ,
stateJSHTMLOpenCmt : tLineCmt ,
stateJSHTMLCloseCmt : tLineCmt ,
stateCSS : tCSS ,
stateCSSDqStr : tCSSStr ,
stateCSSSqStr : tCSSStr ,
stateCSSDqURL : tCSSStr ,
stateCSSSqURL : tCSSStr ,
stateCSSURL : tCSSStr ,
stateCSSBlockCmt : tBlockCmt ,
stateCSSLineCmt : tLineCmt ,
stateError : tError ,
}
var commentStart = []byte ("<!--" )
var commentEnd = []byte ("-->" )
func tText(c context , s []byte ) (context , int ) {
k := 0
for {
i := k + bytes .IndexByte (s [k :], '<' )
if i < k || i +1 == len (s ) {
return c , len (s )
} else if i +4 <= len (s ) && bytes .Equal (commentStart , s [i :i +4 ]) {
return context {state : stateHTMLCmt }, i + 4
}
i ++
end := false
if s [i ] == '/' {
if i +1 == len (s ) {
return c , len (s )
}
end , i = true , i +1
}
j , e := eatTagName (s , i )
if j != i {
if end {
e = elementNone
}
return context {state : stateTag , element : e }, j
}
k = j
}
}
var elementContentType = [...]state {
elementNone : stateText ,
elementScript : stateJS ,
elementStyle : stateCSS ,
elementTextarea : stateRCDATA ,
elementTitle : stateRCDATA ,
}
func tTag(c context , s []byte ) (context , int ) {
i := eatWhiteSpace (s , 0 )
if i == len (s ) {
return c , len (s )
}
if s [i ] == '>' {
return context {
state : elementContentType [c .element ],
element : c .element ,
}, i + 1
}
j , err := eatAttrName (s , i )
if err != nil {
return context {state : stateError , err : err }, len (s )
}
state , attr := stateTag , attrNone
if i == j {
return context {
state : stateError ,
err : errorf (ErrBadHTML , nil , 0 , "expected space, attr name, or end of tag, but got %q" , s [i :]),
}, len (s )
}
attrName := strings .ToLower (string (s [i :j ]))
if c .element == elementScript && attrName == "type" {
attr = attrScriptType
} else {
switch attrType (attrName ) {
case contentTypeURL :
attr = attrURL
case contentTypeCSS :
attr = attrStyle
case contentTypeJS :
attr = attrScript
case contentTypeSrcset :
attr = attrSrcset
}
}
if j == len (s ) {
state = stateAttrName
} else {
state = stateAfterName
}
return context {state : state , element : c .element , attr : attr }, j
}
func tAttrName(c context , s []byte ) (context , int ) {
i , err := eatAttrName (s , 0 )
if err != nil {
return context {state : stateError , err : err }, len (s )
} else if i != len (s ) {
c .state = stateAfterName
}
return c , i
}
func tAfterName(c context , s []byte ) (context , int ) {
i := eatWhiteSpace (s , 0 )
if i == len (s ) {
return c , len (s )
} else if s [i ] != '=' {
c .state = stateTag
return c , i
}
c .state = stateBeforeValue
return c , i + 1
}
var attrStartStates = [...]state {
attrNone : stateAttr ,
attrScript : stateJS ,
attrScriptType : stateAttr ,
attrStyle : stateCSS ,
attrURL : stateURL ,
attrSrcset : stateSrcset ,
}
func tBeforeValue(c context , s []byte ) (context , int ) {
i := eatWhiteSpace (s , 0 )
if i == len (s ) {
return c , len (s )
}
delim := delimSpaceOrTagEnd
switch s [i ] {
case '\'' :
delim , i = delimSingleQuote , i +1
case '"' :
delim , i = delimDoubleQuote , i +1
}
c .state , c .delim = attrStartStates [c .attr ], delim
return c , i
}
func tHTMLCmt(c context , s []byte ) (context , int ) {
if i := bytes .Index (s , commentEnd ); i != -1 {
return context {}, i + 3
}
return c , len (s )
}
var specialTagEndMarkers = [...][]byte {
elementScript : []byte ("script" ),
elementStyle : []byte ("style" ),
elementTextarea : []byte ("textarea" ),
elementTitle : []byte ("title" ),
}
var (
specialTagEndPrefix = []byte ("</" )
tagEndSeparators = []byte ("> \t\n\f/" )
)
func tSpecialTagEnd(c context , s []byte ) (context , int ) {
if c .element != elementNone {
if c .element == elementScript && (isInScriptLiteral (c .state ) || isComment (c .state )) {
return c , len (s )
}
if i := indexTagEnd (s , specialTagEndMarkers [c .element ]); i != -1 {
return context {}, i
}
}
return c , len (s )
}
func indexTagEnd(s []byte , tag []byte ) int {
res := 0
plen := len (specialTagEndPrefix )
for len (s ) > 0 {
i := bytes .Index (s , specialTagEndPrefix )
if i == -1 {
return i
}
s = s [i +plen :]
if len (tag ) <= len (s ) && bytes .EqualFold (tag , s [:len (tag )]) {
s = s [len (tag ):]
if len (s ) > 0 && bytes .IndexByte (tagEndSeparators , s [0 ]) != -1 {
return res + i
}
res += len (tag )
}
res += i + plen
}
return -1
}
func tAttr(c context , s []byte ) (context , int ) {
return c , len (s )
}
func tURL(c context , s []byte ) (context , int ) {
if bytes .ContainsAny (s , "#?" ) {
c .urlPart = urlPartQueryOrFrag
} else if len (s ) != eatWhiteSpace (s , 0 ) && c .urlPart == urlPartNone {
c .urlPart = urlPartPreQuery
}
return c , len (s )
}
func tJS(c context , s []byte ) (context , int ) {
i := bytes .IndexAny (s , "\"`'/{}<-#" )
if i == -1 {
c .jsCtx = nextJSCtx (s , c .jsCtx )
return c , len (s )
}
c .jsCtx = nextJSCtx (s [:i ], c .jsCtx )
switch s [i ] {
case '"' :
c .state , c .jsCtx = stateJSDqStr , jsCtxRegexp
case '\'' :
c .state , c .jsCtx = stateJSSqStr , jsCtxRegexp
case '`' :
c .state , c .jsCtx = stateJSTmplLit , jsCtxRegexp
case '/' :
switch {
case i +1 < len (s ) && s [i +1 ] == '/' :
c .state , i = stateJSLineCmt , i +1
case i +1 < len (s ) && s [i +1 ] == '*' :
c .state , i = stateJSBlockCmt , i +1
case c .jsCtx == jsCtxRegexp :
c .state = stateJSRegexp
case c .jsCtx == jsCtxDivOp :
c .jsCtx = jsCtxRegexp
default :
return context {
state : stateError ,
err : errorf (ErrSlashAmbig , nil , 0 , "'/' could start a division or regexp: %.32q" , s [i :]),
}, len (s )
}
case '<' :
if i +3 < len (s ) && bytes .Equal (commentStart , s [i :i +4 ]) {
c .state , i = stateJSHTMLOpenCmt , i +3
}
case '-' :
if i +2 < len (s ) && bytes .Equal (commentEnd , s [i :i +3 ]) {
c .state , i = stateJSHTMLCloseCmt , i +2
}
case '#' :
if i +1 < len (s ) && s [i +1 ] == '!' {
c .state , i = stateJSLineCmt , i +1
}
case '{' :
if len (c .jsBraceDepth ) == 0 {
return c , i + 1
}
c .jsBraceDepth [len (c .jsBraceDepth )-1 ]++
case '}' :
if len (c .jsBraceDepth ) == 0 {
return c , i + 1
}
c .jsBraceDepth [len (c .jsBraceDepth )-1 ]--
if c .jsBraceDepth [len (c .jsBraceDepth )-1 ] >= 0 {
return c , i + 1
}
c .jsBraceDepth = c .jsBraceDepth [:len (c .jsBraceDepth )-1 ]
c .state = stateJSTmplLit
default :
panic ("unreachable" )
}
return c , i + 1
}
func tJSTmpl(c context , s []byte ) (context , int ) {
var k int
for {
i := k + bytes .IndexAny (s [k :], "`\\$" )
if i < k {
break
}
switch s [i ] {
case '\\' :
i ++
if i == len (s ) {
return context {
state : stateError ,
err : errorf (ErrPartialEscape , nil , 0 , "unfinished escape sequence in JS string: %q" , s ),
}, len (s )
}
case '$' :
if len (s ) >= i +2 && s [i +1 ] == '{' {
c .jsBraceDepth = append (c .jsBraceDepth , 0 )
c .state = stateJS
return c , i + 2
}
case '`' :
c .state = stateJS
return c , i + 1
}
k = i + 1
}
return c , len (s )
}
func tJSDelimited(c context , s []byte ) (context , int ) {
specials := `\"`
switch c .state {
case stateJSSqStr :
specials = `\'`
case stateJSRegexp :
specials = `\/[]`
}
k , inCharset := 0 , false
for {
i := k + bytes .IndexAny (s [k :], specials )
if i < k {
break
}
switch s [i ] {
case '\\' :
i ++
if i == len (s ) {
return context {
state : stateError ,
err : errorf (ErrPartialEscape , nil , 0 , "unfinished escape sequence in JS string: %q" , s ),
}, len (s )
}
case '[' :
inCharset = true
case ']' :
inCharset = false
case '/' :
if i > 0 && i +7 <= len (s ) && bytes .Equal (bytes .ToLower (s [i -1 :i +7 ]), []byte ("</script" )) {
i ++
} else if !inCharset {
c .state , c .jsCtx = stateJS , jsCtxDivOp
return c , i + 1
}
default :
if !inCharset {
c .state , c .jsCtx = stateJS , jsCtxDivOp
return c , i + 1
}
}
k = i + 1
}
if inCharset {
return context {
state : stateError ,
err : errorf (ErrPartialCharset , nil , 0 , "unfinished JS regexp charset: %q" , s ),
}, len (s )
}
return c , len (s )
}
var blockCommentEnd = []byte ("*/" )
func tBlockCmt(c context , s []byte ) (context , int ) {
i := bytes .Index (s , blockCommentEnd )
if i == -1 {
return c , len (s )
}
switch c .state {
case stateJSBlockCmt :
c .state = stateJS
case stateCSSBlockCmt :
c .state = stateCSS
default :
panic (c .state .String ())
}
return c , i + 2
}
func tLineCmt(c context , s []byte ) (context , int ) {
var lineTerminators string
var endState state
switch c .state {
case stateJSLineCmt , stateJSHTMLOpenCmt , stateJSHTMLCloseCmt :
lineTerminators , endState = "\n\r\u2028\u2029" , stateJS
case stateCSSLineCmt :
lineTerminators , endState = "\n\f\r" , stateCSS
default :
panic (c .state .String ())
}
i := bytes .IndexAny (s , lineTerminators )
if i == -1 {
return c , len (s )
}
c .state = endState
return c , i
}
func tCSS(c context , s []byte ) (context , int ) {
k := 0
for {
i := k + bytes .IndexAny (s [k :], `("'/` )
if i < k {
return c , len (s )
}
switch s [i ] {
case '(' :
p := bytes .TrimRight (s [:i ], "\t\n\f\r " )
if endsWithCSSKeyword (p , "url" ) {
j := len (s ) - len (bytes .TrimLeft (s [i +1 :], "\t\n\f\r " ))
switch {
case j != len (s ) && s [j ] == '"' :
c .state , j = stateCSSDqURL , j +1
case j != len (s ) && s [j ] == '\'' :
c .state , j = stateCSSSqURL , j +1
default :
c .state = stateCSSURL
}
return c , j
}
case '/' :
if i +1 < len (s ) {
switch s [i +1 ] {
case '/' :
c .state = stateCSSLineCmt
return c , i + 2
case '*' :
c .state = stateCSSBlockCmt
return c , i + 2
}
}
case '"' :
c .state = stateCSSDqStr
return c , i + 1
case '\'' :
c .state = stateCSSSqStr
return c , i + 1
}
k = i + 1
}
}
func tCSSStr(c context , s []byte ) (context , int ) {
var endAndEsc string
switch c .state {
case stateCSSDqStr , stateCSSDqURL :
endAndEsc = `\"`
case stateCSSSqStr , stateCSSSqURL :
endAndEsc = `\'`
case stateCSSURL :
endAndEsc = "\\\t\n\f\r )"
default :
panic (c .state .String ())
}
k := 0
for {
i := k + bytes .IndexAny (s [k :], endAndEsc )
if i < k {
c , nread := tURL (c , decodeCSS (s [k :]))
return c , k + nread
}
if s [i ] == '\\' {
i ++
if i == len (s ) {
return context {
state : stateError ,
err : errorf (ErrPartialEscape , nil , 0 , "unfinished escape sequence in CSS string: %q" , s ),
}, len (s )
}
} else {
c .state = stateCSS
return c , i + 1
}
c , _ = tURL (c , decodeCSS (s [:i +1 ]))
k = i + 1
}
}
func tError(c context , s []byte ) (context , int ) {
return c , len (s )
}
func eatAttrName(s []byte , i int ) (int , *Error ) {
for j := i ; j < len (s ); j ++ {
switch s [j ] {
case ' ' , '\t' , '\n' , '\f' , '\r' , '=' , '>' :
return j , nil
case '\'' , '"' , '<' :
return -1 , errorf (ErrBadHTML , nil , 0 , "%q in attribute name: %.32q" , s [j :j +1 ], s )
default :
}
}
return len (s ), nil
}
var elementNameMap = map [string ]element {
"script" : elementScript ,
"style" : elementStyle ,
"textarea" : elementTextarea ,
"title" : elementTitle ,
}
func asciiAlpha(c byte ) bool {
return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z'
}
func asciiAlphaNum(c byte ) bool {
return asciiAlpha (c ) || '0' <= c && c <= '9'
}
func eatTagName(s []byte , i int ) (int , element ) {
if i == len (s ) || !asciiAlpha (s [i ]) {
return i , elementNone
}
j := i + 1
for j < len (s ) {
x := s [j ]
if asciiAlphaNum (x ) {
j ++
continue
}
if (x == ':' || x == '-' ) && j +1 < len (s ) && asciiAlphaNum (s [j +1 ]) {
j += 2
continue
}
break
}
return j , elementNameMap [strings .ToLower (string (s [i :j ]))]
}
func eatWhiteSpace(s []byte , i int ) int {
for j := i ; j < len (s ); j ++ {
switch s [j ] {
case ' ' , '\t' , '\n' , '\f' , '\r' :
default :
return j
}
}
return len (s )
}
The pages are generated with Golds v0.7.0-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .