common.go (12967B)
1 package minify 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 7 "github.com/tdewolff/parse/v2" 8 "github.com/tdewolff/parse/v2/strconv" 9 ) 10 11 var ( 12 textMimeBytes = []byte("text/plain") 13 charsetASCIIBytes = []byte("charset=us-ascii") 14 dataBytes = []byte("data:") 15 base64Bytes = []byte(";base64") 16 ) 17 18 // Epsilon is the closest number to zero that is not considered to be zero. 19 var Epsilon = 0.00001 20 21 // Mediatype minifies a given mediatype by removing all whitespace and lowercasing all parts except strings (which may be case sensitive). 22 func Mediatype(b []byte) []byte { 23 j := 0 24 inString := false 25 start, lastString := 0, 0 26 for i, c := range b { 27 if !inString && parse.IsWhitespace(c) { 28 if start != 0 { 29 j += copy(b[j:], b[start:i]) 30 } else { 31 j += i 32 } 33 start = i + 1 34 } else if c == '"' { 35 inString = !inString 36 if inString { 37 parse.ToLower(b[lastString:i]) 38 } else { 39 lastString = j + (i + 1 - start) 40 } 41 } 42 } 43 if start != 0 { 44 j += copy(b[j:], b[start:]) 45 parse.ToLower(b[lastString:j]) 46 return b[:j] 47 } 48 parse.ToLower(b[lastString:]) 49 return b 50 } 51 52 // DataURI minifies a data URI and calls a minifier by the specified mediatype. Specifications: https://www.ietf.org/rfc/rfc2397.txt. 53 func DataURI(m *M, dataURI []byte) []byte { 54 origData := parse.Copy(dataURI) 55 mediatype, data, err := parse.DataURI(dataURI) 56 if err != nil { 57 return dataURI 58 } 59 60 data, _ = m.Bytes(string(mediatype), data) 61 base64Len := len(";base64") + base64.StdEncoding.EncodedLen(len(data)) 62 asciiLen := len(data) 63 for _, c := range data { 64 if parse.DataURIEncodingTable[c] { 65 asciiLen += 2 66 } 67 if asciiLen > base64Len { 68 break 69 } 70 } 71 if len(origData) < base64Len && len(origData) < asciiLen { 72 return origData 73 } 74 if base64Len < asciiLen { 75 encoded := make([]byte, base64Len-len(";base64")) 76 base64.StdEncoding.Encode(encoded, data) 77 data = encoded 78 mediatype = append(mediatype, base64Bytes...) 79 } else { 80 data = parse.EncodeURL(data, parse.DataURIEncodingTable) 81 } 82 if len("text/plain") <= len(mediatype) && parse.EqualFold(mediatype[:len("text/plain")], textMimeBytes) { 83 mediatype = mediatype[len("text/plain"):] 84 } 85 for i := 0; i+len(";charset=us-ascii") <= len(mediatype); i++ { 86 // must start with semicolon and be followed by end of mediatype or semicolon 87 if mediatype[i] == ';' && parse.EqualFold(mediatype[i+1:i+len(";charset=us-ascii")], charsetASCIIBytes) && (i+len(";charset=us-ascii") >= len(mediatype) || mediatype[i+len(";charset=us-ascii")] == ';') { 88 mediatype = append(mediatype[:i], mediatype[i+len(";charset=us-ascii"):]...) 89 break 90 } 91 } 92 return append(append(append(dataBytes, mediatype...), ','), data...) 93 } 94 95 // MaxInt is the maximum value of int. 96 const MaxInt = int(^uint(0) >> 1) 97 98 // MinInt is the minimum value of int. 99 const MinInt = -MaxInt - 1 100 101 // Decimal minifies a given byte slice containing a decimal and removes superfluous characters. It differs from Number in that it does not parse exponents. 102 // It does not parse or output exponents. prec is the number of significant digits. When prec is zero it will keep all digits. Only digits after the dot can be removed to reach the number of significant digits. Very large number may thus have more significant digits. 103 func Decimal(num []byte, prec int) []byte { 104 if len(num) <= 1 { 105 return num 106 } 107 108 // omit first + and register mantissa start and end, whether it's negative and the exponent 109 neg := false 110 start := 0 111 dot := -1 112 end := len(num) 113 if 0 < end && (num[0] == '+' || num[0] == '-') { 114 if num[0] == '-' { 115 neg = true 116 } 117 start++ 118 } 119 for i, c := range num[start:] { 120 if c == '.' { 121 dot = start + i 122 break 123 } 124 } 125 if dot == -1 { 126 dot = end 127 } 128 129 // trim leading zeros but leave at least one digit 130 for start < end-1 && num[start] == '0' { 131 start++ 132 } 133 // trim trailing zeros 134 i := end - 1 135 for ; dot < i; i-- { 136 if num[i] != '0' { 137 end = i + 1 138 break 139 } 140 } 141 if i == dot { 142 end = dot 143 if start == end { 144 num[start] = '0' 145 return num[start : start+1] 146 } 147 } else if start == end-1 && num[start] == '0' { 148 return num[start:end] 149 } 150 151 // apply precision 152 if 0 < prec && dot <= start+prec { 153 precEnd := start + prec + 1 // include dot 154 if dot == start { // for numbers like .012 155 digit := start + 1 156 for digit < end && num[digit] == '0' { 157 digit++ 158 } 159 precEnd = digit + prec 160 } 161 if precEnd < end { 162 end = precEnd 163 164 // process either an increase from a lesser significant decimal (>= 5) 165 // or remove trailing zeros after the dot, or both 166 i := end - 1 167 inc := '5' <= num[end] 168 for ; start < i; i-- { 169 if i == dot { 170 // no-op 171 } else if inc && num[i] != '9' { 172 num[i]++ 173 inc = false 174 break 175 } else if inc && i < dot { // end inc for integer 176 num[i] = '0' 177 } else if !inc && (i < dot || num[i] != '0') { 178 break 179 } 180 } 181 if i < dot { 182 end = dot 183 } else { 184 end = i + 1 185 } 186 187 if inc { 188 if dot == start && end == start+1 { 189 num[start] = '1' 190 } else if num[start] == '9' { 191 num[start] = '1' 192 num[start+1] = '0' 193 end++ 194 } else { 195 num[start]++ 196 } 197 } 198 } 199 } 200 201 if neg { 202 start-- 203 num[start] = '-' 204 } 205 return num[start:end] 206 } 207 208 // Number minifies a given byte slice containing a number and removes superfluous characters. 209 func Number(num []byte, prec int) []byte { 210 if len(num) <= 1 { 211 return num 212 } 213 214 // omit first + and register mantissa start and end, whether it's negative and the exponent 215 neg := false 216 start := 0 217 dot := -1 218 end := len(num) 219 origExp := 0 220 if num[0] == '+' || num[0] == '-' { 221 if num[0] == '-' { 222 neg = true 223 } 224 start++ 225 } 226 for i, c := range num[start:] { 227 if c == '.' { 228 dot = start + i 229 } else if c == 'e' || c == 'E' { 230 end = start + i 231 i += start + 1 232 if i < len(num) && num[i] == '+' { 233 i++ 234 } 235 if tmpOrigExp, n := strconv.ParseInt(num[i:]); 0 < n && int64(MinInt) <= tmpOrigExp && tmpOrigExp <= int64(MaxInt) { 236 // range checks for when int is 32 bit 237 origExp = int(tmpOrigExp) 238 } else { 239 return num 240 } 241 break 242 } 243 } 244 if dot == -1 { 245 dot = end 246 } 247 248 // trim leading zeros but leave at least one digit 249 for start < end-1 && num[start] == '0' { 250 start++ 251 } 252 // trim trailing zeros 253 i := end - 1 254 for ; dot < i; i-- { 255 if num[i] != '0' { 256 end = i + 1 257 break 258 } 259 } 260 if i == dot { 261 end = dot 262 if start == end { 263 num[start] = '0' 264 return num[start : start+1] 265 } 266 } else if start == end-1 && num[start] == '0' { 267 return num[start:end] 268 } 269 270 // apply precision 271 if 0 < prec { //&& (dot <= start+prec || start+prec+1 < dot || 0 < origExp) { // don't minify 9 to 10, but do 999 to 1e3 and 99e1 to 1e3 272 precEnd := start + prec 273 if dot == start { // for numbers like .012 274 digit := start + 1 275 for digit < end && num[digit] == '0' { 276 digit++ 277 } 278 precEnd = digit + prec 279 } else if dot < precEnd { // for numbers where precision will include the dot 280 precEnd++ 281 } 282 if precEnd < end && (dot < end || 1 < dot-precEnd+origExp) { // do not minify 9=>10 or 99=>100 or 9e1=>1e2 (but 90), but 999=>1e3 and 99e1=>1e3 283 end = precEnd 284 inc := '5' <= num[end] 285 if dot == end { 286 inc = end+1 < len(num) && '5' <= num[end+1] 287 } 288 if precEnd < dot { 289 origExp += dot - precEnd 290 dot = precEnd 291 } 292 // process either an increase from a lesser significant decimal (>= 5) 293 // and remove trailing zeros 294 i := end - 1 295 for ; start < i; i-- { 296 if i == dot { 297 // no-op 298 } else if inc && num[i] != '9' { 299 num[i]++ 300 inc = false 301 break 302 } else if !inc && num[i] != '0' { 303 break 304 } 305 } 306 end = i + 1 307 if end < dot { 308 origExp += dot - end 309 dot = end 310 } 311 if inc { // single digit left 312 if dot == start { 313 num[start] = '1' 314 dot = start + 1 315 } else if num[start] == '9' { 316 num[start] = '1' 317 origExp++ 318 } else { 319 num[start]++ 320 } 321 } 322 } 323 } 324 325 // n is the number of significant digits 326 // normExp would be the exponent if it were normalised (0.1 <= f < 1) 327 n := 0 328 normExp := 0 329 if dot == start { 330 for i = dot + 1; i < end; i++ { 331 if num[i] != '0' { 332 n = end - i 333 normExp = dot - i + 1 334 break 335 } 336 } 337 } else if dot == end { 338 normExp = end - start 339 for i = end - 1; start <= i; i-- { 340 if num[i] != '0' { 341 n = i + 1 - start 342 end = i + 1 343 break 344 } 345 } 346 } else { 347 n = end - start - 1 348 normExp = dot - start 349 } 350 351 if origExp < 0 && (normExp < MinInt-origExp || normExp-n < MinInt-origExp) || 0 < origExp && (MaxInt-origExp < normExp || MaxInt-origExp < normExp-n) { 352 return num // exponent overflow 353 } 354 normExp += origExp 355 356 // intExp would be the exponent if it were an integer 357 intExp := normExp - n 358 lenIntExp := strconv.LenInt(int64(intExp)) 359 lenNormExp := strconv.LenInt(int64(normExp)) 360 361 // there are three cases to consider when printing the number 362 // case 1: without decimals and with a positive exponent (large numbers: 5e4) 363 // case 2: with decimals and with a negative exponent (small numbers with many digits: .123456e-4) 364 // case 3: with decimals and without an exponent (around zero: 5.6) 365 // case 4: without decimals and with a negative exponent (small numbers: 123456e-9) 366 if n <= normExp { 367 // case 1: print number with positive exponent 368 if dot < end { 369 // remove dot, either from the front or copy the smallest part 370 if dot == start { 371 start = end - n 372 } else if dot-start < end-dot-1 { 373 copy(num[start+1:], num[start:dot]) 374 start++ 375 } else { 376 copy(num[dot:], num[dot+1:end]) 377 end-- 378 } 379 } 380 if n+3 <= normExp { 381 num[end] = 'e' 382 end++ 383 for i := end + lenIntExp - 1; end <= i; i-- { 384 num[i] = byte(intExp%10) + '0' 385 intExp /= 10 386 } 387 end += lenIntExp 388 } else if n+2 == normExp { 389 num[end] = '0' 390 num[end+1] = '0' 391 end += 2 392 } else if n+1 == normExp { 393 num[end] = '0' 394 end++ 395 } 396 } else if normExp < -3 && lenNormExp < lenIntExp && dot < end { 397 // case 2: print normalized number (0.1 <= f < 1) 398 zeroes := -normExp + origExp 399 if 0 < zeroes { 400 copy(num[start+1:], num[start+1+zeroes:end]) 401 end -= zeroes 402 } else if zeroes < 0 { 403 copy(num[start+1:], num[start:dot]) 404 num[start] = '.' 405 } 406 num[end] = 'e' 407 num[end+1] = '-' 408 end += 2 409 for i := end + lenNormExp - 1; end <= i; i-- { 410 num[i] = -byte(normExp%10) + '0' 411 normExp /= 10 412 } 413 end += lenNormExp 414 } else if -lenIntExp-1 <= normExp { 415 // case 3: print number without exponent 416 zeroes := -normExp 417 if 0 < zeroes { 418 // dot placed at the front and negative exponent, adding zeroes 419 newDot := end - n - zeroes - 1 420 if newDot != dot { 421 d := start - newDot 422 if 0 < d { 423 if dot < end { 424 // copy original digits after the dot towards the end 425 copy(num[dot+1+d:], num[dot+1:end]) 426 if start < dot { 427 // copy original digits before the dot towards the end 428 copy(num[start+d+1:], num[start:dot]) 429 } 430 } else if start < dot { 431 // copy original digits before the dot towards the end 432 copy(num[start+d:], num[start:dot]) 433 } 434 newDot = start 435 end += d 436 } else { 437 start += -d 438 } 439 num[newDot] = '.' 440 for i := 0; i < zeroes; i++ { 441 num[newDot+1+i] = '0' 442 } 443 } 444 } else { 445 // dot placed in the middle of the number 446 if dot == start { 447 // when there are zeroes after the dot 448 dot = end - n - 1 449 start = dot 450 } else if end <= dot { 451 // when input has no dot in it 452 dot = end 453 end++ 454 } 455 newDot := start + normExp 456 // move digits between dot and newDot towards the end 457 if dot < newDot { 458 copy(num[dot:], num[dot+1:newDot+1]) 459 } else if newDot < dot { 460 copy(num[newDot+1:], num[newDot:dot]) 461 } 462 num[newDot] = '.' 463 } 464 } else { 465 // case 4: print number with negative exponent 466 // find new end, considering moving numbers to the front, removing the dot and increasing the length of the exponent 467 newEnd := end 468 if dot == start { 469 newEnd = start + n 470 } else { 471 newEnd-- 472 } 473 newEnd += 2 + lenIntExp 474 475 exp := intExp 476 lenExp := lenIntExp 477 if newEnd < len(num) { 478 // it saves space to convert the decimal to an integer and decrease the exponent 479 if dot < end { 480 if dot == start { 481 copy(num[start:], num[end-n:end]) 482 end = start + n 483 } else { 484 copy(num[dot:], num[dot+1:end]) 485 end-- 486 } 487 } 488 } else { 489 // it does not save space and will panic, so we revert to the original representation 490 exp = origExp 491 lenExp = 1 492 if origExp <= -10 || 10 <= origExp { 493 lenExp = strconv.LenInt(int64(origExp)) 494 } 495 } 496 num[end] = 'e' 497 num[end+1] = '-' 498 end += 2 499 for i := end + lenExp - 1; end <= i; i-- { 500 num[i] = -byte(exp%10) + '0' 501 exp /= 10 502 } 503 end += lenExp 504 } 505 506 if neg { 507 start-- 508 num[start] = '-' 509 } 510 return num[start:end] 511 } 512 513 func UpdateErrorPosition(err error, input *parse.Input, offset int) error { 514 if perr, ok := err.(*parse.Error); ok { 515 r := bytes.NewBuffer(input.Bytes()) 516 line, column, _ := parse.Position(r, offset) 517 perr.Line += line - 1 518 perr.Column += column - 1 519 return perr 520 } 521 return err 522 }