-
Notifications
You must be signed in to change notification settings - Fork 41
Expand file tree
/
Copy pathcss_parse.js
More file actions
155 lines (104 loc) · 6.02 KB
/
css_parse.js
File metadata and controls
155 lines (104 loc) · 6.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
if(typeof(String.prototype.trim) !== 'function'){ String.prototype.trim = function(){ return this.replace(/^\s+|\s+$/g, ''); }; }
//________________________________________________________________________________
function css_parse(css, silent)
{
var errors = [];
function error(msg)
{
var err = new Error(msg + ' at "' + css.substr(0,100) + '..."');
err.reason = msg; if(silent) errors.push(err); else throw err;
}//___________________________________________________________________________
function match(re){ var m = re.exec(css); if(m){ css = css.slice(m[0].length); return m; } } // match regexp and return captures
//____________________________________________________________________________
function whitespace(){ match(/^\s*/); }
function open(){ return match(/^{\s*/); } // opening brace
function close(){ return match(/^}/); } // closing brace
function comment()
{
whitespace();
if(css[0] != '/' || css[1] != '*') return;
var i = 2; while(css[i] != '' && (css[i] != '*' || css[i+1] != '/')) i++;
if(css[i+1] == '') return error('End of comment is missing');
var str = css.slice(2,i); css = css.slice(i+2);
return { type: 'comment', comment: str };
}
function comments(){ var c, cmnts = []; while(c = comment()){ cmnts.push(c); } return cmnts; }
//____________________________________________________________________________
function selector()
{
whitespace(); while(css[0] == '}'){ error('extra closing bracket'); css = css.slice(1); whitespace(); }
var m = match(/^(("(?:\\"|[^"])*"|'(?:\\'|[^'])*'|[^{])+)/);
if(m) return m[0].trim() // remove all comments from selectors
.replace(/\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/+/g, '')
.replace(/"(?:\\"|[^"])*"|'(?:\\'|[^'])*'/g, function(m){ return m.replace(/,/g, '\u200C'); })
.split(/\s*(?![^(]*\)),\s*/)
.map(function(s){ return s.replace(/\u200C/g, ','); });
}//___________________________________________________________________________
function declaration()
{
match(/^([;\s]*)+/); // ignore empty declarations + whitespace
const comment_regexp = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g;
var prop = match(/^(\*?[-#\/\*\\\w]+(\[[0-9a-z_-]+\])?)\s*/); if(!prop) return;
prop = prop[0].trim();
if(!match(/^:\s*/)) return error("property missing ':'");
// Quotes regex repeats verbatim inside and outside parentheses
var val = match(/^((?:\/\*.*?\*\/|'(?:\\'|.)*?'|"(?:\\"|.)*?"|\((\s*'(?:\\'|.)*?'|"(?:\\"|.)*?"|[^)]*?)\s*\)|[^};])+)/);
var ret = { type: 'declaration', property: prop.replace(comment_regexp, ''), value: val ? val[0].replace(comment_regexp, '').trim() : '' };
match(/^[;\s]*/);
return ret;
}
function declarations()
{
if(!open()) return error("missing '{'");
var d, decls = comments(); while(d = declaration()){ decls.push(d); decls = decls.concat(comments()); }
if(!close()) return error("missing '}'");
return decls;
}//___________________________________________________________________________
function keyframe()
{
whitespace();
var m, vals = []; while(m = match(/^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/)){ vals.push(m[1]); match(/^,\s*/); }
if(vals.length) return { type: 'keyframe', values: vals, declarations: declarations() };
}
function at_keyframes()
{
var m = match(/^@([-\w]+)?keyframes\s*/); if(!m) return;
var vendor = m[1];
var m = match(/^([-\w]+)\s*/); if(!m) return error("@keyframes missing name"); // identifier
var name = m[1];
if(!open()) return error("@keyframes missing '{'");
var frame, frames = comments(); while(frame = keyframe()){ frames.push(frame); frames = frames.concat(comments()); }
if(!close()) return error("@keyframes missing '}'");
return { type: 'keyframes', name: name, vendor: vendor, keyframes: frames };
}//___________________________________________________________________________
function at_page (){ var m = match(/^@page */); if(m){ var sel = selector() || []; return { type: 'page', selectors: sel, declarations: declarations() }; } }
function at_fontface(){ var m = match(/^@font-face\s*/); if(m) return { type: 'font-face', declarations: declarations() }; }
function at_supports(){ var m = match(/^@supports *([^{]+)/); if(m) return { type: 'supports', supports: m[1].trim(), rules: rules() }; }
function at_host (){ var m = match(/^@host\s*/); if(m) return { type: 'host', rules: rules() }; }
function at_media (){ var m = match(/^@media *([^{]+)/); if(m) return { type: 'media', media: m[1].trim(), rules: rules() }; }
function at_custom_m(){ var m = match(/^@custom-media\s+(--[^\s]+)\s*([^{;]+);/); if(m) return { type: 'custom-media', name: m[1].trim(), media: m[2].trim() }; }
function at_document(){ var m = match(/^@([-\w]+)?document *([^{]+)/); if(m) return { type: 'document', document: m[2].trim(), vendor: m[1].trim(), rules: rules() }; }
function at_x (){ var m = match(/^@(import|charset|namespace)\s*([^;]+);/); if(m) return { type: m[1], name: m[2].trim() }; }
//____________________________________________________________________________
function at_rule()
{
whitespace(); if(css[0] == '@') return at_keyframes() || at_supports() || at_host() || at_media() || at_custom_m() || at_page() || at_document() || at_fontface() || at_x();
}//___________________________________________________________________________
function rule()
{
var sel = selector() || []; if(!sel.length) error('selector missing');
return { type: 'rule', selectors: sel, declarations: declarations() };
}//___________________________________________________________________________
function rules(core)
{
if(!core && !open()) return error("missing '{'");
var node, rules = comments();
while(css.length && (core || css[0] != '}') && (node = (at_rule() || rule())))
{
rules.push(node); rules = rules.concat(comments());
}
if(!core && !close()) return error("missing '}'");
return rules;
}//___________________________________________________________________________
return { type: 'stylesheet', stylesheet: { rules: rules(true), errors: errors } };
}