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