diff options
Diffstat (limited to 'vendor/github.com/tdewolff/minify/css/css.go')
-rw-r--r-- | vendor/github.com/tdewolff/minify/css/css.go | 520 |
1 files changed, 321 insertions, 199 deletions
diff --git a/vendor/github.com/tdewolff/minify/css/css.go b/vendor/github.com/tdewolff/minify/css/css.go index de0a8c8..1c34166 100644 --- a/vendor/github.com/tdewolff/minify/css/css.go +++ b/vendor/github.com/tdewolff/minify/css/css.go @@ -4,6 +4,7 @@ package css // import "github.com/tdewolff/minify/css" import ( "bytes" "encoding/hex" + "fmt" "io" "strconv" @@ -29,16 +30,19 @@ type cssMinifier struct { w io.Writer p *css.Parser o *Minifier + + valuesBuffer []Token } //////////////////////////////////////////////////////////////// // DefaultMinifier is the default minifier. -var DefaultMinifier = &Minifier{Decimals: -1} +var DefaultMinifier = &Minifier{Decimals: -1, KeepCSS2: false} // Minifier is a CSS minifier. type Minifier struct { Decimals int + KeepCSS2 bool } // Minify minifies CSS data, it reads from r and writes to w. @@ -108,7 +112,19 @@ func (c *cssMinifier) minifyGrammar() error { if _, err := c.w.Write(data); err != nil { return err } - for _, val := range c.p.Values() { + values := c.p.Values() + if css.ToHash(data[1:]) == css.Import && len(values) == 2 && values[1].TokenType == css.URLToken { + url := values[1].Data + if url[4] != '"' && url[4] != '\'' { + url = url[3:] + url[0] = '"' + url[len(url)-1] = '"' + } else { + url = url[4 : len(url)-1] + } + values[1].Data = url + } + for _, val := range values { if _, err := c.w.Write(val.Data); err != nil { return err } @@ -216,138 +232,238 @@ func (c *cssMinifier) minifySelectors(property []byte, values []css.Token) error return nil } -func (c *cssMinifier) minifyDeclaration(property []byte, values []css.Token) error { - if len(values) == 0 { +type Token struct { + css.TokenType + Data []byte + Components []css.Token // only filled for functions +} + +func (t Token) String() string { + if len(t.Components) == 0 { + return t.TokenType.String() + "(" + string(t.Data) + ")" + } else { + return fmt.Sprint(t.Components) + } +} + +func (c *cssMinifier) minifyDeclaration(property []byte, components []css.Token) error { + if len(components) == 0 { return nil } + prop := css.ToHash(property) - inProgid := false - for i, value := range values { - if inProgid { - if value.TokenType == css.FunctionToken { - inProgid = false - } - continue - } else if value.TokenType == css.IdentToken && css.ToHash(value.Data) == css.Progid { - inProgid = true - continue + + // Strip !important from the component list, this will be added later separately + important := false + if len(components) > 2 && components[len(components)-2].TokenType == css.DelimToken && components[len(components)-2].Data[0] == '!' && css.ToHash(components[len(components)-1].Data) == css.Important { + components = components[:len(components)-2] + important = true + } + + // Check if this is a simple list of values separated by whitespace or commas, otherwise we'll not be processing + simple := true + prevSep := true + values := c.valuesBuffer[:0] + for i := 0; i < len(components); i++ { + comp := components[i] + if comp.TokenType == css.LeftParenthesisToken || comp.TokenType == css.LeftBraceToken || comp.TokenType == css.LeftBracketToken || comp.TokenType == css.RightParenthesisToken || comp.TokenType == css.RightBraceToken || comp.TokenType == css.RightBracketToken { + simple = false + break } - value.TokenType, value.Data = c.shortenToken(prop, value.TokenType, value.Data) - if prop == css.Font || prop == css.Font_Family || prop == css.Font_Weight { - if value.TokenType == css.IdentToken && (prop == css.Font || prop == css.Font_Weight) { - val := css.ToHash(value.Data) - if val == css.Normal && prop == css.Font_Weight { - // normal could also be specified for font-variant, not just font-weight - value.TokenType = css.NumberToken - value.Data = []byte("400") - } else if val == css.Bold { - value.TokenType = css.NumberToken - value.Data = []byte("700") - } - } else if value.TokenType == css.StringToken && (prop == css.Font || prop == css.Font_Family) && len(value.Data) > 2 { - unquote := true - parse.ToLower(value.Data) - s := value.Data[1 : len(value.Data)-1] - if len(s) > 0 { - for _, split := range bytes.Split(s, spaceBytes) { - val := css.ToHash(split) - // if len is zero, it contains two consecutive spaces - if val == css.Inherit || val == css.Serif || val == css.Sans_Serif || val == css.Monospace || val == css.Fantasy || val == css.Cursive || val == css.Initial || val == css.Default || - len(split) == 0 || !css.IsIdent(split) { - unquote = false - break - } + + if !prevSep && comp.TokenType != css.WhitespaceToken && comp.TokenType != css.CommaToken { + simple = false + break + } + + if comp.TokenType == css.WhitespaceToken || comp.TokenType == css.CommaToken { + prevSep = true + if comp.TokenType == css.CommaToken { + values = append(values, Token{components[i].TokenType, components[i].Data, nil}) + } + } else if comp.TokenType == css.FunctionToken { + prevSep = false + j := i + 1 + level := 0 + for ; j < len(components); j++ { + if components[j].TokenType == css.LeftParenthesisToken { + level++ + } else if components[j].TokenType == css.RightParenthesisToken { + if level == 0 { + j++ + break } + level-- } - if unquote { - value.Data = s - } - } - } else if prop == css.Outline || prop == css.Border || prop == css.Border_Bottom || prop == css.Border_Left || prop == css.Border_Right || prop == css.Border_Top { - if css.ToHash(value.Data) == css.None { - value.TokenType = css.NumberToken - value.Data = zeroBytes } + values = append(values, Token{components[i].TokenType, components[i].Data, components[i:j]}) + i = j - 1 + } else { + prevSep = false + values = append(values, Token{components[i].TokenType, components[i].Data, nil}) } - values[i].TokenType, values[i].Data = value.TokenType, value.Data } + c.valuesBuffer = values - important := false - if len(values) > 2 && values[len(values)-2].TokenType == css.DelimToken && values[len(values)-2].Data[0] == '!' && css.ToHash(values[len(values)-1].Data) == css.Important { - values = values[:len(values)-2] - important = true - } + // Do not process complex values (eg. containing blocks or is not alternated between whitespace/commas and flat values + if !simple { + if prop == css.Filter && len(components) == 11 { + if bytes.Equal(components[0].Data, []byte("progid")) && + components[1].TokenType == css.ColonToken && + bytes.Equal(components[2].Data, []byte("DXImageTransform")) && + components[3].Data[0] == '.' && + bytes.Equal(components[4].Data, []byte("Microsoft")) && + components[5].Data[0] == '.' && + bytes.Equal(components[6].Data, []byte("Alpha(")) && + bytes.Equal(parse.ToLower(components[7].Data), []byte("opacity")) && + components[8].Data[0] == '=' && + components[10].Data[0] == ')' { + components = components[6:] + components[0].Data = []byte("alpha(") + } + } - if len(values) == 1 { - if prop == css.Background && css.ToHash(values[0].Data) == css.None { - values[0].Data = backgroundNoneBytes - } else if bytes.Equal(property, msfilterBytes) { - alpha := []byte("progid:DXImageTransform.Microsoft.Alpha(Opacity=") - if values[0].TokenType == css.StringToken && bytes.HasPrefix(values[0].Data[1:len(values[0].Data)-1], alpha) { - values[0].Data = append(append([]byte{values[0].Data[0]}, []byte("alpha(opacity=")...), values[0].Data[1+len(alpha):]...) + for _, component := range components { + if _, err := c.w.Write(component.Data); err != nil { + return err } } - } else { - if prop == css.Margin || prop == css.Padding || prop == css.Border_Width { - if (values[0].TokenType == css.NumberToken || values[0].TokenType == css.DimensionToken || values[0].TokenType == css.PercentageToken) && (len(values)+1)%2 == 0 { - valid := true - for i := 1; i < len(values); i += 2 { - if values[i].TokenType != css.WhitespaceToken || values[i+1].TokenType != css.NumberToken && values[i+1].TokenType != css.DimensionToken && values[i+1].TokenType != css.PercentageToken { - valid = false + if important { + if _, err := c.w.Write([]byte("!important")); err != nil { + return err + } + } + return nil + } + + for i := range values { + values[i].TokenType, values[i].Data = c.shortenToken(prop, values[i].TokenType, values[i].Data) + } + + if len(values) > 0 { + switch prop { + case css.Font, css.Font_Weight, css.Font_Family: + if prop == css.Font { + // in "font:" shorthand all values before the size have "normal" + // as valid and, at the same time, default value, so just skip them + for i, value := range values { + if !(value.TokenType == css.IdentToken && css.ToHash(value.Data) == css.Normal) { + values = values[i:] break } } - if valid { - n := (len(values) + 1) / 2 - if n == 2 { - if bytes.Equal(values[0].Data, values[2].Data) { - values = values[:1] - } - } else if n == 3 { - if bytes.Equal(values[0].Data, values[2].Data) && bytes.Equal(values[0].Data, values[4].Data) { - values = values[:1] - } else if bytes.Equal(values[0].Data, values[4].Data) { - values = values[:3] - } - } else if n == 4 { - if bytes.Equal(values[0].Data, values[2].Data) && bytes.Equal(values[0].Data, values[4].Data) && bytes.Equal(values[0].Data, values[6].Data) { - values = values[:1] - } else if bytes.Equal(values[0].Data, values[4].Data) && bytes.Equal(values[2].Data, values[6].Data) { - values = values[:3] - } else if bytes.Equal(values[2].Data, values[6].Data) { - values = values[:5] + } + for i, value := range values { + if value.TokenType == css.IdentToken { + val := css.ToHash(value.Data) + if prop == css.Font_Weight && val == css.Normal { + values[i].TokenType = css.NumberToken + values[i].Data = []byte("400") + } else if val == css.Bold { + values[i].TokenType = css.NumberToken + values[i].Data = []byte("700") + } + } else if value.TokenType == css.StringToken && len(value.Data) > 2 { + unquote := true + parse.ToLower(value.Data) + s := value.Data[1 : len(value.Data)-1] + if len(s) > 0 { + for _, split := range bytes.Split(s, spaceBytes) { + val := css.ToHash(split) + // if len is zero, it contains two consecutive spaces + if val == css.Inherit || val == css.Serif || val == css.Sans_Serif || val == css.Monospace || val == css.Fantasy || val == css.Cursive || val == css.Initial || val == css.Default || + len(split) == 0 || !css.IsIdent(split) { + unquote = false + break + } } } + if unquote { + values[i].Data = s + } } } - } else if prop == css.Filter && len(values) == 11 { - if bytes.Equal(values[0].Data, []byte("progid")) && - values[1].TokenType == css.ColonToken && - bytes.Equal(values[2].Data, []byte("DXImageTransform")) && - values[3].Data[0] == '.' && - bytes.Equal(values[4].Data, []byte("Microsoft")) && - values[5].Data[0] == '.' && - bytes.Equal(values[6].Data, []byte("Alpha(")) && - bytes.Equal(parse.ToLower(values[7].Data), []byte("opacity")) && - values[8].Data[0] == '=' && - values[10].Data[0] == ')' { - values = values[6:] - values[0].Data = []byte("alpha(") + case css.Margin, css.Padding, css.Border_Width: + n := len(values) + if n == 2 { + if bytes.Equal(values[0].Data, values[1].Data) { + values = values[:1] + } + } else if n == 3 { + if bytes.Equal(values[0].Data, values[1].Data) && bytes.Equal(values[0].Data, values[2].Data) { + values = values[:1] + } else if bytes.Equal(values[0].Data, values[2].Data) { + values = values[:2] + } + } else if n == 4 { + if bytes.Equal(values[0].Data, values[1].Data) && bytes.Equal(values[0].Data, values[2].Data) && bytes.Equal(values[0].Data, values[3].Data) { + values = values[:1] + } else if bytes.Equal(values[0].Data, values[2].Data) && bytes.Equal(values[1].Data, values[3].Data) { + values = values[:2] + } else if bytes.Equal(values[1].Data, values[3].Data) { + values = values[:3] + } + } + case css.Outline, css.Border, css.Border_Bottom, css.Border_Left, css.Border_Right, css.Border_Top: + none := false + iZero := -1 + for i, value := range values { + if len(value.Data) == 1 && value.Data[0] == '0' { + iZero = i + } else if css.ToHash(value.Data) == css.None { + values[i].TokenType = css.NumberToken + values[i].Data = zeroBytes + none = true + } + } + if none && iZero != -1 { + values = append(values[:iZero], values[iZero+1:]...) + } + case css.Background: + ident := css.ToHash(values[0].Data) + if len(values) == 1 && (ident == css.None || ident == css.Transparent) { + values[0].Data = backgroundNoneBytes + } + case css.Box_Shadow: + if len(values) == 4 && len(values[0].Data) == 1 && values[0].Data[0] == '0' && len(values[1].Data) == 1 && values[1].Data[0] == '0' && len(values[2].Data) == 1 && values[2].Data[0] == '0' && len(values[3].Data) == 1 && values[3].Data[0] == '0' { + values = values[:2] + } + default: + if bytes.Equal(property, msfilterBytes) { + alpha := []byte("progid:DXImageTransform.Microsoft.Alpha(Opacity=") + if values[0].TokenType == css.StringToken && bytes.HasPrefix(values[0].Data[1:len(values[0].Data)-1], alpha) { + values[0].Data = append(append([]byte{values[0].Data[0]}, []byte("alpha(opacity=")...), values[0].Data[1+len(alpha):]...) + } } } } - for i := 0; i < len(values); i++ { - if values[i].TokenType == css.FunctionToken { - n, err := c.minifyFunction(values[i:]) + prevComma := true + for _, value := range values { + if !prevComma && value.TokenType != css.CommaToken { + if _, err := c.w.Write([]byte(" ")); err != nil { + return err + } + } + + if value.TokenType == css.FunctionToken { + err := c.minifyFunction(value.Components) if err != nil { return err } - i += n - 1 - } else if _, err := c.w.Write(values[i].Data); err != nil { - return err + } else { + if _, err := c.w.Write(value.Data); err != nil { + return err + } + } + + if value.TokenType == css.CommaToken { + prevComma = true + } else { + prevComma = false } } + if important { if _, err := c.w.Write([]byte("!important")); err != nil { return err @@ -356,104 +472,76 @@ func (c *cssMinifier) minifyDeclaration(property []byte, values []css.Token) err return nil } -func (c *cssMinifier) minifyFunction(values []css.Token) (int, error) { - n := 1 - simple := true - for i, value := range values[1:] { - if value.TokenType == css.RightParenthesisToken { - n++ - break - } - if i%2 == 0 && (value.TokenType != css.NumberToken && value.TokenType != css.PercentageToken) || (i%2 == 1 && value.TokenType != css.CommaToken) { - simple = false - } - n++ - } - values = values[:n] - if simple && (n-1)%2 == 0 { - fun := css.ToHash(values[0].Data[:len(values[0].Data)-1]) - nArgs := (n - 1) / 2 - if (fun == css.Rgba || fun == css.Hsla) && nArgs == 4 { - d, _ := strconv.ParseFloat(string(values[7].Data), 32) // can never fail because if simple == true than this is a NumberToken or PercentageToken - if d-1.0 > -minify.Epsilon { - if fun == css.Rgba { - values[0].Data = []byte("rgb(") - fun = css.Rgb - } else { - values[0].Data = []byte("hsl(") - fun = css.Hsl - } - values = values[:len(values)-2] - values[len(values)-1].Data = []byte(")") - nArgs = 3 - } else if d < minify.Epsilon { - values[0].Data = []byte("transparent") - values = values[:1] - fun = 0 - nArgs = 0 +func (c *cssMinifier) minifyFunction(values []css.Token) error { + n := len(values) + if n > 2 { + simple := true + for i, value := range values[1 : n-1] { + if i%2 == 0 && (value.TokenType != css.NumberToken && value.TokenType != css.PercentageToken) || (i%2 == 1 && value.TokenType != css.CommaToken) { + simple = false } } - if fun == css.Rgb && nArgs == 3 { - var err [3]error - rgb := [3]byte{} - for j := 0; j < 3; j++ { - val := values[j*2+1] - if val.TokenType == css.NumberToken { - var d int64 - d, err[j] = strconv.ParseInt(string(val.Data), 10, 32) - if d < 0 { - d = 0 - } else if d > 255 { - d = 255 - } - rgb[j] = byte(d) - } else if val.TokenType == css.PercentageToken { - var d float64 - d, err[j] = strconv.ParseFloat(string(val.Data[:len(val.Data)-1]), 32) - if d < 0.0 { - d = 0.0 - } else if d > 100.0 { - d = 100.0 + + if simple && n%2 == 1 { + fun := css.ToHash(values[0].Data[0 : len(values[0].Data)-1]) + for i := 1; i < n; i += 2 { + values[i].TokenType, values[i].Data = c.shortenToken(0, values[i].TokenType, values[i].Data) + } + + nArgs := (n - 1) / 2 + if (fun == css.Rgba || fun == css.Hsla) && nArgs == 4 { + d, _ := strconv.ParseFloat(string(values[7].Data), 32) // can never fail because if simple == true than this is a NumberToken or PercentageToken + if d-1.0 > -minify.Epsilon { + if fun == css.Rgba { + values[0].Data = []byte("rgb(") + fun = css.Rgb + } else { + values[0].Data = []byte("hsl(") + fun = css.Hsl } - rgb[j] = byte((d / 100.0 * 255.0) + 0.5) + values = values[:len(values)-2] + values[len(values)-1].Data = []byte(")") + nArgs = 3 + } else if d < minify.Epsilon { + values[0].Data = []byte("transparent") + values = values[:1] + fun = 0 + nArgs = 0 } } - if err[0] == nil && err[1] == nil && err[2] == nil { - val := make([]byte, 7) - val[0] = '#' - hex.Encode(val[1:], rgb[:]) - parse.ToLower(val) - if s, ok := ShortenColorHex[string(val)]; ok { - if _, err := c.w.Write(s); err != nil { - return 0, err - } - } else { - if len(val) == 7 && val[1] == val[2] && val[3] == val[4] && val[5] == val[6] { - val[2] = val[3] - val[3] = val[5] - val = val[:4] - } - if _, err := c.w.Write(val); err != nil { - return 0, err + if fun == css.Rgb && nArgs == 3 { + var err [3]error + rgb := [3]byte{} + for j := 0; j < 3; j++ { + val := values[j*2+1] + if val.TokenType == css.NumberToken { + var d int64 + d, err[j] = strconv.ParseInt(string(val.Data), 10, 32) + if d < 0 { + d = 0 + } else if d > 255 { + d = 255 + } + rgb[j] = byte(d) + } else if val.TokenType == css.PercentageToken { + var d float64 + d, err[j] = strconv.ParseFloat(string(val.Data[:len(val.Data)-1]), 32) + if d < 0.0 { + d = 0.0 + } else if d > 100.0 { + d = 100.0 + } + rgb[j] = byte((d / 100.0 * 255.0) + 0.5) } } - return n, nil - } - } else if fun == css.Hsl && nArgs == 3 { - if values[1].TokenType == css.NumberToken && values[3].TokenType == css.PercentageToken && values[5].TokenType == css.PercentageToken { - h, err1 := strconv.ParseFloat(string(values[1].Data), 32) - s, err2 := strconv.ParseFloat(string(values[3].Data[:len(values[3].Data)-1]), 32) - l, err3 := strconv.ParseFloat(string(values[5].Data[:len(values[5].Data)-1]), 32) - if err1 == nil && err2 == nil && err3 == nil { - r, g, b := css.HSL2RGB(h/360.0, s/100.0, l/100.0) - rgb := []byte{byte((r * 255.0) + 0.5), byte((g * 255.0) + 0.5), byte((b * 255.0) + 0.5)} + if err[0] == nil && err[1] == nil && err[2] == nil { val := make([]byte, 7) val[0] = '#' hex.Encode(val[1:], rgb[:]) parse.ToLower(val) if s, ok := ShortenColorHex[string(val)]; ok { if _, err := c.w.Write(s); err != nil { - return 0, err + return err } } else { if len(val) == 7 && val[1] == val[2] && val[3] == val[4] && val[5] == val[6] { @@ -462,20 +550,50 @@ func (c *cssMinifier) minifyFunction(values []css.Token) (int, error) { val = val[:4] } if _, err := c.w.Write(val); err != nil { - return 0, err + return err } } - return n, nil + return nil + } + } else if fun == css.Hsl && nArgs == 3 { + if values[1].TokenType == css.NumberToken && values[3].TokenType == css.PercentageToken && values[5].TokenType == css.PercentageToken { + h, err1 := strconv.ParseFloat(string(values[1].Data), 32) + s, err2 := strconv.ParseFloat(string(values[3].Data[:len(values[3].Data)-1]), 32) + l, err3 := strconv.ParseFloat(string(values[5].Data[:len(values[5].Data)-1]), 32) + if err1 == nil && err2 == nil && err3 == nil { + r, g, b := css.HSL2RGB(h/360.0, s/100.0, l/100.0) + rgb := []byte{byte((r * 255.0) + 0.5), byte((g * 255.0) + 0.5), byte((b * 255.0) + 0.5)} + val := make([]byte, 7) + val[0] = '#' + hex.Encode(val[1:], rgb[:]) + parse.ToLower(val) + if s, ok := ShortenColorHex[string(val)]; ok { + if _, err := c.w.Write(s); err != nil { + return err + } + } else { + if len(val) == 7 && val[1] == val[2] && val[3] == val[4] && val[5] == val[6] { + val[2] = val[3] + val[3] = val[5] + val = val[:4] + } + if _, err := c.w.Write(val); err != nil { + return err + } + } + return nil + } } } } } + for _, value := range values { if _, err := c.w.Write(value.Data); err != nil { - return 0, err + return err } } - return n, nil + return nil } func (c *cssMinifier) shortenToken(prop css.Hash, tt css.TokenType, data []byte) (css.TokenType, []byte) { @@ -491,11 +609,15 @@ func (c *cssMinifier) shortenToken(prop css.Hash, tt css.TokenType, data []byte) } dim := data[n:] parse.ToLower(dim) - data = minify.Number(data[:n], c.o.Decimals) - if tt == css.PercentageToken && (len(data) != 1 || data[0] != '0' || prop == css.Color) { - data = append(data, '%') - } else if tt == css.DimensionToken && (len(data) != 1 || data[0] != '0' || requiredDimension[string(dim)]) { + if !c.o.KeepCSS2 { + data = minify.Number(data[:n], c.o.Decimals) + } else { + data = minify.Decimal(data[:n], c.o.Decimals) // don't use exponents + } + if tt == css.DimensionToken && (len(data) != 1 || data[0] != '0' || !optionalZeroDimension[string(dim)] || prop == css.Flex) { data = append(data, dim...) + } else if tt == css.PercentageToken { + data = append(data, '%') // TODO: drop percentage for properties that accept <percentage> and <length> } } else if tt == css.IdentToken { //parse.ToLower(data) // TODO: not all identifiers are case-insensitive; all <custom-ident> properties are case-sensitive @@ -541,7 +663,7 @@ func (c *cssMinifier) shortenToken(prop css.Hash, tt css.TokenType, data []byte) } else if tt == css.URLToken { parse.ToLower(data[:3]) if len(data) > 10 { - uri := data[4 : len(data)-1] + uri := parse.TrimWhitespace(data[4 : len(data)-1]) delim := byte('"') if uri[0] == '\'' || uri[0] == '"' { delim = uri[0] |