gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }