Repository URL to install this package:
|
Version:
1.2.7 ▾
|
"use strict";
// http://www.w3.org/TR/CSS21/grammar.html
// https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027
let commentre = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g;
module.exports = function (css, options) {
options = options || {};
/**
* Positional.
*/
let lineno = 1;
let column = 1;
/**
* Update lineno and column based on `str`.
*/
function updatePosition(str) {
let lines = str.match(/\n/g);
if (lines) {
lineno += lines.length;
}
let i = str.lastIndexOf('\n');
column = ~i ? str.length - i : column + str.length;
}
/**
* Mark position and patch `node.position`.
*/
function position() {
let start = {
line: lineno,
column
};
return function (node) {
node.position = new Position(start);
whitespace();
return node;
};
}
/**
* Store position information for a node
*/
function Position(start) {
this.start = start;
this.end = {
line: lineno,
column
};
this.source = options.source;
}
/**
* Non-enumerable source string
*/
Position.prototype.content = css;
/**
* Error `msg`.
*/
let errorsList = [];
function error(msg) {
let err = new Error(options.source + ':' + lineno + ':' + column + ': ' + msg);
err.reason = msg;
err.filename = options.source;
err.line = lineno;
err.column = column;
err.source = css;
if (options.silent) {
errorsList.push(err);
} else {
throw err;
}
}
/**
* Parse stylesheet.
*/
function stylesheet() {
let rulesList = rules();
return {
type: 'stylesheet',
stylesheet: {
rules: rulesList,
parsingErrors: errorsList
}
};
}
/**
* Opening brace.
*/
function open() {
return match(/^{\s*/);
}
/**
* Closing brace.
*/
function close() {
return match(/^}/);
}
/**
* Parse ruleset.
*/
function rules() {
let node;
let rules = [];
whitespace();
comments(rules);
while (css.length && css.charAt(0) != '}' && (node = atrule() || rule())) {
if (node !== false) {
rules.push(node);
comments(rules);
}
}
return rules;
}
/**
* Match `re` and return captures.
*/
function match(re) {
let m = re.exec(css);
if (!m) {
return;
}
let str = m[0];
updatePosition(str);
css = css.slice(str.length);
return m;
}
/**
* Parse whitespace.
*/
function whitespace() {
match(/^\s*/);
}
/**
* Parse comments;
*/
function comments(rules) {
let c;
rules = rules || [];
while (c = comment()) {
if (c !== false) {
rules.push(c);
}
}
return rules;
}
/**
* Parse comment.
*/
function comment() {
let pos = position();
if (css.charAt(0) != '/' || css.charAt(1) != '*') {
return;
}
let i = 2;
while (css.charAt(i) != '' && (css.charAt(i) != '*' || css.charAt(i + 1) != '/')) {
++i;
}
i += 2;
if (css.charAt(i - 1) === '') {
return error('End of comment missing');
}
let str = css.slice(2, i - 2);
column += 2;
updatePosition(str);
css = css.slice(i);
column += 2;
return pos({
type: 'comment',
comment: str
});
}
/**
* Parse selector.
*/
function selector() {
let m = match(/^([^{]+)/);
if (!m) {
return;
}
/* @fix Remove all comments from selectors
* http://ostermiller.org/findcomment.html */
return trim(m[0]).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, ',');
});
}
/**
* Parse declaration.
*/
function declaration() {
let pos = position(); // prop
let prop = match(/^(\*?[-#\/\*\\\w]+(\[[0-9a-z_-]+\])?)\s*/);
if (!prop) {
return;
}
prop = trim(prop[0]); // :
if (!match(/^:\s*/)) {
return error("property missing ':'");
} // val
let val = match(/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)/);
let ret = pos({
type: 'declaration',
property: prop.replace(commentre, ''),
value: val ? trim(val[0]).replace(commentre, '') : ''
}); // ;
match(/^[;\s]*/);
return ret;
}
/**
* Parse declarations.
*/
function declarations() {
let decls = [];
if (!open()) {
return error("missing '{'");
}
comments(decls); // declarations
let decl;
while (decl = declaration()) {
if (decl !== false) {
decls.push(decl);
comments(decls);
}
}
if (!close()) {
return error("missing '}'");
}
return decls;
}
/**
* Parse keyframe.
*/
function keyframe() {
let m;
let vals = [];
let pos = position();
while (m = match(/^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/)) {
vals.push(m[1]);
match(/^,\s*/);
}
if (!vals.length) {
return;
}
return pos({
type: 'keyframe',
values: vals,
declarations: declarations()
});
}
/**
* Parse keyframes.
*/
function atkeyframes() {
let pos = position();
var m = match(/^@([-\w]+)?keyframes\s*/);
if (!m) {
return;
}
let vendor = m[1]; // identifier
var m = match(/^([-\w]+)\s*/);
if (!m) {
return error('@keyframes missing name');
}
let name = m[1];
if (!open()) {
return error("@keyframes missing '{'");
}
let frame;
let frames = comments();
while (frame = keyframe()) {
frames.push(frame);
frames = frames.concat(comments());
}
if (!close()) {
return error("@keyframes missing '}'");
}
return pos({
type: 'keyframes',
name,
vendor,
keyframes: frames
});
}
/**
* Parse supports.
*/
function atsupports() {
let pos = position();
let m = match(/^@supports *([^{]+)/);
if (!m) {
return;
}
let supports = trim(m[1]);
if (!open()) {
return error("@supports missing '{'");
}
let style = comments().concat(rules());
if (!close()) {
return error("@supports missing '}'");
}
return pos({
type: 'supports',
supports,
rules: style
});
}
/**
* Parse host.
*/
function athost() {
let pos = position();
let m = match(/^@host\s*/);
if (!m) {
return;
}
if (!open()) {
return error("@host missing '{'");
}
let style = comments().concat(rules());
if (!close()) {
return error("@host missing '}'");
}
return pos({
type: 'host',
rules: style
});
}
/**
* Parse media.
*/
function atmedia() {
let pos = position();
let m = match(/^@media *([^{]+)/);
if (!m) {
return;
}
let media = trim(m[1]);
if (!open()) {
return error("@media missing '{'");
}
let style = comments().concat(rules());
if (!close()) {
return error("@media missing '}'");
}
return pos({
type: 'media',
media,
rules: style
});
}
/**
* Parse custom-media.
*/
function atcustommedia() {
let pos = position();
let m = match(/^@custom-media\s+(--[^\s]+)\s*([^{;]+);/);
if (!m) {
return;
}
return pos({
type: 'custom-media',
name: trim(m[1]),
media: trim(m[2])
});
}
/**
* Parse paged media.
*/
function atpage() {
let pos = position();
let m = match(/^@page */);
if (!m) {
return;
}
let sel = selector() || [];
if (!open()) {
return error("@page missing '{'");
}
let decls = comments(); // declarations
let decl;
while (decl = declaration()) {
decls.push(decl);
decls = decls.concat(comments());
}
if (!close()) {
return error("@page missing '}'");
}
return pos({
type: 'page',
selectors: sel,
declarations: decls
});
}
/**
* Parse document.
*/
function atdocument() {
let pos = position();
let m = match(/^@([-\w]+)?document *([^{]+)/);
if (!m) {
return;
}
let vendor = trim(m[1]);
let doc = trim(m[2]);
if (!open()) {
return error("@document missing '{'");
}
let style = comments().concat(rules());
if (!close()) {
return error("@document missing '}'");
}
return pos({
type: 'document',
document: doc,
vendor,
rules: style
});
}
/**
* Parse font-face.
*/
function atfontface() {
let pos = position();
let m = match(/^@font-face\s*/);
if (!m) {
return;
}
if (!open()) {
return error("@font-face missing '{'");
}
let decls = comments(); // declarations
let decl;
while (decl = declaration()) {
decls.push(decl);
decls = decls.concat(comments());
}
if (!close()) {
return error("@font-face missing '}'");
}
return pos({
type: 'font-face',
declarations: decls
});
}
/**
* Parse import
*/
let atimport = _compileAtrule('import');
/**
* Parse charset
*/
let atcharset = _compileAtrule('charset');
/**
* Parse namespace
*/
let atnamespace = _compileAtrule('namespace');
/**
* Parse non-block at-rules
*/
function _compileAtrule(name) {
let re = new RegExp('^@' + name + '\\s*([^;]+);');
return function () {
let pos = position();
let m = match(re);
if (!m) {
return;
}
let ret = {
type: name
};
ret[name] = m[1].trim();
return pos(ret);
};
}
/**
* Parse at rule.
*/
function atrule() {
if (css[0] != '@') {
return;
}
return atkeyframes() || atmedia() || atcustommedia() || atsupports() || atimport() || atcharset() || atnamespace() || atdocument() || atpage() || athost() || atfontface();
}
/**
* Parse rule.
*/
function rule() {
let pos = position();
let sel = selector();
if (!sel) {
return error('selector missing');
}
comments();
return pos({
type: 'rule',
selectors: sel,
declarations: declarations()
});
}
return addParent(stylesheet());
};
/**
* Trim `str`.
*/
function trim(str) {
return str ? str.replace(/^\s+|\s+$/g, '') : '';
}
/**
* Adds non-enumerable parent node reference to each node.
*/
function addParent(obj, parent) {
let isNode = obj && typeof obj.type === 'string';
let childParent = isNode ? obj : parent;
for (let k in obj) {
let value = obj[k];
if (Array.isArray(value)) {
value.forEach(function (v) {
addParent(v, childParent);
});
} else if (value && typeof value === 'object') {
addParent(value, childParent);
}
}
if (isNode) {
Object.defineProperty(obj, 'parent', {
configurable: true,
writable: true,
enumerable: false,
value: parent || null
});
}
return obj;
}