Parser.js 7.47 KB
//.CommonJS
var CSSOM = {
	CSSStyleSheet: require("./CSSStyleSheet").CSSStyleSheet,
	CSSStyleRule: require("./CSSStyleRule").CSSStyleRule,
	CSSImportRule: require("./CSSImportRule").CSSImportRule,
	CSSMediaRule: require("./CSSMediaRule").CSSMediaRule
};
///CommonJS


CSSOM.Parser = function Parser() {};

/**
 * @param {string} cssText
 * @param {Object} options
 */
CSSOM.Parser.prototype.parseStyleSheet = function(cssText, options) {
	options = options || {};
	var i = options.startIndex || 0;

	for (var character; character = token.charAt(i); i++) {
		switch (character) {

		case " ":
		case "\t":
		case "\r":
		case "\n":
		case "\f":
			if (SIGNIFICANT_WHITESPACE[state]) {
				buffer += character;
			}
			break;
	}
};

CSSOM.Parser.prototype.parse = function(token, options) {

	options = options || {};
	var i = options.startIndex || 0;

	this.styleSheetStart(i);

	/**
	  "before-selector" or
	  "selector" or
	  "atRule" or
	  "atBlock" or
	  "before-name" or
	  "name" or
	  "before-value" or
	  "value"
	*/
	var state = options.state || "before-selector";

	var index;
	var j = i;
	var buffer = "";

	var SIGNIFICANT_WHITESPACE = {
		"selector": true,
		"value": true,
		"atRule": true,
		"importRule-begin": true,
		"importRule": true,
		"atBlock": true
	};

	var styleSheet = new CSSOM.CSSStyleSheet;

	// @type CSSStyleSheet|CSSMediaRule
	var currentScope = styleSheet;

	var selector, name, value, priority="", styleRule, mediaRule, importRule;

	var declarationStarts;
	var declarationEnds;

	for (var character; character = token.charAt(i); i++) {

		switch (character) {

		case " ":
		case "\t":
		case "\r":
		case "\n":
		case "\f":
			if (SIGNIFICANT_WHITESPACE[state]) {
				buffer += character;
			}
			break;

		// String
		case '"':
			j = i + 1;
			index = token.indexOf('"', j) + 1;
			if (!index) {
				throw '" is missing';
			}
			buffer += token.slice(i, index);
			i = index - 1;
			if (state == 'before-value') {
				state = 'value';
			}
			break;

		case "'":
			j = i + 1;
			index = token.indexOf("'", j) + 1;
			if (!index) {
				throw "' is missing";
			}
			buffer += token.slice(i, index);
			i = index - 1;
			switch (state) {
				case 'before-value':
					state = 'value';
					break;
				case 'importRule-begin':
					state = 'importRule';
					break;
			}
			break;

		// Comment
		case "/":
			if (token.charAt(i + 1) == "*") {
				i += 2;
				index = token.indexOf("*/", i);
				if (index == -1) {
					throw SyntaxError("Missing */");
				} else {
					i = index + 1;
				}
			} else {
				buffer += character;
			}
			if (state == "importRule-begin") {
				buffer += " ";
				state = "importRule";
			}
			break;

		// At-rule
		case "@":
			if (token.indexOf("@media", i) == i) {
				state = "atBlock";
				mediaRule = new CSSOM.CSSMediaRule;
				mediaRule.__starts = i;
				i += "media".length;
				buffer = "";
				break;
			} else if (token.indexOf("@import", i) == i) {
				state = "importRule-begin";
				i += "import".length;
				buffer += "@import";
				break;
			} else if (state == "selector") {
				state = "atRule";
			}
			buffer += character;
			break;

		case "{":
			if (state == "selector" || state == "atRule") {
				this.selectorEnd(i, buffer);
				buffer = "";
				state = "before-name";
			} else if (state == "atBlock") {
				mediaRule.media.mediaText = buffer.trim();
				currentScope = mediaRule;
				buffer = "";
				state = "before-selector";
			}
			break;

		case ":":
			if (state == "name") {
				name = buffer;
				buffer = "";
				state = "before-value";
			} else {
				buffer += character;
			}
			break;

		case '(':
			if (state == 'value') {
				index = token.indexOf(')', i + 1);
				if (index == -1) {
					throw i + ': unclosed "("';
				}
				buffer += token.slice(i, index + 1);
				i = index;
			} else {
				buffer += character;
			}
			break;

		case "!":
			if (state == "value" && token.indexOf("!important", i) === i) {
				priority = "important";
				i += "important".length;
			} else {
				buffer += character;
			}
			break;

		case ";":
			switch (state) {
				case "value":
					this.declarationEnd(i, name, buffer, priority);
					priority = "";
					buffer = "";
					state = "before-name";
					break;
				case "atRule":
					buffer = "";
					state = "before-selector";
					break;
				case "importRule":
					importRule = new CSSOM.CSSImportRule;
					importRule.cssText = buffer + character;
					currentScope.cssRules.push(importRule);
					buffer = "";
					state = "before-selector";
					break;
				default:
					buffer += character;
					break;
			}
			break;

		case "}":
			switch (state) {
				case "value":
					this.declarationEnd(i, name, buffer, priority);
					// fall down
				case "before-name":
					this.styleRuleEnd(i);
					buffer = "";
					break;
				case "name":
					throw i + ": Oops";
					break;
				case "before-selector":
				case "selector":
					// End of media rule.
					// Nesting rules aren't supported yet
					if (!mediaRule) {
						throw "unexpected }";
					}
					mediaRule.__ends = i + 1;
					styleSheet.cssRules.push(mediaRule);
					currentScope = styleSheet;
					buffer = "";
					break;
			}
			state = "before-selector";
			break;

		default:
			switch (state) {
				case "before-selector":
					this.styleRuleStart(i);
					state = "selector";
					break;
				case "before-name":
					state = "name";
					break;
				case "before-value":
					state = "value";
					break;
				case "importRule-begin":
					state = "importRule";
					break;
			}
			buffer += character;
			break;
		}
	}

	return styleSheet;
};

CSSOM.Parser.prototype.compile = function() {
	var handlers = {
		styleSheetStart: this.styleSheetStart,
		styleRuleStart: this.styleRuleStart,
		selectorEnd: this.selectorEnd,
		declarationEnd: this.declarationEnd,
		styleRuleEnd: this.styleRuleEnd,
		styleSheetEnd: this.styleSheetEnd
	};
	var parser = this.parse.toString();
	for (var key in handlers) {
		if (!handlers.hasOwnProperty(key)) {
			continue;
		}
		parser = parser.replace(new RegExp('^.*' + key + '.*$', 'gm'), handlers[key].toString()
			.replace(/^function.+$/m, '')
			.replace(/^}/m, ''))
			.replace(/this\.?/g, '');
	}
	return parser;
};

