|
|
- // http://www.w3.org/TR/CSS21/grammar.html
- // https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027
- var commentre = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g
-
- module.exports = function(css, options){
- options = options || {};
-
- /**
- * Positional.
- */
-
- var lineno = 1;
- var column = 1;
-
- /**
- * Update lineno and column based on `str`.
- */
-
- function updatePosition(str) {
- var lines = str.match(/\n/g);
- if (lines) lineno += lines.length;
- var i = str.lastIndexOf('\n');
- column = ~i ? str.length - i : column + str.length;
- }
-
- /**
- * Mark position and patch `node.position`.
- */
-
- function position() {
- var start = { line: lineno, column: 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: column };
- this.source = options.source;
- }
-
- /**
- * Non-enumerable source string
- */
-
- Position.prototype.content = css;
-
- /**
- * Error `msg`.
- */
-
- var errorsList = [];
-
- function error(msg) {
- var 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() {
- var rulesList = rules();
-
- return {
- type: 'stylesheet',
- stylesheet: {
- source: options.source,
- rules: rulesList,
- parsingErrors: errorsList
- }
- };
- }
-
- /**
- * Opening brace.
- */
-
- function open() {
- return match(/^{\s*/);
- }
-
- /**
- * Closing brace.
- */
-
- function close() {
- return match(/^}/);
- }
-
- /**
- * Parse ruleset.
- */
-
- function rules() {
- var node;
- var 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) {
- var m = re.exec(css);
- if (!m) return;
- var str = m[0];
- updatePosition(str);
- css = css.slice(str.length);
- return m;
- }
-
- /**
- * Parse whitespace.
- */
-
- function whitespace() {
- match(/^\s*/);
- }
-
- /**
- * Parse comments;
- */
-
- function comments(rules) {
- var c;
- rules = rules || [];
- while (c = comment()) {
- if (c !== false) {
- rules.push(c);
- }
- }
- return rules;
- }
-
- /**
- * Parse comment.
- */
-
- function comment() {
- var pos = position();
- if ('/' != css.charAt(0) || '*' != css.charAt(1)) return;
-
- var 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');
- }
-
- var 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() {
- var 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() {
- var pos = position();
-
- // prop
- var prop = match(/^(\*?[-#\/\*\\\w]+(\[[0-9a-z_-]+\])?)\s*/);
- if (!prop) return;
- prop = trim(prop[0]);
-
- // :
- if (!match(/^:\s*/)) return error("property missing ':'");
-
- // val
- var val = match(/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)/);
-
- var ret = pos({
- type: 'declaration',
- property: prop.replace(commentre, ''),
- value: val ? trim(val[0]).replace(commentre, '') : ''
- });
-
- // ;
- match(/^[;\s]*/);
-
- return ret;
- }
-
- /**
- * Parse declarations.
- */
-
- function declarations() {
- var decls = [];
-
- if (!open()) return error("missing '{'");
- comments(decls);
-
- // declarations
- var decl;
- while (decl = declaration()) {
- if (decl !== false) {
- decls.push(decl);
- comments(decls);
- }
- }
-
- if (!close()) return error("missing '}'");
- return decls;
- }
-
- /**
- * Parse keyframe.
- */
-
- function keyframe() {
- var m;
- var vals = [];
- var 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() {
- var pos = position();
- var m = match(/^@([-\w]+)?keyframes\s*/);
-
- if (!m) return;
- var vendor = m[1];
-
- // identifier
- var m = match(/^([-\w]+)\s*/);
- if (!m) return error("@keyframes missing name");
- var name = m[1];
-
- if (!open()) return error("@keyframes missing '{'");
-
- var frame;
- var frames = comments();
- while (frame = keyframe()) {
- frames.push(frame);
- frames = frames.concat(comments());
- }
-
- if (!close()) return error("@keyframes missing '}'");
-
- return pos({
- type: 'keyframes',
- name: name,
- vendor: vendor,
- keyframes: frames
- });
- }
-
- /**
- * Parse supports.
- */
-
- function atsupports() {
- var pos = position();
- var m = match(/^@supports *([^{]+)/);
-
- if (!m) return;
- var supports = trim(m[1]);
-
- if (!open()) return error("@supports missing '{'");
-
- var style = comments().concat(rules());
-
- if (!close()) return error("@supports missing '}'");
-
- return pos({
- type: 'supports',
- supports: supports,
- rules: style
- });
- }
-
- /**
- * Parse host.
- */
-
- function athost() {
- var pos = position();
- var m = match(/^@host\s*/);
-
- if (!m) return;
-
- if (!open()) return error("@host missing '{'");
-
- var style = comments().concat(rules());
-
- if (!close()) return error("@host missing '}'");
-
- return pos({
- type: 'host',
- rules: style
- });
- }
-
- /**
- * Parse media.
- */
-
- function atmedia() {
- var pos = position();
- var m = match(/^@media *([^{]+)/);
-
- if (!m) return;
- var media = trim(m[1]);
-
- if (!open()) return error("@media missing '{'");
-
- var style = comments().concat(rules());
-
- if (!close()) return error("@media missing '}'");
-
- return pos({
- type: 'media',
- media: media,
- rules: style
- });
- }
-
-
- /**
- * Parse custom-media.
- */
-
- function atcustommedia() {
- var pos = position();
- var 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() {
- var pos = position();
- var m = match(/^@page */);
- if (!m) return;
-
- var sel = selector() || [];
-
- if (!open()) return error("@page missing '{'");
- var decls = comments();
-
- // declarations
- var 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() {
- var pos = position();
- var m = match(/^@([-\w]+)?document *([^{]+)/);
- if (!m) return;
-
- var vendor = trim(m[1]);
- var doc = trim(m[2]);
-
- if (!open()) return error("@document missing '{'");
-
- var style = comments().concat(rules());
-
- if (!close()) return error("@document missing '}'");
-
- return pos({
- type: 'document',
- document: doc,
- vendor: vendor,
- rules: style
- });
- }
-
- /**
- * Parse font-face.
- */
-
- function atfontface() {
- var pos = position();
- var m = match(/^@font-face\s*/);
- if (!m) return;
-
- if (!open()) return error("@font-face missing '{'");
- var decls = comments();
-
- // declarations
- var 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
- */
-
- var atimport = _compileAtrule('import');
-
- /**
- * Parse charset
- */
-
- var atcharset = _compileAtrule('charset');
-
- /**
- * Parse namespace
- */
-
- var atnamespace = _compileAtrule('namespace');
-
- /**
- * Parse non-block at-rules
- */
-
-
- function _compileAtrule(name) {
- var re = new RegExp('^@' + name + '\\s*([^;]+);');
- return function() {
- var pos = position();
- var m = match(re);
- if (!m) return;
- var 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() {
- var pos = position();
- var 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) {
- var isNode = obj && typeof obj.type === 'string';
- var childParent = isNode ? obj : parent;
-
- for (var k in obj) {
- var 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;
- }
|