UNPKG

11.8 kBJavaScriptView Raw
1//.CommonJS
2var CSSOM = {};
3///CommonJS
4
5
6/**
7 * @param {string} token
8 */
9CSSOM.parse = function parse(token) {
10
11 var i = 0;
12
13 /**
14 "before-selector" or
15 "selector" or
16 "atRule" or
17 "atBlock" or
18 "conditionBlock" or
19 "before-name" or
20 "name" or
21 "before-value" or
22 "value"
23 */
24 var state = "before-selector";
25
26 var index;
27 var buffer = "";
28 var valueParenthesisDepth = 0;
29
30 var SIGNIFICANT_WHITESPACE = {
31 "selector": true,
32 "value": true,
33 "value-parenthesis": true,
34 "atRule": true,
35 "importRule-begin": true,
36 "importRule": true,
37 "atBlock": true,
38 "conditionBlock": true,
39 'documentRule-begin': true
40 };
41
42 var styleSheet = new CSSOM.CSSStyleSheet();
43
44 // @type CSSStyleSheet|CSSMediaRule|CSSSupportsRule|CSSFontFaceRule|CSSKeyframesRule|CSSDocumentRule
45 var currentScope = styleSheet;
46
47 // @type CSSMediaRule|CSSSupportsRule|CSSKeyframesRule|CSSDocumentRule
48 var parentRule;
49
50 var ancestorRules = [];
51 var hasAncestors = false;
52 var prevScope;
53
54 var name, priority="", styleRule, mediaRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule;
55
56 var atKeyframesRegExp = /@(-(?:\w+-)+)?keyframes/g;
57
58 var parseError = function(message) {
59 var lines = token.substring(0, i).split('\n');
60 var lineCount = lines.length;
61 var charCount = lines.pop().length + 1;
62 var error = new Error(message + ' (line ' + lineCount + ', char ' + charCount + ')');
63 error.line = lineCount;
64 /* jshint sub : true */
65 error['char'] = charCount;
66 error.styleSheet = styleSheet;
67 throw error;
68 };
69
70 for (var character; (character = token.charAt(i)); i++) {
71
72 switch (character) {
73
74 case " ":
75 case "\t":
76 case "\r":
77 case "\n":
78 case "\f":
79 if (SIGNIFICANT_WHITESPACE[state]) {
80 buffer += character;
81 }
82 break;
83
84 // String
85 case '"':
86 index = i + 1;
87 do {
88 index = token.indexOf('"', index) + 1;
89 if (!index) {
90 parseError('Unmatched "');
91 }
92 } while (token[index - 2] === '\\');
93 buffer += token.slice(i, index);
94 i = index - 1;
95 switch (state) {
96 case 'before-value':
97 state = 'value';
98 break;
99 case 'importRule-begin':
100 state = 'importRule';
101 break;
102 }
103 break;
104
105 case "'":
106 index = i + 1;
107 do {
108 index = token.indexOf("'", index) + 1;
109 if (!index) {
110 parseError("Unmatched '");
111 }
112 } while (token[index - 2] === '\\');
113 buffer += token.slice(i, index);
114 i = index - 1;
115 switch (state) {
116 case 'before-value':
117 state = 'value';
118 break;
119 case 'importRule-begin':
120 state = 'importRule';
121 break;
122 }
123 break;
124
125 // Comment
126 case "/":
127 if (token.charAt(i + 1) === "*") {
128 i += 2;
129 index = token.indexOf("*/", i);
130 if (index === -1) {
131 parseError("Missing */");
132 } else {
133 i = index + 1;
134 }
135 } else {
136 buffer += character;
137 }
138 if (state === "importRule-begin") {
139 buffer += " ";
140 state = "importRule";
141 }
142 break;
143
144 // At-rule
145 case "@":
146 if (token.indexOf("@-moz-document", i) === i) {
147 state = "documentRule-begin";
148 documentRule = new CSSOM.CSSDocumentRule();
149 documentRule.__starts = i;
150 i += "-moz-document".length;
151 buffer = "";
152 break;
153 } else if (token.indexOf("@media", i) === i) {
154 state = "atBlock";
155 mediaRule = new CSSOM.CSSMediaRule();
156 mediaRule.__starts = i;
157 i += "media".length;
158 buffer = "";
159 break;
160 } else if (token.indexOf("@supports", i) === i) {
161 state = "conditionBlock";
162 supportsRule = new CSSOM.CSSSupportsRule();
163 supportsRule.__starts = i;
164 i += "supports".length;
165 buffer = "";
166 break;
167 } else if (token.indexOf("@host", i) === i) {
168 state = "hostRule-begin";
169 i += "host".length;
170 hostRule = new CSSOM.CSSHostRule();
171 hostRule.__starts = i;
172 buffer = "";
173 break;
174 } else if (token.indexOf("@import", i) === i) {
175 state = "importRule-begin";
176 i += "import".length;
177 buffer += "@import";
178 break;
179 } else if (token.indexOf("@font-face", i) === i) {
180 state = "fontFaceRule-begin";
181 i += "font-face".length;
182 fontFaceRule = new CSSOM.CSSFontFaceRule();
183 fontFaceRule.__starts = i;
184 buffer = "";
185 break;
186 } else {
187 atKeyframesRegExp.lastIndex = i;
188 var matchKeyframes = atKeyframesRegExp.exec(token);
189 if (matchKeyframes && matchKeyframes.index === i) {
190 state = "keyframesRule-begin";
191 keyframesRule = new CSSOM.CSSKeyframesRule();
192 keyframesRule.__starts = i;
193 keyframesRule._vendorPrefix = matchKeyframes[1]; // Will come out as undefined if no prefix was found
194 i += matchKeyframes[0].length - 1;
195 buffer = "";
196 break;
197 } else if (state === "selector") {
198 state = "atRule";
199 }
200 }
201 buffer += character;
202 break;
203
204 case "{":
205 if (state === "selector" || state === "atRule") {
206 styleRule.selectorText = buffer.trim();
207 styleRule.style.__starts = i;
208 buffer = "";
209 state = "before-name";
210 } else if (state === "atBlock") {
211 mediaRule.media.mediaText = buffer.trim();
212
213 if (parentRule) {
214 ancestorRules.push(parentRule);
215 }
216
217 currentScope = parentRule = mediaRule;
218 mediaRule.parentStyleSheet = styleSheet;
219 buffer = "";
220 state = "before-selector";
221 } else if (state === "conditionBlock") {
222 supportsRule.conditionText = buffer.trim();
223
224 if (parentRule) {
225 ancestorRules.push(parentRule);
226 }
227
228 currentScope = parentRule = supportsRule;
229 supportsRule.parentStyleSheet = styleSheet;
230 buffer = "";
231 state = "before-selector";
232 } else if (state === "hostRule-begin") {
233 if (parentRule) {
234 ancestorRules.push(parentRule);
235 }
236
237 currentScope = parentRule = hostRule;
238 hostRule.parentStyleSheet = styleSheet;
239 buffer = "";
240 state = "before-selector";
241 } else if (state === "fontFaceRule-begin") {
242 if (parentRule) {
243 ancestorRules.push(parentRule);
244 fontFaceRule.parentRule = parentRule;
245 }
246 fontFaceRule.parentStyleSheet = styleSheet;
247 styleRule = fontFaceRule;
248 buffer = "";
249 state = "before-name";
250 } else if (state === "keyframesRule-begin") {
251 keyframesRule.name = buffer.trim();
252 if (parentRule) {
253 ancestorRules.push(parentRule);
254 keyframesRule.parentRule = parentRule;
255 }
256 keyframesRule.parentStyleSheet = styleSheet;
257 currentScope = parentRule = keyframesRule;
258 buffer = "";
259 state = "keyframeRule-begin";
260 } else if (state === "keyframeRule-begin") {
261 styleRule = new CSSOM.CSSKeyframeRule();
262 styleRule.keyText = buffer.trim();
263 styleRule.__starts = i;
264 buffer = "";
265 state = "before-name";
266 } else if (state === "documentRule-begin") {
267 // FIXME: what if this '{' is in the url text of the match function?
268 documentRule.matcher.matcherText = buffer.trim();
269 if (parentRule) {
270 ancestorRules.push(parentRule);
271 documentRule.parentRule = parentRule;
272 }
273 currentScope = parentRule = documentRule;
274 documentRule.parentStyleSheet = styleSheet;
275 buffer = "";
276 state = "before-selector";
277 }
278 break;
279
280 case ":":
281 if (state === "name") {
282 name = buffer.trim();
283 buffer = "";
284 state = "before-value";
285 } else {
286 buffer += character;
287 }
288 break;
289
290 case "(":
291 if (state === 'value') {
292 // ie css expression mode
293 if (buffer.trim() === 'expression') {
294 var info = (new CSSOM.CSSValueExpression(token, i)).parse();
295
296 if (info.error) {
297 parseError(info.error);
298 } else {
299 buffer += info.expression;
300 i = info.idx;
301 }
302 } else {
303 state = 'value-parenthesis';
304 //always ensure this is reset to 1 on transition
305 //from value to value-parenthesis
306 valueParenthesisDepth = 1;
307 buffer += character;
308 }
309 } else if (state === 'value-parenthesis') {
310 valueParenthesisDepth++;
311 buffer += character;
312 } else {
313 buffer += character;
314 }
315 break;
316
317 case ")":
318 if (state === 'value-parenthesis') {
319 valueParenthesisDepth--;
320 if (valueParenthesisDepth === 0) state = 'value';
321 }
322 buffer += character;
323 break;
324
325 case "!":
326 if (state === "value" && token.indexOf("!important", i) === i) {
327 priority = "important";
328 i += "important".length;
329 } else {
330 buffer += character;
331 }
332 break;
333
334 case ";":
335 switch (state) {
336 case "value":
337 styleRule.style.setProperty(name, buffer.trim(), priority);
338 priority = "";
339 buffer = "";
340 state = "before-name";
341 break;
342 case "atRule":
343 buffer = "";
344 state = "before-selector";
345 break;
346 case "importRule":
347 importRule = new CSSOM.CSSImportRule();
348 importRule.parentStyleSheet = importRule.styleSheet.parentStyleSheet = styleSheet;
349 importRule.cssText = buffer + character;
350 styleSheet.cssRules.push(importRule);
351 buffer = "";
352 state = "before-selector";
353 break;
354 default:
355 buffer += character;
356 break;
357 }
358 break;
359
360 case "}":
361 switch (state) {
362 case "value":
363 styleRule.style.setProperty(name, buffer.trim(), priority);
364 priority = "";
365 /* falls through */
366 case "before-name":
367 case "name":
368 styleRule.__ends = i + 1;
369 if (parentRule) {
370 styleRule.parentRule = parentRule;
371 }
372 styleRule.parentStyleSheet = styleSheet;
373 currentScope.cssRules.push(styleRule);
374 buffer = "";
375 if (currentScope.constructor === CSSOM.CSSKeyframesRule) {
376 state = "keyframeRule-begin";
377 } else {
378 state = "before-selector";
379 }
380 break;
381 case "keyframeRule-begin":
382 case "before-selector":
383 case "selector":
384 // End of media/supports/document rule.
385 if (!parentRule) {
386 parseError("Unexpected }");
387 }
388
389 // Handle rules nested in @media or @supports
390 hasAncestors = ancestorRules.length > 0;
391
392 while (ancestorRules.length > 0) {
393 parentRule = ancestorRules.pop();
394
395 if (
396 parentRule.constructor.name === "CSSMediaRule"
397 || parentRule.constructor.name === "CSSSupportsRule"
398 ) {
399 prevScope = currentScope;
400 currentScope = parentRule;
401 currentScope.cssRules.push(prevScope);
402 break;
403 }
404
405 if (ancestorRules.length === 0) {
406 hasAncestors = false;
407 }
408 }
409
410 if (!hasAncestors) {
411 currentScope.__ends = i + 1;
412 styleSheet.cssRules.push(currentScope);
413 currentScope = styleSheet;
414 parentRule = null;
415 }
416
417 buffer = "";
418 state = "before-selector";
419 break;
420 }
421 break;
422
423 default:
424 switch (state) {
425 case "before-selector":
426 state = "selector";
427 styleRule = new CSSOM.CSSStyleRule();
428 styleRule.__starts = i;
429 break;
430 case "before-name":
431 state = "name";
432 break;
433 case "before-value":
434 state = "value";
435 break;
436 case "importRule-begin":
437 state = "importRule";
438 break;
439 }
440 buffer += character;
441 break;
442 }
443 }
444
445 return styleSheet;
446};
447
448
449//.CommonJS
450exports.parse = CSSOM.parse;
451// The following modules cannot be included sooner due to the mutual dependency with parse.js
452CSSOM.CSSStyleSheet = require("./CSSStyleSheet").CSSStyleSheet;
453CSSOM.CSSStyleRule = require("./CSSStyleRule").CSSStyleRule;
454CSSOM.CSSImportRule = require("./CSSImportRule").CSSImportRule;
455CSSOM.CSSMediaRule = require("./CSSMediaRule").CSSMediaRule;
456CSSOM.CSSSupportsRule = require("./CSSSupportsRule").CSSSupportsRule;
457CSSOM.CSSFontFaceRule = require("./CSSFontFaceRule").CSSFontFaceRule;
458CSSOM.CSSHostRule = require("./CSSHostRule").CSSHostRule;
459CSSOM.CSSStyleDeclaration = require('./CSSStyleDeclaration').CSSStyleDeclaration;
460CSSOM.CSSKeyframeRule = require('./CSSKeyframeRule').CSSKeyframeRule;
461CSSOM.CSSKeyframesRule = require('./CSSKeyframesRule').CSSKeyframesRule;
462CSSOM.CSSValueExpression = require('./CSSValueExpression').CSSValueExpression;
463CSSOM.CSSDocumentRule = require('./CSSDocumentRule').CSSDocumentRule;
464///CommonJS