UNPKG

11.9 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 fontFaceRule.parentRule = parentRule;
244 }
245 fontFaceRule.parentStyleSheet = styleSheet;
246 styleRule = fontFaceRule;
247 buffer = "";
248 state = "before-name";
249 } else if (state === "keyframesRule-begin") {
250 keyframesRule.name = buffer.trim();
251 if (parentRule) {
252 ancestorRules.push(parentRule);
253 keyframesRule.parentRule = parentRule;
254 }
255 keyframesRule.parentStyleSheet = styleSheet;
256 currentScope = parentRule = keyframesRule;
257 buffer = "";
258 state = "keyframeRule-begin";
259 } else if (state === "keyframeRule-begin") {
260 styleRule = new CSSOM.CSSKeyframeRule();
261 styleRule.keyText = buffer.trim();
262 styleRule.__starts = i;
263 buffer = "";
264 state = "before-name";
265 } else if (state === "documentRule-begin") {
266 // FIXME: what if this '{' is in the url text of the match function?
267 documentRule.matcher.matcherText = buffer.trim();
268 if (parentRule) {
269 ancestorRules.push(parentRule);
270 documentRule.parentRule = parentRule;
271 }
272 currentScope = parentRule = documentRule;
273 documentRule.parentStyleSheet = styleSheet;
274 buffer = "";
275 state = "before-selector";
276 }
277 break;
278
279 case ":":
280 if (state === "name") {
281 name = buffer.trim();
282 buffer = "";
283 state = "before-value";
284 } else {
285 buffer += character;
286 }
287 break;
288
289 case "(":
290 if (state === 'value') {
291 // ie css expression mode
292 if (buffer.trim() === 'expression') {
293 var info = (new CSSOM.CSSValueExpression(token, i)).parse();
294
295 if (info.error) {
296 parseError(info.error);
297 } else {
298 buffer += info.expression;
299 i = info.idx;
300 }
301 } else {
302 state = 'value-parenthesis';
303 //always ensure this is reset to 1 on transition
304 //from value to value-parenthesis
305 valueParenthesisDepth = 1;
306 buffer += character;
307 }
308 } else if (state === 'value-parenthesis') {
309 valueParenthesisDepth++;
310 buffer += character;
311 } else {
312 buffer += character;
313 }
314 break;
315
316 case ")":
317 if (state === 'value-parenthesis') {
318 valueParenthesisDepth--;
319 if (valueParenthesisDepth === 0) state = 'value';
320 }
321 buffer += character;
322 break;
323
324 case "!":
325 if (state === "value" && token.indexOf("!important", i) === i) {
326 priority = "important";
327 i += "important".length;
328 } else {
329 buffer += character;
330 }
331 break;
332
333 case ";":
334 switch (state) {
335 case "value":
336 styleRule.style.setProperty(name, buffer.trim(), priority);
337 priority = "";
338 buffer = "";
339 state = "before-name";
340 break;
341 case "atRule":
342 buffer = "";
343 state = "before-selector";
344 break;
345 case "importRule":
346 importRule = new CSSOM.CSSImportRule();
347 importRule.parentStyleSheet = importRule.styleSheet.parentStyleSheet = styleSheet;
348 importRule.cssText = buffer + character;
349 styleSheet.cssRules.push(importRule);
350 buffer = "";
351 state = "before-selector";
352 break;
353 default:
354 buffer += character;
355 break;
356 }
357 break;
358
359 case "}":
360 switch (state) {
361 case "value":
362 styleRule.style.setProperty(name, buffer.trim(), priority);
363 priority = "";
364 /* falls through */
365 case "before-name":
366 case "name":
367 styleRule.__ends = i + 1;
368 if (parentRule) {
369 styleRule.parentRule = parentRule;
370 }
371 styleRule.parentStyleSheet = styleSheet;
372 currentScope.cssRules.push(styleRule);
373 buffer = "";
374 if (currentScope.constructor === CSSOM.CSSKeyframesRule) {
375 state = "keyframeRule-begin";
376 } else {
377 state = "before-selector";
378 }
379 break;
380 case "keyframeRule-begin":
381 case "before-selector":
382 case "selector":
383 // End of media/supports/document rule.
384 if (!parentRule) {
385 parseError("Unexpected }");
386 }
387
388 // Handle rules nested in @media or @supports
389 hasAncestors = ancestorRules.length > 0;
390
391 while (ancestorRules.length > 0) {
392 parentRule = ancestorRules.pop();
393
394 if (
395 parentRule.constructor.name === "CSSMediaRule"
396 || parentRule.constructor.name === "CSSSupportsRule"
397 ) {
398 prevScope = currentScope;
399 currentScope = parentRule;
400 currentScope.cssRules.push(prevScope);
401 break;
402 }
403
404 if (ancestorRules.length === 0) {
405 hasAncestors = false;
406 }
407 }
408
409 if (!hasAncestors) {
410 currentScope.__ends = i + 1;
411 styleSheet.cssRules.push(currentScope);
412 currentScope = styleSheet;
413 parentRule = null;
414 }
415
416 buffer = "";
417 state = "before-selector";
418 break;
419 }
420 break;
421
422 default:
423 switch (state) {
424 case "before-selector":
425 state = "selector";
426 styleRule = new CSSOM.CSSStyleRule();
427 styleRule.__starts = i;
428 break;
429 case "before-name":
430 state = "name";
431 break;
432 case "before-value":
433 state = "value";
434 break;
435 case "importRule-begin":
436 state = "importRule";
437 break;
438 }
439 buffer += character;
440 break;
441 }
442 }
443
444 return styleSheet;
445};
446
447
448//.CommonJS
449exports.parse = CSSOM.parse;
450// The following modules cannot be included sooner due to the mutual dependency with parse.js
451CSSOM.CSSStyleSheet = require("./CSSStyleSheet").CSSStyleSheet;
452CSSOM.CSSStyleRule = require("./CSSStyleRule").CSSStyleRule;
453CSSOM.CSSImportRule = require("./CSSImportRule").CSSImportRule;
454CSSOM.CSSGroupingRule = require("./CSSGroupingRule").CSSGroupingRule;
455CSSOM.CSSMediaRule = require("./CSSMediaRule").CSSMediaRule;
456CSSOM.CSSConditionRule = require("./CSSConditionRule").CSSConditionRule;
457CSSOM.CSSSupportsRule = require("./CSSSupportsRule").CSSSupportsRule;
458CSSOM.CSSFontFaceRule = require("./CSSFontFaceRule").CSSFontFaceRule;
459CSSOM.CSSHostRule = require("./CSSHostRule").CSSHostRule;
460CSSOM.CSSStyleDeclaration = require('./CSSStyleDeclaration').CSSStyleDeclaration;
461CSSOM.CSSKeyframeRule = require('./CSSKeyframeRule').CSSKeyframeRule;
462CSSOM.CSSKeyframesRule = require('./CSSKeyframesRule').CSSKeyframesRule;
463CSSOM.CSSValueExpression = require('./CSSValueExpression').CSSValueExpression;
464CSSOM.CSSDocumentRule = require('./CSSDocumentRule').CSSDocumentRule;
465///CommonJS