parse.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  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. module.exports = function(input) {
  11. var tokens = [];
  12. var value = input;
  13. var next, quote, prev, token, escape, escapePos, whitespacePos;
  14. var pos = 0;
  15. var code = value.charCodeAt(pos);
  16. var max = value.length;
  17. var stack = [{ nodes: tokens }];
  18. var balanced = 0;
  19. var parent;
  20. var name = "";
  21. var before = "";
  22. var after = "";
  23. while (pos < max) {
  24. // Whitespaces
  25. if (code <= 32) {
  26. next = pos;
  27. do {
  28. next += 1;
  29. code = value.charCodeAt(next);
  30. } while (code <= 32);
  31. token = value.slice(pos, next);
  32. prev = tokens[tokens.length - 1];
  33. if (code === closeParentheses && balanced) {
  34. after = token;
  35. } else if (prev && prev.type === "div") {
  36. prev.after = token;
  37. } else if (
  38. code === comma ||
  39. code === colon ||
  40. (code === slash && value.charCodeAt(next + 1) !== star)
  41. ) {
  42. before = token;
  43. } else {
  44. tokens.push({
  45. type: "space",
  46. sourceIndex: pos,
  47. value: token
  48. });
  49. }
  50. pos = next;
  51. // Quotes
  52. } else if (code === singleQuote || code === doubleQuote) {
  53. next = pos;
  54. quote = code === singleQuote ? "'" : '"';
  55. token = {
  56. type: "string",
  57. sourceIndex: pos,
  58. quote: quote
  59. };
  60. do {
  61. escape = false;
  62. next = value.indexOf(quote, next + 1);
  63. if (~next) {
  64. escapePos = next;
  65. while (value.charCodeAt(escapePos - 1) === backslash) {
  66. escapePos -= 1;
  67. escape = !escape;
  68. }
  69. } else {
  70. value += quote;
  71. next = value.length - 1;
  72. token.unclosed = true;
  73. }
  74. } while (escape);
  75. token.value = value.slice(pos + 1, next);
  76. tokens.push(token);
  77. pos = next + 1;
  78. code = value.charCodeAt(pos);
  79. // Comments
  80. } else if (code === slash && value.charCodeAt(pos + 1) === star) {
  81. token = {
  82. type: "comment",
  83. sourceIndex: pos
  84. };
  85. next = value.indexOf("*/", pos);
  86. if (next === -1) {
  87. token.unclosed = true;
  88. next = value.length;
  89. }
  90. token.value = value.slice(pos + 2, next);
  91. tokens.push(token);
  92. pos = next + 2;
  93. code = value.charCodeAt(pos);
  94. // Dividers
  95. } else if (code === slash || code === comma || code === colon) {
  96. token = value[pos];
  97. tokens.push({
  98. type: "div",
  99. sourceIndex: pos - before.length,
  100. value: token,
  101. before: before,
  102. after: ""
  103. });
  104. before = "";
  105. pos += 1;
  106. code = value.charCodeAt(pos);
  107. // Open parentheses
  108. } else if (openParentheses === code) {
  109. // Whitespaces after open parentheses
  110. next = pos;
  111. do {
  112. next += 1;
  113. code = value.charCodeAt(next);
  114. } while (code <= 32);
  115. token = {
  116. type: "function",
  117. sourceIndex: pos - name.length,
  118. value: name,
  119. before: value.slice(pos + 1, next)
  120. };
  121. pos = next;
  122. if (name === "url" && code !== singleQuote && code !== doubleQuote) {
  123. next -= 1;
  124. do {
  125. escape = false;
  126. next = value.indexOf(")", next + 1);
  127. if (~next) {
  128. escapePos = next;
  129. while (value.charCodeAt(escapePos - 1) === backslash) {
  130. escapePos -= 1;
  131. escape = !escape;
  132. }
  133. } else {
  134. value += ")";
  135. next = value.length - 1;
  136. token.unclosed = true;
  137. }
  138. } while (escape);
  139. // Whitespaces before closed
  140. whitespacePos = next;
  141. do {
  142. whitespacePos -= 1;
  143. code = value.charCodeAt(whitespacePos);
  144. } while (code <= 32);
  145. if (pos !== whitespacePos + 1) {
  146. token.nodes = [
  147. {
  148. type: "word",
  149. sourceIndex: pos,
  150. value: value.slice(pos, whitespacePos + 1)
  151. }
  152. ];
  153. } else {
  154. token.nodes = [];
  155. }
  156. if (token.unclosed && whitespacePos + 1 !== next) {
  157. token.after = "";
  158. token.nodes.push({
  159. type: "space",
  160. sourceIndex: whitespacePos + 1,
  161. value: value.slice(whitespacePos + 1, next)
  162. });
  163. } else {
  164. token.after = value.slice(whitespacePos + 1, next);
  165. }
  166. pos = next + 1;
  167. code = value.charCodeAt(pos);
  168. tokens.push(token);
  169. } else {
  170. balanced += 1;
  171. token.after = "";
  172. tokens.push(token);
  173. stack.push(token);
  174. tokens = token.nodes = [];
  175. parent = token;
  176. }
  177. name = "";
  178. // Close parentheses
  179. } else if (closeParentheses === code && balanced) {
  180. pos += 1;
  181. code = value.charCodeAt(pos);
  182. parent.after = after;
  183. after = "";
  184. balanced -= 1;
  185. stack.pop();
  186. parent = stack[balanced];
  187. tokens = parent.nodes;
  188. // Words
  189. } else {
  190. next = pos;
  191. do {
  192. if (code === backslash) {
  193. next += 1;
  194. }
  195. next += 1;
  196. code = value.charCodeAt(next);
  197. } while (
  198. next < max &&
  199. !(
  200. code <= 32 ||
  201. code === singleQuote ||
  202. code === doubleQuote ||
  203. code === comma ||
  204. code === colon ||
  205. code === slash ||
  206. code === openParentheses ||
  207. (code === closeParentheses && balanced)
  208. )
  209. );
  210. token = value.slice(pos, next);
  211. if (openParentheses === code) {
  212. name = token;
  213. } else {
  214. tokens.push({
  215. type: "word",
  216. sourceIndex: pos,
  217. value: token
  218. });
  219. }
  220. pos = next;
  221. }
  222. }
  223. for (pos = stack.length - 1; pos; pos -= 1) {
  224. stack[pos].unclosed = true;
  225. }
  226. return stack[0].nodes;
  227. };