parse.js 9.5 KB

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