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