parse.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. var openParentheses = "(".charCodeAt(0);
  2. var closeParentheses = ")".charCodeAt(0);
  3. var singleQuote = "'".charCodeAt(0);
  4. var doubleQuote = '"'.charCodeAt(0);
  5. var backslash = "\\".charCodeAt(0);
  6. var slash = "/".charCodeAt(0);
  7. var comma = ",".charCodeAt(0);
  8. var colon = ":".charCodeAt(0);
  9. var star = "*".charCodeAt(0);
  10. var uLower = "u".charCodeAt(0);
  11. var uUpper = "U".charCodeAt(0);
  12. var plus = "+".charCodeAt(0);
  13. var isUnicodeRange = /^[a-f0-9?-]+$/i;
  14. module.exports = function(input) {
  15. var tokens = [];
  16. var value = input;
  17. var next,
  18. quote,
  19. prev,
  20. token,
  21. escape,
  22. escapePos,
  23. whitespacePos,
  24. parenthesesOpenPos;
  25. var pos = 0;
  26. var code = value.charCodeAt(pos);
  27. var max = value.length;
  28. var stack = [{ nodes: tokens }];
  29. var balanced = 0;
  30. var parent;
  31. var name = "";
  32. var before = "";
  33. var after = "";
  34. while (pos < max) {
  35. // Whitespaces
  36. if (code <= 32) {
  37. next = pos;
  38. do {
  39. next += 1;
  40. code = value.charCodeAt(next);
  41. } while (code <= 32);
  42. token = value.slice(pos, next);
  43. prev = tokens[tokens.length - 1];
  44. if (code === closeParentheses && balanced) {
  45. after = token;
  46. } else if (prev && prev.type === "div") {
  47. prev.after = token;
  48. } else if (
  49. code === comma ||
  50. code === colon ||
  51. (code === slash &&
  52. value.charCodeAt(next + 1) !== star &&
  53. (!parent ||
  54. (parent && parent.type === "function" && parent.value !== "calc")))
  55. ) {
  56. before = token;
  57. } else {
  58. tokens.push({
  59. type: "space",
  60. sourceIndex: pos,
  61. value: token
  62. });
  63. }
  64. pos = next;
  65. // Quotes
  66. } else if (code === singleQuote || code === doubleQuote) {
  67. next = pos;
  68. quote = code === singleQuote ? "'" : '"';
  69. token = {
  70. type: "string",
  71. sourceIndex: pos,
  72. quote: quote
  73. };
  74. do {
  75. escape = false;
  76. next = value.indexOf(quote, next + 1);
  77. if (~next) {
  78. escapePos = next;
  79. while (value.charCodeAt(escapePos - 1) === backslash) {
  80. escapePos -= 1;
  81. escape = !escape;
  82. }
  83. } else {
  84. value += quote;
  85. next = value.length - 1;
  86. token.unclosed = true;
  87. }
  88. } while (escape);
  89. token.value = value.slice(pos + 1, next);
  90. tokens.push(token);
  91. pos = next + 1;
  92. code = value.charCodeAt(pos);
  93. // Comments
  94. } else if (code === slash && value.charCodeAt(pos + 1) === star) {
  95. token = {
  96. type: "comment",
  97. sourceIndex: pos
  98. };
  99. next = value.indexOf("*/", pos);
  100. if (next === -1) {
  101. token.unclosed = true;
  102. next = value.length;
  103. }
  104. token.value = value.slice(pos + 2, next);
  105. tokens.push(token);
  106. pos = next + 2;
  107. code = value.charCodeAt(pos);
  108. // Operation within calc
  109. } else if (
  110. (code === slash || code === star) &&
  111. parent &&
  112. parent.type === "function" &&
  113. parent.value === "calc"
  114. ) {
  115. token = value[pos];
  116. tokens.push({
  117. type: "word",
  118. sourceIndex: pos - before.length,
  119. value: token
  120. });
  121. pos += 1;
  122. code = value.charCodeAt(pos);
  123. // Dividers
  124. } else if (code === slash || code === comma || code === colon) {
  125. token = value[pos];
  126. tokens.push({
  127. type: "div",
  128. sourceIndex: pos - before.length,
  129. value: token,
  130. before: before,
  131. after: ""
  132. });
  133. before = "";
  134. pos += 1;
  135. code = value.charCodeAt(pos);
  136. // Open parentheses
  137. } else if (openParentheses === code) {
  138. // Whitespaces after open parentheses
  139. next = pos;
  140. do {
  141. next += 1;
  142. code = value.charCodeAt(next);
  143. } while (code <= 32);
  144. parenthesesOpenPos = pos;
  145. token = {
  146. type: "function",
  147. sourceIndex: pos - name.length,
  148. value: name,
  149. before: value.slice(parenthesesOpenPos + 1, next)
  150. };
  151. pos = next;
  152. if (name === "url" && code !== singleQuote && code !== doubleQuote) {
  153. next -= 1;
  154. do {
  155. escape = false;
  156. next = value.indexOf(")", next + 1);
  157. if (~next) {
  158. escapePos = next;
  159. while (value.charCodeAt(escapePos - 1) === backslash) {
  160. escapePos -= 1;
  161. escape = !escape;
  162. }
  163. } else {
  164. value += ")";
  165. next = value.length - 1;
  166. token.unclosed = true;
  167. }
  168. } while (escape);
  169. // Whitespaces before closed
  170. whitespacePos = next;
  171. do {
  172. whitespacePos -= 1;
  173. code = value.charCodeAt(whitespacePos);
  174. } while (code <= 32);
  175. if (parenthesesOpenPos < whitespacePos) {
  176. if (pos !== whitespacePos + 1) {
  177. token.nodes = [
  178. {
  179. type: "word",
  180. sourceIndex: pos,
  181. value: value.slice(pos, whitespacePos + 1)
  182. }
  183. ];
  184. } else {
  185. token.nodes = [];
  186. }
  187. if (token.unclosed && whitespacePos + 1 !== next) {
  188. token.after = "";
  189. token.nodes.push({
  190. type: "space",
  191. sourceIndex: whitespacePos + 1,
  192. value: value.slice(whitespacePos + 1, next)
  193. });
  194. } else {
  195. token.after = value.slice(whitespacePos + 1, next);
  196. }
  197. } else {
  198. token.after = "";
  199. token.nodes = [];
  200. }
  201. pos = next + 1;
  202. code = value.charCodeAt(pos);
  203. tokens.push(token);
  204. } else {
  205. balanced += 1;
  206. token.after = "";
  207. tokens.push(token);
  208. stack.push(token);
  209. tokens = token.nodes = [];
  210. parent = token;
  211. }
  212. name = "";
  213. // Close parentheses
  214. } else if (closeParentheses === code && balanced) {
  215. pos += 1;
  216. code = value.charCodeAt(pos);
  217. parent.after = after;
  218. after = "";
  219. balanced -= 1;
  220. stack.pop();
  221. parent = stack[balanced];
  222. tokens = parent.nodes;
  223. // Words
  224. } else {
  225. next = pos;
  226. do {
  227. if (code === backslash) {
  228. next += 1;
  229. }
  230. next += 1;
  231. code = value.charCodeAt(next);
  232. } while (
  233. next < max &&
  234. !(
  235. code <= 32 ||
  236. code === singleQuote ||
  237. code === doubleQuote ||
  238. code === comma ||
  239. code === colon ||
  240. code === slash ||
  241. code === openParentheses ||
  242. (code === star &&
  243. parent &&
  244. parent.type === "function" &&
  245. parent.value === "calc") ||
  246. (code === slash &&
  247. parent.type === "function" &&
  248. parent.value === "calc") ||
  249. (code === closeParentheses && balanced)
  250. )
  251. );
  252. token = value.slice(pos, next);
  253. if (openParentheses === code) {
  254. name = token;
  255. } else if (
  256. (uLower === token.charCodeAt(0) || uUpper === token.charCodeAt(0)) &&
  257. plus === token.charCodeAt(1) &&
  258. isUnicodeRange.test(token.slice(2))
  259. ) {
  260. tokens.push({
  261. type: "unicode-range",
  262. sourceIndex: pos,
  263. value: token
  264. });
  265. } else {
  266. tokens.push({
  267. type: "word",
  268. sourceIndex: pos,
  269. value: token
  270. });
  271. }
  272. pos = next;
  273. }
  274. }
  275. for (pos = stack.length - 1; pos; pos -= 1) {
  276. stack[pos].unclosed = true;
  277. }
  278. return stack[0].nodes;
  279. };