util.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. 'use strict'
  2. var config
  3. var CHAR_COMMENT_REPLACEMENT = '\uFFFD' // �
  4. var CHAR_TOKEN_REPLACEMENT = '\u00A4'// ¤
  5. var CHAR_TOKEN_START = '\u00AB' // «
  6. var CHAR_TOKEN_END = '\u00BB' // »
  7. var REGEX_COMMENT_REPLACEMENT = new RegExp(CHAR_COMMENT_REPLACEMENT, 'ig')
  8. var REGEX_TOKEN_REPLACEMENT = new RegExp(CHAR_TOKEN_REPLACEMENT, 'ig')
  9. var PATTERN_NUMBER = '\\-?(\\d*?\\.\\d+|\\d+)'
  10. var PATTERN_NUMBER_WITH_CALC = '(calc' + CHAR_TOKEN_REPLACEMENT + ')|(' + PATTERN_NUMBER + ')(?!d\\()'
  11. var PATTERN_TOKEN = CHAR_TOKEN_START + '\\d+:\\d+' + CHAR_TOKEN_END // «offset:index»
  12. var PATTERN_TOKEN_WITH_NAME = '\\w*?' + CHAR_TOKEN_START + '\\d+:\\d+' + CHAR_TOKEN_END // «offset:index»
  13. var REGEX_COMMENT = /\/\*[^]*?\*\//igm // none-greedy
  14. var REGEX_DIRECTIVE = /\/\*\s*(?:!)?\s*rtl:[^]*?\*\//img
  15. var REGEX_ESCAPE = /[.*+?^${}()|[\]\\]/g
  16. var REGEX_FUNCTION = /\([^\(\)]+\)/i
  17. var REGEX_HEX_COLOR = /#[a-f0-9]{3,6}/ig
  18. var REGEX_CALC = /calc/
  19. var REGEX_TOKENS = new RegExp(PATTERN_TOKEN, 'ig')
  20. var REGEX_TOKENS_WITH_NAME = new RegExp(PATTERN_TOKEN_WITH_NAME, 'ig')
  21. var REGEX_COMPLEMENT = new RegExp(PATTERN_NUMBER_WITH_CALC, 'i')
  22. var REGEX_NEGATE_ALL = new RegExp(PATTERN_NUMBER_WITH_CALC, 'ig')
  23. var REGEX_NEGATE_ONE = new RegExp(PATTERN_NUMBER_WITH_CALC, 'i')
  24. var DEFAULT_STRING_MAP_OPTIONS = { scope: '*', ignoreCase: true }
  25. var TOKEN_ID = 0
  26. function compare (what, to, ignoreCase) {
  27. if (ignoreCase) {
  28. return what.toLowerCase() === to.toLowerCase()
  29. }
  30. return what === to
  31. }
  32. function escapeRegExp (string) {
  33. return string.replace(REGEX_ESCAPE, '\\$&')
  34. }
  35. module.exports = {
  36. extend: function (dest, src) {
  37. if (typeof dest === 'undefined' || typeof dest !== 'object') {
  38. dest = {}
  39. }
  40. for (var prop in src) {
  41. if (!dest.hasOwnProperty(prop)) {
  42. dest[prop] = src[prop]
  43. }
  44. }
  45. return dest
  46. },
  47. swap: function (value, a, b, options) {
  48. var expr = escapeRegExp(a) + '|' + escapeRegExp(b)
  49. options = options || DEFAULT_STRING_MAP_OPTIONS
  50. var greedy = options.hasOwnProperty('greedy') ? options.greedy : config.greedy
  51. if (!greedy) {
  52. expr = '\\b(' + expr + ')\\b'
  53. }
  54. var flags = options.ignoreCase ? 'img' : 'mg'
  55. return value.replace(new RegExp(expr, flags), function (m) { return compare(m, a, options.ignoreCase) ? b : a })
  56. },
  57. swapLeftRight: function (value) {
  58. return this.swap(value, 'left', 'right')
  59. },
  60. swapLtrRtl: function (value) {
  61. return this.swap(value, 'ltr', 'rtl')
  62. },
  63. applyStringMap: function (value, isUrl) {
  64. var result = value
  65. for (var x = 0; x < config.stringMap.length; x++) {
  66. var map = config.stringMap[x]
  67. var options = this.extend(map.options, DEFAULT_STRING_MAP_OPTIONS)
  68. if (options.scope === '*' || (isUrl && options.scope === 'url') || (!isUrl && options.scope === 'selector')) {
  69. if (Array.isArray(map.search) && Array.isArray(map.replace)) {
  70. for (var mapIndex = 0; mapIndex < map.search.length; mapIndex++) {
  71. result = this.swap(result, map.search[mapIndex], map.replace[mapIndex % map.search.length], options)
  72. }
  73. } else {
  74. result = this.swap(result, map.search, map.replace, options)
  75. }
  76. if (map.exclusive === true) {
  77. break
  78. }
  79. }
  80. }
  81. return result
  82. },
  83. negate: function (value) {
  84. var state = this.saveTokens(value)
  85. state.value = state.value.replace(REGEX_NEGATE_ONE, function (num) {
  86. return REGEX_TOKEN_REPLACEMENT.test(num) ? num.replace(REGEX_TOKEN_REPLACEMENT, function (m) { return '(-1*' + m + ')' }) : parseFloat(num, 10) * -1
  87. })
  88. return this.restoreTokens(state)
  89. },
  90. negateAll: function (value) {
  91. var state = this.saveTokens(value)
  92. state.value = state.value.replace(REGEX_NEGATE_ALL, function (num) {
  93. return REGEX_TOKEN_REPLACEMENT.test(num) ? num.replace(REGEX_TOKEN_REPLACEMENT, function (m) { return '(-1*' + m + ')' }) : parseFloat(num, 10) * -1
  94. })
  95. return this.restoreTokens(state)
  96. },
  97. complement: function (value) {
  98. var state = this.saveTokens(value)
  99. state.value = state.value.replace(REGEX_COMPLEMENT, function (num) {
  100. return REGEX_TOKEN_REPLACEMENT.test(num) ? num.replace(REGEX_TOKEN_REPLACEMENT, function (m) { return '(100% - ' + m + ')' }) : 100 - parseFloat(num, 10)
  101. })
  102. return this.restoreTokens(state)
  103. },
  104. flipLength: function (value) {
  105. return config.useCalc ? 'calc(100% - ' + value + ')' : value
  106. },
  107. save: function (what, who, replacement, restorer, exclude) {
  108. var state = {
  109. value: who,
  110. store: [],
  111. replacement: replacement,
  112. restorer: restorer
  113. }
  114. state.value = state.value.replace(what, function (c) {
  115. if (exclude && c.match(exclude)) {
  116. return c
  117. } else {
  118. state.store.push(c); return state.replacement
  119. }
  120. })
  121. return state
  122. },
  123. restore: function (state) {
  124. var index = 0
  125. var result = state.value.replace(state.restorer, function () {
  126. return state.store[index++]
  127. })
  128. state.store.length = 0
  129. return result
  130. },
  131. saveComments: function (value) {
  132. return this.save(REGEX_COMMENT, value, CHAR_COMMENT_REPLACEMENT, REGEX_COMMENT_REPLACEMENT)
  133. },
  134. restoreComments: function (state) {
  135. return this.restore(state)
  136. },
  137. saveTokens: function (value, excludeCalc) {
  138. return excludeCalc === true
  139. ? this.save(REGEX_TOKENS_WITH_NAME, value, CHAR_TOKEN_REPLACEMENT, REGEX_TOKEN_REPLACEMENT, REGEX_CALC)
  140. : this.save(REGEX_TOKENS, value, CHAR_TOKEN_REPLACEMENT, REGEX_TOKEN_REPLACEMENT)
  141. },
  142. restoreTokens: function (state) {
  143. return this.restore(state)
  144. },
  145. guard: function (what, who, indexed) {
  146. var state = {
  147. value: who,
  148. store: [],
  149. offset: TOKEN_ID++,
  150. token: CHAR_TOKEN_START + TOKEN_ID,
  151. indexed: indexed === true
  152. }
  153. if (state.indexed === true) {
  154. while (what.test(state.value)) {
  155. state.value = state.value.replace(what, function (m) { state.store.push(m); return state.token + ':' + state.store.length + CHAR_TOKEN_END })
  156. }
  157. } else {
  158. state.value = state.value.replace(what, function (m) { state.store.push(m); return state.token + CHAR_TOKEN_END })
  159. }
  160. return state
  161. },
  162. unguard: function (state, callback) {
  163. if (state.indexed === true) {
  164. var detokenizer = new RegExp('(\\w*?)' + state.token + ':(\\d+)' + CHAR_TOKEN_END, 'i')
  165. while (detokenizer.test(state.value)) {
  166. state.value = state.value.replace(detokenizer, function (match, name, index) {
  167. var value = state.store[index - 1]
  168. if (typeof callback === 'function') {
  169. return name + callback(value, name)
  170. }
  171. return name + value
  172. })
  173. }
  174. return state.value
  175. } else {
  176. return state.value.replace(new RegExp('(\\w*?)' + state.token + CHAR_TOKEN_END, 'i'), function (match, name) {
  177. var value = state.store.shift()
  178. if (typeof callback === 'function') {
  179. return name + callback(value, name)
  180. }
  181. return name + value
  182. })
  183. }
  184. },
  185. guardHexColors: function (value) {
  186. return this.guard(REGEX_HEX_COLOR, value, true)
  187. },
  188. unguardHexColors: function (state, callback) {
  189. return this.unguard(state, callback)
  190. },
  191. guardFunctions: function (value) {
  192. return this.guard(REGEX_FUNCTION, value, true)
  193. },
  194. unguardFunctions: function (state, callback) {
  195. return this.unguard(state, callback)
  196. },
  197. trimDirective: function (value) {
  198. return value.replace(REGEX_DIRECTIVE, '')
  199. },
  200. regexCache: {},
  201. regexDirective: function (name) {
  202. // /(?:\/\*(?:!)?rtl:ignore(?::)?)([^]*?)(?:\*\/)/img
  203. this.regexCache[name] = this.regexCache[name] || new RegExp('(?:\\/\\*\\s*(?:!)?\\s*rtl:' + (name ? escapeRegExp(name) + '(?::)?' : '') + ')([^]*?)(?:\\*\\/)', 'img')
  204. return this.regexCache[name]
  205. },
  206. regex: function (what, options) {
  207. what = what || []
  208. var expression = ''
  209. for (var x = 0; x < what.length; x++) {
  210. switch (what[x]) {
  211. case 'percent':
  212. expression += '|(' + PATTERN_NUMBER + '%)'
  213. break
  214. case 'length':
  215. expression += '|(' + PATTERN_NUMBER + ')(?:ex|ch|r?em|vh|vw|vmin|vmax|px|mm|cm|in|pt|pc)?'
  216. break
  217. case 'number':
  218. expression += '|(' + PATTERN_NUMBER + ')'
  219. break
  220. case 'position':
  221. expression += '|(left|center|right|top|bottom)'
  222. break
  223. case 'calc':
  224. expression += '|(calc' + PATTERN_TOKEN + ')'
  225. break
  226. }
  227. }
  228. return new RegExp(expression.slice(1), options)
  229. },
  230. isLastOfType: function (node) {
  231. var isLast = true
  232. var next = node.next()
  233. while (next) {
  234. if (next && next.type === node.type) {
  235. isLast = false
  236. break
  237. }
  238. next = next.next()
  239. }
  240. return isLast
  241. },
  242. /**
  243. * Simple breakable each: returning false in the callback will break the loop
  244. * returns false if the loop was broken, otherwise true
  245. */
  246. each: function (array, callback) {
  247. for (var len = 0; len < array.length; len++) {
  248. if (callback(array[len]) === false) {
  249. return false
  250. }
  251. }
  252. return true
  253. }
  254. }
  255. module.exports.configure = function (configuration) {
  256. config = configuration
  257. return this
  258. }