123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- var Class = require('./Class');
- var trim = require('./trim');
- var repeat = require('./repeat');
- var defaults = require('./defaults');
- var camelCase = require('./camelCase');
- exports = {
- parse: function(css) {
- return new Parser(css).parse();
- },
- stringify: function(stylesheet, options) {
- return new Compiler(stylesheet, options).compile();
- }
- };
- var regComments = /(\/\*[\s\S]*?\*\/)/gi;
- var regOpen = /^{\s*/;
- var regClose = /^}/;
- var regWhitespace = /^\s*/;
- var regProperty = /^(\*?[-#/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/;
- var regValue = /^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};])+)/;
- var regSelector = /^([^{]+)/;
- var regSemicolon = /^[;\s]*/;
- var regColon = /^:\s*/;
- var regMedia = /^@media *([^{]+)/;
- var regKeyframes = /^@([-\w]+)?keyframes\s*/;
- var regFontFace = /^@font-face\s*/;
- var regSupports = /^@supports *([^{]+)/;
- var regIdentifier = /^([-\w]+)\s*/;
- var regKeyframeSelector = /^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/;
- var regComma = /^,\s*/;
- var Parser = Class({
- initialize: function Parser(css) {
- this.input = stripCmt(css);
- this.open = this._createMatcher(regOpen);
- this.close = this._createMatcher(regClose);
- this.whitespace = this._createMatcher(regWhitespace);
- this.atImport = this._createAtRule('import');
- this.atCharset = this._createAtRule('charset');
- this.atNamespace = this._createAtRule('namespace');
- },
- parse: function() {
- return this.stylesheet();
- },
- stylesheet: function() {
- return {
- type: 'stylesheet',
- rules: this.rules()
- };
- },
- rules: function() {
- var rule;
- var rules = [];
- this.whitespace();
- while (
- this.input.length &&
- this.input[0] !== '}' &&
- (rule = this.atRule() || this.rule())
- ) {
- rules.push(rule);
- this.whitespace();
- }
- return rules;
- },
- atRule: function() {
- if (this.input[0] !== '@') return;
- return (
- this.atKeyframes() ||
- this.atMedia() ||
- this.atSupports() ||
- this.atImport() ||
- this.atCharset() ||
- this.atNamespace() ||
- this.atFontFace()
- );
- },
- atKeyframes: function() {
- var matched = this.match(regKeyframes);
- if (!matched) return;
- var vendor = matched[1] || '';
- matched = this.match(regIdentifier);
- if (!matched) throw Error('@keyframes missing name');
- var name = matched[1];
- if (!this.open()) throw Error("@keyframes missing '{'");
- var keyframes = [];
- var keyframe;
- while ((keyframe = this.keyframe())) {
- keyframes.push(keyframe);
- }
- if (!this.close()) throw Error("@keyframes missing '}'");
- return {
- type: 'keyframes',
- name: name,
- vendor: vendor,
- keyframes: keyframes
- };
- },
- keyframe: function() {
- var selector = [];
- var matched;
- while ((matched = this.match(regKeyframeSelector))) {
- selector.push(matched[1]);
- this.match(regComma);
- }
- if (!selector.length) return;
- this.whitespace();
- return {
- type: 'keyframe',
- selector: selector.join(', '),
- declarations: this.declarations()
- };
- },
- atSupports: function() {
- var matched = this.match(regSupports);
- if (!matched) return;
- var supports = trim(matched[1]);
- if (!this.open()) throw Error("@supports missing '{'");
- var rules = this.rules();
- if (!this.close()) throw Error("@supports missing '}'");
- return {
- type: 'supports',
- supports: supports,
- rules: rules
- };
- },
- atFontFace: function() {
- var matched = this.match(regFontFace);
- if (!matched) return;
- if (!this.open()) throw Error("@font-face missing '{'");
- var declaration;
- var declarations = [];
- while ((declaration = this.declaration())) {
- declarations.push(declaration);
- }
- if (!this.close()) throw Error("@font-face missing '}'");
- return {
- type: 'font-face',
- declarations: declarations
- };
- },
- atMedia: function() {
- var matched = this.match(regMedia);
- if (!matched) return;
- var media = trim(matched[1]);
- if (!this.open()) throw Error("@media missing '{'");
- this.whitespace();
- var rules = this.rules();
- if (!this.close()) throw Error("@media missing '}'");
- return {
- type: 'media',
- media: media,
- rules: rules
- };
- },
- rule: function() {
- var selector = this.selector();
- if (!selector) throw Error('missing selector');
- return {
- type: 'rule',
- selector: selector,
- declarations: this.declarations()
- };
- },
- declarations: function() {
- var declarations = [];
- if (!this.open()) throw Error("missing '{'");
- this.whitespace();
- var declaration;
- while ((declaration = this.declaration())) {
- declarations.push(declaration);
- }
- if (!this.close()) throw Error("missing '}'");
- this.whitespace();
- return declarations;
- },
- declaration: function() {
- var property = this.match(regProperty);
- if (!property) return;
- property = trim(property[0]);
- if (!this.match(regColon)) throw Error("property missing ':'");
- var value = this.match(regValue);
- this.match(regSemicolon);
- this.whitespace();
- return {
- type: 'declaration',
- property: property,
- value: value ? trim(value[0]) : ''
- };
- },
- selector: function() {
- var matched = this.match(regSelector);
- if (!matched) return;
- return trim(matched[0]);
- },
- match: function(reg) {
- var matched = reg.exec(this.input);
- if (!matched) return;
- this.input = this.input.slice(matched[0].length);
- return matched;
- },
- _createMatcher: function(reg) {
- var _this = this;
- return function() {
- return _this.match(reg);
- };
- },
- _createAtRule: function(name) {
- var reg = new RegExp('^@' + name + '\\s*([^;]+);');
- return function() {
- var matched = this.match(reg);
- if (!matched) return;
- var ret = {
- type: name
- };
- ret[name] = trim(matched[1]);
- return ret;
- };
- }
- });
- var Compiler = Class({
- initialize: function Compiler(input) {
- var options =
- arguments.length > 1 && arguments[1] !== undefined
- ? arguments[1]
- : {};
- defaults(options, {
- indent: ' '
- });
- this.input = input;
- this.indentLevel = 0;
- this.indentation = options.indent;
- },
- compile: function() {
- return this.stylesheet(this.input);
- },
- stylesheet: function(node) {
- return this.mapVisit(node.rules, '\n\n');
- },
- media: function(node) {
- return (
- '@media ' +
- node.media +
- ' {\n' +
- this.indent(1) +
- this.mapVisit(node.rules, '\n\n') +
- this.indent(-1) +
- '\n}'
- );
- },
- keyframes: function(node) {
- return (
- '@'.concat(node.vendor, 'keyframes ') +
- node.name +
- ' {\n' +
- this.indent(1) +
- this.mapVisit(node.keyframes, '\n') +
- this.indent(-1) +
- '\n}'
- );
- },
- supports: function(node) {
- return (
- '@supports ' +
- node.supports +
- ' {\n' +
- this.indent(1) +
- this.mapVisit(node.rules, '\n\n') +
- this.indent(-1) +
- '\n}'
- );
- },
- keyframe: function(node) {
- return this.rule(node);
- },
- mapVisit: function(nodes, delimiter) {
- var str = '';
- for (var i = 0, len = nodes.length; i < len; i++) {
- var node = nodes[i];
- str += this[camelCase(node.type)](node);
- if (delimiter && i < len - 1) str += delimiter;
- }
- return str;
- },
- fontFace: function(node) {
- return (
- '@font-face {\n' +
- this.indent(1) +
- this.mapVisit(node.declarations, '\n') +
- this.indent(-1) +
- '\n}'
- );
- },
- rule: function(node) {
- return (
- this.indent() +
- node.selector +
- ' {\n' +
- this.indent(1) +
- this.mapVisit(node.declarations, '\n') +
- this.indent(-1) +
- '\n' +
- this.indent() +
- '}'
- );
- },
- declaration: function(node) {
- return this.indent() + node.property + ': ' + node.value + ';';
- },
- import: function(node) {
- return '@import '.concat(node.import, ';');
- },
- charset: function(node) {
- return '@charset '.concat(node.charset, ';');
- },
- namespace: function(node) {
- return '@namespace '.concat(node.namespace, ';');
- },
- indent: function(level) {
- if (level) {
- this.indentLevel += level;
- return '';
- }
- return repeat(this.indentation, this.indentLevel);
- }
- });
- var stripCmt = function(str) {
- return str.replace(regComments, '');
- };
- module.exports = exports;
|