common.go 13 KB

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