1
0
mirror of https://github.com/golang/go synced 2024-11-22 07:44:43 -07:00

exp/template/html: change transition functions to return indices

Formulaic changes to transition functions in preparation for CL 5074041.
This should be completely semantics preserving.

R=nigeltao
CC=golang-dev
https://golang.org/cl/5091041
This commit is contained in:
Mike Samuel 2011-09-19 20:52:14 -07:00
parent 3c3a86ccc7
commit 3a013f1175
3 changed files with 147 additions and 143 deletions

View File

@ -547,22 +547,22 @@ var delimEnds = [...]string{
// escapeText escapes a text template node. // escapeText escapes a text template node.
func (e *escaper) escapeText(c context, n *parse.TextNode) context { func (e *escaper) escapeText(c context, n *parse.TextNode) context {
s, written := n.Text, 0 s, written, i, b := n.Text, 0, 0, new(bytes.Buffer)
var b bytes.Buffer for i != len(s) {
for len(s) > 0 { c1, nread := contextAfterText(c, s[i:])
c1, s1 := contextAfterText(c, s) i1 := i + nread
if c.state == c1.state && (c.state == stateText || c.state == stateRCDATA) { if c.state == c1.state && (c.state == stateText || c.state == stateRCDATA) {
i0, i1 := len(n.Text)-len(s), len(n.Text)-len(s1) for j := i; j < i1; j++ {
for i := i0; i < i1; i++ { if s[j] == '<' {
if n.Text[i] == '<' { b.Write(s[written:j])
b.Write(n.Text[written:i])
b.WriteString("&lt;") b.WriteString("&lt;")
written = i + 1 written = j + 1
} }
} }
} }
c, s = c1, s1 c, i = c1, i1
} }
if written != 0 && c.state != stateError { if written != 0 && c.state != stateError {
b.Write(n.Text[written:]) b.Write(n.Text[written:])
e.editTextNode(n, b.Bytes()) e.editTextNode(n, b.Bytes())
@ -572,7 +572,7 @@ func (e *escaper) escapeText(c context, n *parse.TextNode) context {
// contextAfterText starts in context c, consumes some tokens from the front of // contextAfterText starts in context c, consumes some tokens from the front of
// s, then returns the context after those tokens and the unprocessed suffix. // s, then returns the context after those tokens and the unprocessed suffix.
func contextAfterText(c context, s []byte) (context, []byte) { func contextAfterText(c context, s []byte) (context, int) {
if c.delim == delimNone { if c.delim == delimNone {
return transitionFunc[c.state](c, s) return transitionFunc[c.state](c, s)
} }
@ -584,9 +584,10 @@ func contextAfterText(c context, s []byte) (context, []byte) {
// <button onclick="alert(&quot;Hi!&quot;)"> // <button onclick="alert(&quot;Hi!&quot;)">
// without having to entity decode token boundaries. // without having to entity decode token boundaries.
for u := []byte(html.UnescapeString(string(s))); len(u) != 0; { for u := []byte(html.UnescapeString(string(s))); len(u) != 0; {
c, u = transitionFunc[c.state](c, u) c1, i1 := transitionFunc[c.state](c, u)
c, u = c1, u[i1:]
} }
return c, nil return c, len(s)
} }
if c.delim != delimSpaceOrTagEnd { if c.delim != delimSpaceOrTagEnd {
// Consume any quote. // Consume any quote.
@ -594,7 +595,7 @@ func contextAfterText(c context, s []byte) (context, []byte) {
} }
// On exiting an attribute, we discard all state information // On exiting an attribute, we discard all state information
// except the state and element. // except the state and element.
return context{state: stateTag, element: c.element}, s[i:] return context{state: stateTag, element: c.element}, i
} }
// editActionNode records a change to an action pipeline for later commit. // editActionNode records a change to an action pipeline for later commit.

View File

@ -165,43 +165,44 @@ func htmlReplacer(s string, replacementTable []string, badRunes bool) string {
// For example, `<b>&iexcl;Hi!</b> <script>...</script>` -> `&iexcl;Hi! `. // For example, `<b>&iexcl;Hi!</b> <script>...</script>` -> `&iexcl;Hi! `.
func stripTags(html string) string { func stripTags(html string) string {
var b bytes.Buffer var b bytes.Buffer
s, c := []byte(html), context{} s, c, i := []byte(html), context{}, 0
// Using the transition funcs helps us avoid mangling // Using the transition funcs helps us avoid mangling
// `<div title="1>2">` or `I <3 Ponies!`. // `<div title="1>2">` or `I <3 Ponies!`.
for len(s) > 0 { for i != len(s) {
if c.delim == delimNone { if c.delim == delimNone {
d, t := transitionFunc[c.state](c, s) d, nread := transitionFunc[c.state](c, s[i:])
i1 := i + nread
if c.state == stateText || c.state == stateRCDATA { if c.state == stateText || c.state == stateRCDATA {
i := len(s) - len(t)
// Emit text up to the start of the tag or comment. // Emit text up to the start of the tag or comment.
j := i1
if d.state != c.state { if d.state != c.state {
for j := i - 1; j >= 0; j-- { for j1 := j - 1; j1 >= i; j1-- {
if s[j] == '<' { if s[j1] == '<' {
i = j j = j1
break break
} }
} }
} }
b.Write(s[:i]) b.Write(s[i:j])
} }
c, s = d, t c, i = d, i1
continue continue
} }
i := bytes.IndexAny(s, delimEnds[c.delim]) i1 := i + bytes.IndexAny(s[i:], delimEnds[c.delim])
if i == -1 { if i1 < i {
break break
} }
if c.delim != delimSpaceOrTagEnd { if c.delim != delimSpaceOrTagEnd {
// Consume any quote. // Consume any quote.
i++ i1++
} }
c, s = context{state: stateTag, element: c.element}, s[i:] c, i = context{state: stateTag, element: c.element}, i1
} }
if c.state == stateText { if c.state == stateText {
if b.Len() == 0 { if b.Len() == 0 {
return html return html
} }
b.Write(s) b.Write(s[i:])
} }
return b.String() return b.String()
} }

View File

@ -14,8 +14,9 @@ import (
// transitionFunc is the array of context transition functions for text nodes. // transitionFunc is the array of context transition functions for text nodes.
// A transition function takes a context and template text input, and returns // A transition function takes a context and template text input, and returns
// the updated context and any unconsumed text. // the updated context and the number of bytes consumed from the front of the
var transitionFunc = [...]func(context, []byte) (context, []byte){ // input.
var transitionFunc = [...]func(context, []byte) (context, int){
stateText: tText, stateText: tText,
stateTag: tTag, stateTag: tTag,
stateAttrName: tAttrName, stateAttrName: tAttrName,
@ -46,27 +47,28 @@ var commentStart = []byte("<!--")
var commentEnd = []byte("-->") var commentEnd = []byte("-->")
// tText is the context transition function for the text state. // tText is the context transition function for the text state.
func tText(c context, s []byte) (context, []byte) { func tText(c context, s []byte) (context, int) {
k := 0
for { for {
i := bytes.IndexByte(s, '<') i := k + bytes.IndexByte(s[k:], '<')
if i == -1 || i+1 == len(s) { if i < k || i+1 == len(s) {
return c, nil return c, len(s)
} else if i+4 <= len(s) && bytes.Equal(commentStart, s[i:i+4]) { } else if i+4 <= len(s) && bytes.Equal(commentStart, s[i:i+4]) {
return context{state: stateHTMLCmt}, s[i+4:] return context{state: stateHTMLCmt}, i + 4
} }
i++ i++
if s[i] == '/' { if s[i] == '/' {
if i+1 == len(s) { if i+1 == len(s) {
return c, nil return c, len(s)
} }
i++ i++
} }
j, e := eatTagName(s, i) j, e := eatTagName(s, i)
if j != i { if j != i {
// We've found an HTML tag. // We've found an HTML tag.
return context{state: stateTag, element: e}, s[j:] return context{state: stateTag, element: e}, j
} }
s = s[j:] k = j
} }
panic("unreachable") panic("unreachable")
} }
@ -80,21 +82,21 @@ var elementContentType = [...]state{
} }
// tTag is the context transition function for the tag state. // tTag is the context transition function for the tag state.
func tTag(c context, s []byte) (context, []byte) { func tTag(c context, s []byte) (context, int) {
// Find the attribute name. // Find the attribute name.
i := eatWhiteSpace(s, 0) i := eatWhiteSpace(s, 0)
if i == len(s) { if i == len(s) {
return c, nil return c, len(s)
} }
if s[i] == '>' { if s[i] == '>' {
return context{ return context{
state: elementContentType[c.element], state: elementContentType[c.element],
element: c.element, element: c.element,
}, s[i+1:] }, i + 1
} }
j, err := eatAttrName(s, i) j, err := eatAttrName(s, i)
if err != nil { if err != nil {
return context{state: stateError, err: err}, nil return context{state: stateError, err: err}, len(s)
} }
state, attr := stateTag, attrNone state, attr := stateTag, attrNone
if i != j { if i != j {
@ -112,35 +114,35 @@ func tTag(c context, s []byte) (context, []byte) {
state = stateAfterName state = stateAfterName
} }
} }
return context{state: state, element: c.element, attr: attr}, s[j:] return context{state: state, element: c.element, attr: attr}, j
} }
// tAttrName is the context transition function for stateAttrName. // tAttrName is the context transition function for stateAttrName.
func tAttrName(c context, s []byte) (context, []byte) { func tAttrName(c context, s []byte) (context, int) {
i, err := eatAttrName(s, 0) i, err := eatAttrName(s, 0)
if err != nil { if err != nil {
return context{state: stateError, err: err}, nil return context{state: stateError, err: err}, len(s)
} else if i == len(s) { } else if i == len(s) {
return c, nil return c, len(s)
} }
c.state = stateAfterName c.state = stateAfterName
return c, s[i:] return c, i
} }
// tAfterName is the context transition function for stateAfterName. // tAfterName is the context transition function for stateAfterName.
func tAfterName(c context, s []byte) (context, []byte) { func tAfterName(c context, s []byte) (context, int) {
// Look for the start of the value. // Look for the start of the value.
i := eatWhiteSpace(s, 0) i := eatWhiteSpace(s, 0)
if i == len(s) { if i == len(s) {
return c, nil return c, len(s)
} else if s[i] != '=' { } else if s[i] != '=' {
// Occurs due to tag ending '>', and valueless attribute. // Occurs due to tag ending '>', and valueless attribute.
c.state = stateTag c.state = stateTag
return c, s[i:] return c, i
} }
c.state = stateBeforeValue c.state = stateBeforeValue
// Consume the "=". // Consume the "=".
return c, s[i+1:] return c, i + 1
} }
var attrStartStates = [...]state{ var attrStartStates = [...]state{
@ -151,10 +153,10 @@ var attrStartStates = [...]state{
} }
// tBeforeValue is the context transition function for stateBeforeValue. // tBeforeValue is the context transition function for stateBeforeValue.
func tBeforeValue(c context, s []byte) (context, []byte) { func tBeforeValue(c context, s []byte) (context, int) {
i := eatWhiteSpace(s, 0) i := eatWhiteSpace(s, 0)
if i == len(s) { if i == len(s) {
return c, nil return c, len(s)
} }
// Find the attribute delimiter. // Find the attribute delimiter.
delim := delimSpaceOrTagEnd delim := delimSpaceOrTagEnd
@ -165,16 +167,16 @@ func tBeforeValue(c context, s []byte) (context, []byte) {
delim, i = delimDoubleQuote, i+1 delim, i = delimDoubleQuote, i+1
} }
c.state, c.delim, c.attr = attrStartStates[c.attr], delim, attrNone c.state, c.delim, c.attr = attrStartStates[c.attr], delim, attrNone
return c, s[i:] return c, i
} }
// tHTMLCmt is the context transition function for stateHTMLCmt. // tHTMLCmt is the context transition function for stateHTMLCmt.
func tHTMLCmt(c context, s []byte) (context, []byte) { func tHTMLCmt(c context, s []byte) (context, int) {
i := bytes.Index(s, commentEnd) i := bytes.Index(s, commentEnd)
if i != -1 { if i != -1 {
return context{}, s[i+3:] return context{}, i + 3
} }
return c, nil return c, len(s)
} }
// specialTagEndMarkers maps element types to the character sequence that // specialTagEndMarkers maps element types to the character sequence that
@ -188,24 +190,24 @@ var specialTagEndMarkers = [...]string{
// tSpecialTagEnd is the context transition function for raw text and RCDATA // tSpecialTagEnd is the context transition function for raw text and RCDATA
// element states. // element states.
func tSpecialTagEnd(c context, s []byte) (context, []byte) { func tSpecialTagEnd(c context, s []byte) (context, int) {
if c.element != elementNone { if c.element != elementNone {
end := specialTagEndMarkers[c.element] end := specialTagEndMarkers[c.element]
i := strings.Index(strings.ToLower(string(s)), end) i := strings.Index(strings.ToLower(string(s)), end)
if i != -1 { if i != -1 {
return context{state: stateTag}, s[i+len(end):] return context{state: stateTag}, i + len(end)
} }
} }
return c, nil return c, len(s)
} }
// tAttr is the context transition function for the attribute state. // tAttr is the context transition function for the attribute state.
func tAttr(c context, s []byte) (context, []byte) { func tAttr(c context, s []byte) (context, int) {
return c, nil return c, len(s)
} }
// tURL is the context transition function for the URL state. // tURL is the context transition function for the URL state.
func tURL(c context, s []byte) (context, []byte) { func tURL(c context, s []byte) (context, int) {
if bytes.IndexAny(s, "#?") >= 0 { if bytes.IndexAny(s, "#?") >= 0 {
c.urlPart = urlPartQueryOrFrag c.urlPart = urlPartQueryOrFrag
} else if len(s) != eatWhiteSpace(s, 0) && c.urlPart == urlPartNone { } else if len(s) != eatWhiteSpace(s, 0) && c.urlPart == urlPartNone {
@ -213,20 +215,20 @@ func tURL(c context, s []byte) (context, []byte) {
// attrs: http://www.w3.org/TR/html5/index.html#attributes-1 // attrs: http://www.w3.org/TR/html5/index.html#attributes-1
c.urlPart = urlPartPreQuery c.urlPart = urlPartPreQuery
} }
return c, nil return c, len(s)
} }
// tJS is the context transition function for the JS state. // tJS is the context transition function for the JS state.
func tJS(c context, s []byte) (context, []byte) { func tJS(c context, s []byte) (context, int) {
if d, t := tSpecialTagEnd(c, s); t != nil { if d, i := tSpecialTagEnd(c, s); i != len(s) {
return d, t return d, i
} }
i := bytes.IndexAny(s, `"'/`) i := bytes.IndexAny(s, `"'/`)
if i == -1 { if i == -1 {
// Entire input is non string, comment, regexp tokens. // Entire input is non string, comment, regexp tokens.
c.jsCtx = nextJSCtx(s, c.jsCtx) c.jsCtx = nextJSCtx(s, c.jsCtx)
return c, nil return c, len(s)
} }
c.jsCtx = nextJSCtx(s[:i], c.jsCtx) c.jsCtx = nextJSCtx(s[:i], c.jsCtx)
switch s[i] { switch s[i] {
@ -248,18 +250,18 @@ func tJS(c context, s []byte) (context, []byte) {
return context{ return context{
state: stateError, state: stateError,
err: errorf(ErrSlashAmbig, 0, "'/' could start div or regexp: %.32q", s[i:]), err: errorf(ErrSlashAmbig, 0, "'/' could start div or regexp: %.32q", s[i:]),
}, nil }, len(s)
} }
default: default:
panic("unreachable") panic("unreachable")
} }
return c, s[i+1:] return c, i + 1
} }
// tJSStr is the context transition function for the JS string states. // tJSStr is the context transition function for the JS string states.
func tJSStr(c context, s []byte) (context, []byte) { func tJSStr(c context, s []byte) (context, int) {
if d, t := tSpecialTagEnd(c, s); t != nil { if d, i := tSpecialTagEnd(c, s); i != len(s) {
return d, t return d, i
} }
quoteAndEsc := `\"` quoteAndEsc := `\"`
@ -267,55 +269,54 @@ func tJSStr(c context, s []byte) (context, []byte) {
quoteAndEsc = `\'` quoteAndEsc = `\'`
} }
b := s k := 0
for { for {
i := bytes.IndexAny(b, quoteAndEsc) i := k + bytes.IndexAny(s[k:], quoteAndEsc)
if i == -1 { if i < k {
return c, nil return c, len(s)
} }
if b[i] == '\\' { if s[i] == '\\' {
i++ i++
if i == len(b) { if i == len(s) {
return context{ return context{
state: stateError, state: stateError,
err: errorf(ErrPartialEscape, 0, "unfinished escape sequence in JS string: %q", s), err: errorf(ErrPartialEscape, 0, "unfinished escape sequence in JS string: %q", s),
}, nil }, len(s)
} }
} else { } else {
c.state, c.jsCtx = stateJS, jsCtxDivOp c.state, c.jsCtx = stateJS, jsCtxDivOp
return c, b[i+1:] return c, i + 1
} }
b = b[i+1:] k = i + 1
} }
panic("unreachable") panic("unreachable")
} }
// tJSRegexp is the context transition function for the /RegExp/ literal state. // tJSRegexp is the context transition function for the /RegExp/ literal state.
func tJSRegexp(c context, s []byte) (context, []byte) { func tJSRegexp(c context, s []byte) (context, int) {
if d, t := tSpecialTagEnd(c, s); t != nil { if d, i := tSpecialTagEnd(c, s); i != len(s) {
return d, t return d, i
} }
b := s k, inCharset := 0, false
inCharset := false
for { for {
i := bytes.IndexAny(b, `/[\]`) i := k + bytes.IndexAny(s[k:], `\/[]`)
if i == -1 { if i < k {
break break
} }
switch b[i] { switch s[i] {
case '/': case '/':
if !inCharset { if !inCharset {
c.state, c.jsCtx = stateJS, jsCtxDivOp c.state, c.jsCtx = stateJS, jsCtxDivOp
return c, b[i+1:] return c, i + 1
} }
case '\\': case '\\':
i++ i++
if i == len(b) { if i == len(s) {
return context{ return context{
state: stateError, state: stateError,
err: errorf(ErrPartialEscape, 0, "unfinished escape sequence in JS regexp: %q", s), err: errorf(ErrPartialEscape, 0, "unfinished escape sequence in JS regexp: %q", s),
}, nil }, len(s)
} }
case '[': case '[':
inCharset = true inCharset = true
@ -324,7 +325,7 @@ func tJSRegexp(c context, s []byte) (context, []byte) {
default: default:
panic("unreachable") panic("unreachable")
} }
b = b[i+1:] k = i + 1
} }
if inCharset { if inCharset {
@ -333,22 +334,22 @@ func tJSRegexp(c context, s []byte) (context, []byte) {
return context{ return context{
state: stateError, state: stateError,
err: errorf(ErrPartialCharset, 0, "unfinished JS regexp charset: %q", s), err: errorf(ErrPartialCharset, 0, "unfinished JS regexp charset: %q", s),
}, nil }, len(s)
} }
return c, nil return c, len(s)
} }
var blockCommentEnd = []byte("*/") var blockCommentEnd = []byte("*/")
// tBlockCmt is the context transition function for /*comment*/ states. // tBlockCmt is the context transition function for /*comment*/ states.
func tBlockCmt(c context, s []byte) (context, []byte) { func tBlockCmt(c context, s []byte) (context, int) {
if d, t := tSpecialTagEnd(c, s); t != nil { if d, i := tSpecialTagEnd(c, s); i != len(s) {
return d, t return d, i
} }
i := bytes.Index(s, blockCommentEnd) i := bytes.Index(s, blockCommentEnd)
if i == -1 { if i == -1 {
return c, nil return c, len(s)
} }
switch c.state { switch c.state {
case stateJSBlockCmt: case stateJSBlockCmt:
@ -358,13 +359,13 @@ func tBlockCmt(c context, s []byte) (context, []byte) {
default: default:
panic(c.state.String()) panic(c.state.String())
} }
return c, s[i+2:] return c, i + 2
} }
// tLineCmt is the context transition function for //comment states. // tLineCmt is the context transition function for //comment states.
func tLineCmt(c context, s []byte) (context, []byte) { func tLineCmt(c context, s []byte) (context, int) {
if d, t := tSpecialTagEnd(c, s); t != nil { if d, i := tSpecialTagEnd(c, s); i != len(s) {
return d, t return d, i
} }
var lineTerminators string var lineTerminators string
var endState state var endState state
@ -386,21 +387,21 @@ func tLineCmt(c context, s []byte) (context, []byte) {
i := bytes.IndexAny(s, lineTerminators) i := bytes.IndexAny(s, lineTerminators)
if i == -1 { if i == -1 {
return c, nil return c, len(s)
} }
c.state = endState c.state = endState
// Per section 7.4 of EcmaScript 5 : http://es5.github.com/#x7.4 // Per section 7.4 of EcmaScript 5 : http://es5.github.com/#x7.4
// "However, the LineTerminator at the end of the line is not // "However, the LineTerminator at the end of the line is not
// considered to be part of the single-line comment; it is recognised // considered to be part of the single-line comment; it is
// separately by the lexical grammar and becomes part of the stream of // recognized separately by the lexical grammar and becomes part
// input elements for the syntactic grammar." // of the stream of input elements for the syntactic grammar."
return c, s[i:] return c, i
} }
// tCSS is the context transition function for the CSS state. // tCSS is the context transition function for the CSS state.
func tCSS(c context, s []byte) (context, []byte) { func tCSS(c context, s []byte) (context, int) {
if d, t := tSpecialTagEnd(c, s); t != nil { if d, i := tSpecialTagEnd(c, s); i != len(s) {
return d, t return d, i
} }
// CSS quoted strings are almost never used except for: // CSS quoted strings are almost never used except for:
@ -430,55 +431,55 @@ func tCSS(c context, s []byte) (context, []byte) {
// have the attribute name available if our conservative assumption // have the attribute name available if our conservative assumption
// proves problematic for real code. // proves problematic for real code.
k := 0
for { for {
i := bytes.IndexAny(s, `("'/`) i := k + bytes.IndexAny(s[k:], `("'/`)
if i == -1 { if i < k {
return c, nil return c, len(s)
} }
switch s[i] { switch s[i] {
case '(': case '(':
// Look for url to the left. // Look for url to the left.
p := bytes.TrimRight(s[:i], "\t\n\f\r ") p := bytes.TrimRight(s[:i], "\t\n\f\r ")
if endsWithCSSKeyword(p, "url") { if endsWithCSSKeyword(p, "url") {
q := bytes.TrimLeft(s[i+1:], "\t\n\f\r ") j := len(s) - len(bytes.TrimLeft(s[i+1:], "\t\n\f\r "))
switch { switch {
case len(q) != 0 && q[0] == '"': case j != len(s) && s[j] == '"':
c.state, s = stateCSSDqURL, q[1:] c.state, j = stateCSSDqURL, j+1
case len(q) != 0 && q[0] == '\'': case j != len(s) && s[j] == '\'':
c.state, s = stateCSSSqURL, q[1:] c.state, j = stateCSSSqURL, j+1
default: default:
c.state, s = stateCSSURL, q c.state = stateCSSURL
} }
return c, s return c, j
} }
case '/': case '/':
if i+1 < len(s) { if i+1 < len(s) {
switch s[i+1] { switch s[i+1] {
case '/': case '/':
c.state = stateCSSLineCmt c.state = stateCSSLineCmt
return c, s[i+2:] return c, i + 2
case '*': case '*':
c.state = stateCSSBlockCmt c.state = stateCSSBlockCmt
return c, s[i+2:] return c, i + 2
} }
} }
case '"': case '"':
c.state = stateCSSDqStr c.state = stateCSSDqStr
return c, s[i+1:] return c, i + 1
case '\'': case '\'':
c.state = stateCSSSqStr c.state = stateCSSSqStr
return c, s[i+1:] return c, i + 1
} }
s = s[i+1:] k = i + 1
} }
panic("unreachable") panic("unreachable")
} }
// tCSSStr is the context transition function for the CSS string and URL states. // tCSSStr is the context transition function for the CSS string and URL states.
func tCSSStr(c context, s []byte) (context, []byte) { func tCSSStr(c context, s []byte) (context, int) {
if d, t := tSpecialTagEnd(c, s); t != nil { if d, i := tSpecialTagEnd(c, s); i != len(s) {
return d, t return d, i
} }
var endAndEsc string var endAndEsc string
@ -495,33 +496,34 @@ func tCSSStr(c context, s []byte) (context, []byte) {
panic(c.state.String()) panic(c.state.String())
} }
b := s k := 0
for { for {
i := bytes.IndexAny(b, endAndEsc) i := k + bytes.IndexAny(s[k:], endAndEsc)
if i == -1 { if i < k {
return tURL(c, decodeCSS(b)) c, nread := tURL(c, decodeCSS(s[k:]))
return c, k + nread
} }
if b[i] == '\\' { if s[i] == '\\' {
i++ i++
if i == len(b) { if i == len(s) {
return context{ return context{
state: stateError, state: stateError,
err: errorf(ErrPartialEscape, 0, "unfinished escape sequence in CSS string: %q", s), err: errorf(ErrPartialEscape, 0, "unfinished escape sequence in CSS string: %q", s),
}, nil }, len(s)
} }
} else { } else {
c.state = stateCSS c.state = stateCSS
return c, b[i+1:] return c, i + 1
} }
c, _ = tURL(c, decodeCSS(b[:i+1])) c, _ = tURL(c, decodeCSS(s[:i+1]))
b = b[i+1:] k = i + 1
} }
panic("unreachable") panic("unreachable")
} }
// tError is the context transition function for the error state. // tError is the context transition function for the error state.
func tError(c context, s []byte) (context, []byte) { func tError(c context, s []byte) (context, int) {
return c, nil return c, len(s)
} }
// eatAttrName returns the largest j such that s[i:j] is an attribute name. // eatAttrName returns the largest j such that s[i:j] is an attribute name.