parse.js 8.1 KB

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