CSSOM.Parser.prototype.styleSheetStart = function(i) {
	console.log('styleSheetStart', i);
	this.styleSheet = new CSSOM.CSSStyleSheet;
	this.scopeRules = this.styleSheet.cssRules;
};

CSSOM.Parser.prototype.styleRuleStart = function(i) {
	console.log('styleRuleStart', i);
	this.styleRule = new CSSOM.CSSStyleRule;
	this.styleRule._start = i;
};

CSSOM.Parser.prototype.selectorEnd = function(i, buffer) {
	this.styleRule.selectorText = buffer.trimRight();
	this.styleRule.style._start = i;
};

CSSOM.Parser.prototype.declarationEnd = function(name, value, priority, startIndex, endIndex) {
	console.log('declarationEnd', name, value, priority, startIndex, endIndex);
};

CSSOM.Parser.prototype.styleRuleEnd = function(i) {
	this.styleRule._end = i;
	this.scopeRules.push(this.styleRule);
};


CSSOM.Parser.prototype.styleSheetEnd = function(i) {
	return this.styleSheet;
};

/*
Parser.prototype.nameStart = function(i) {
	this.nameStartIndex = i;
};

Parser.prototype.nameEnd = function(i, buffer) {
	this.name = buffer.trimRight();
	this.nameEndIndex = this.nameStartIndex + this.name.length;
};


Parser.prototype.valueStart = function(i) {
	this.valueStartIndex = i;
};

Parser.prototype.valueEnd = function(i, buffer) {
	var value = buffer.trimRight();
	this.styleRule.style.add(this.name, value, this.nameStartIndex, this.nameEndIndex, this.valueStartIndex, this.valueStartIndex + value.length);
};
*/


//.CommonJS
exports.Parser = CSSOM.Parser;
///CommonJS