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}