You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
266 lines
3.8 KiB
JavaScript
266 lines
3.8 KiB
JavaScript
6 years ago
|
|
||
|
module.exports = function(css){
|
||
|
|
||
|
/**
|
||
|
* Parse stylesheet.
|
||
|
*/
|
||
|
|
||
|
function stylesheet() {
|
||
|
return { stylesheet: { rules: rules() }};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Opening brace.
|
||
|
*/
|
||
|
|
||
|
function open() {
|
||
|
return match(/^{\s*/);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Closing brace.
|
||
|
*/
|
||
|
|
||
|
function close() {
|
||
|
return match(/^}\s*/);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parse ruleset.
|
||
|
*/
|
||
|
|
||
|
function rules() {
|
||
|
var node;
|
||
|
var rules = [];
|
||
|
whitespace();
|
||
|
comments();
|
||
|
while (css[0] != '}' && (node = atrule() || rule())) {
|
||
|
comments();
|
||
|
rules.push(node);
|
||
|
}
|
||
|
return rules;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Match `re` and return captures.
|
||
|
*/
|
||
|
|
||
|
function match(re) {
|
||
|
var m = re.exec(css);
|
||
|
if (!m) return;
|
||
|
css = css.slice(m[0].length);
|
||
|
return m;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parse whitespace.
|
||
|
*/
|
||
|
|
||
|
function whitespace() {
|
||
|
match(/^\s*/);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parse comments;
|
||
|
*/
|
||
|
|
||
|
function comments() {
|
||
|
while (comment()) ;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parse comment.
|
||
|
*/
|
||
|
|
||
|
function comment() {
|
||
|
if ('/' == css[0] && '*' == css[1]) {
|
||
|
var i = 2;
|
||
|
while ('*' != css[i] || '/' != css[i + 1]) ++i;
|
||
|
i += 2;
|
||
|
css = css.slice(i);
|
||
|
whitespace();
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parse selector.
|
||
|
*/
|
||
|
|
||
|
function selector() {
|
||
|
var m = match(/^([^{]+)/);
|
||
|
if (!m) return;
|
||
|
return m[0].trim().split(/\s*,\s*/);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parse declaration.
|
||
|
*/
|
||
|
|
||
|
function declaration() {
|
||
|
// prop
|
||
|
var prop = match(/^(\*?[-\w]+)\s*/);
|
||
|
if (!prop) return;
|
||
|
prop = prop[0];
|
||
|
|
||
|
// :
|
||
|
if (!match(/^:\s*/)) return;
|
||
|
|
||
|
// val
|
||
|
var val = match(/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)\s*/);
|
||
|
if (!val) return;
|
||
|
val = val[0].trim();
|
||
|
|
||
|
// ;
|
||
|
match(/^[;\s]*/);
|
||
|
|
||
|
return { property: prop, value: val };
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parse keyframe.
|
||
|
*/
|
||
|
|
||
|
function keyframe() {
|
||
|
var m;
|
||
|
var vals = [];
|
||
|
|
||
|
while (m = match(/^(from|to|\d+%|\.\d+%|\d+\.\d+%)\s*/)) {
|
||
|
vals.push(m[1]);
|
||
|
match(/^,\s*/);
|
||
|
}
|
||
|
|
||
|
if (!vals.length) return;
|
||
|
|
||
|
return {
|
||
|
values: vals,
|
||
|
declarations: declarations()
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parse keyframes.
|
||
|
*/
|
||
|
|
||
|
function keyframes() {
|
||
|
var m = match(/^@([-\w]+)?keyframes */);
|
||
|
if (!m) return;
|
||
|
var vendor = m[1];
|
||
|
|
||
|
// identifier
|
||
|
var m = match(/^([-\w]+)\s*/);
|
||
|
if (!m) return;
|
||
|
var name = m[1];
|
||
|
|
||
|
if (!open()) return;
|
||
|
comments();
|
||
|
|
||
|
var frame;
|
||
|
var frames = [];
|
||
|
while (frame = keyframe()) {
|
||
|
frames.push(frame);
|
||
|
comments();
|
||
|
}
|
||
|
|
||
|
if (!close()) return;
|
||
|
|
||
|
return {
|
||
|
name: name,
|
||
|
vendor: vendor,
|
||
|
keyframes: frames
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parse media.
|
||
|
*/
|
||
|
|
||
|
function media() {
|
||
|
var m = match(/^@media *([^{]+)/);
|
||
|
if (!m) return;
|
||
|
var media = m[1].trim();
|
||
|
|
||
|
if (!open()) return;
|
||
|
comments();
|
||
|
|
||
|
var style = rules();
|
||
|
|
||
|
if (!close()) return;
|
||
|
|
||
|
return { media: media, rules: style };
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parse import
|
||
|
*/
|
||
|
|
||
|
function atimport() {
|
||
|
return _atrule('import')
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parse charset
|
||
|
*/
|
||
|
|
||
|
function atcharset() {
|
||
|
return _atrule('charset');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parse non-block at-rules
|
||
|
*/
|
||
|
|
||
|
function _atrule(name) {
|
||
|
var m = match(new RegExp('^@' + name + ' *([^;\\n]+);\\s*'));
|
||
|
if (!m) return;
|
||
|
var ret = {}
|
||
|
ret[name] = m[1].trim();
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parse declarations.
|
||
|
*/
|
||
|
|
||
|
function declarations() {
|
||
|
var decls = [];
|
||
|
|
||
|
if (!open()) return;
|
||
|
comments();
|
||
|
|
||
|
// declarations
|
||
|
var decl;
|
||
|
while (decl = declaration()) {
|
||
|
decls.push(decl);
|
||
|
comments();
|
||
|
}
|
||
|
|
||
|
if (!close()) return;
|
||
|
return decls;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parse at rule.
|
||
|
*/
|
||
|
|
||
|
function atrule() {
|
||
|
return keyframes()
|
||
|
|| media()
|
||
|
|| atimport()
|
||
|
|| atcharset();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parse rule.
|
||
|
*/
|
||
|
|
||
|
function rule() {
|
||
|
var sel = selector();
|
||
|
if (!sel) return;
|
||
|
comments();
|
||
|
return { selectors: sel, declarations: declarations() };
|
||
|
}
|
||
|
|
||
|
return stylesheet();
|
||
|
};
|