465 lines
12 KiB
JavaScript
465 lines
12 KiB
JavaScript
//.CommonJS
|
|
var CSSOM = {};
|
|
///CommonJS
|
|
|
|
|
|
/**
|
|
* @param {string} token
|
|
*/
|
|
CSSOM.parse = function parse(token) {
|
|
|
|
var i = 0;
|
|
|
|
/**
|
|
"before-selector" or
|
|
"selector" or
|
|
"atRule" or
|
|
"atBlock" or
|
|
"conditionBlock" or
|
|
"before-name" or
|
|
"name" or
|
|
"before-value" or
|
|
"value"
|
|
*/
|
|
var state = "before-selector";
|
|
|
|
var index;
|
|
var buffer = "";
|
|
var valueParenthesisDepth = 0;
|
|
|
|
var SIGNIFICANT_WHITESPACE = {
|
|
"selector": true,
|
|
"value": true,
|
|
"value-parenthesis": true,
|
|
"atRule": true,
|
|
"importRule-begin": true,
|
|
"importRule": true,
|
|
"atBlock": true,
|
|
"conditionBlock": true,
|
|
'documentRule-begin': true
|
|
};
|
|
|
|
var styleSheet = new CSSOM.CSSStyleSheet();
|
|
|
|
// @type CSSStyleSheet|CSSMediaRule|CSSSupportsRule|CSSFontFaceRule|CSSKeyframesRule|CSSDocumentRule
|
|
var currentScope = styleSheet;
|
|
|
|
// @type CSSMediaRule|CSSSupportsRule|CSSKeyframesRule|CSSDocumentRule
|
|
var parentRule;
|
|
|
|
var ancestorRules = [];
|
|
var hasAncestors = false;
|
|
var prevScope;
|
|
|
|
var name, priority="", styleRule, mediaRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule;
|
|
|
|
var atKeyframesRegExp = /@(-(?:\w+-)+)?keyframes/g;
|
|
|
|
var parseError = function(message) {
|
|
var lines = token.substring(0, i).split('\n');
|
|
var lineCount = lines.length;
|
|
var charCount = lines.pop().length + 1;
|
|
var error = new Error(message + ' (line ' + lineCount + ', char ' + charCount + ')');
|
|
error.line = lineCount;
|
|
/* jshint sub : true */
|
|
error['char'] = charCount;
|
|
error.styleSheet = styleSheet;
|
|
throw error;
|
|
};
|
|
|
|
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 '"':
|
|
index = i + 1;
|
|
do {
|
|
index = token.indexOf('"', index) + 1;
|
|
if (!index) {
|
|
parseError('Unmatched "');
|
|
}
|
|
} while (token[index - 2] === '\\');
|
|
buffer += token.slice(i, index);
|
|
i = index - 1;
|
|
switch (state) {
|
|
case 'before-value':
|
|
state = 'value';
|
|
break;
|
|
case 'importRule-begin':
|
|
state = 'importRule';
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case "'":
|
|
index = i + 1;
|
|
do {
|
|
index = token.indexOf("'", index) + 1;
|
|
if (!index) {
|
|
parseError("Unmatched '");
|
|
}
|
|
} while (token[index - 2] === '\\');
|
|
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) {
|
|
parseError("Missing */");
|
|
} else {
|
|
i = index + 1;
|
|
}
|
|
} else {
|
|
buffer += character;
|
|
}
|
|
if (state === "importRule-begin") {
|
|
buffer += " ";
|
|
state = "importRule";
|
|
}
|
|
break;
|
|
|
|
// At-rule
|
|
case "@":
|
|
if (token.indexOf("@-moz-document", i) === i) {
|
|
state = "documentRule-begin";
|
|
documentRule = new CSSOM.CSSDocumentRule();
|
|
documentRule.__starts = i;
|
|
i += "-moz-document".length;
|
|
buffer = "";
|
|
break;
|
|
} else if (token.indexOf("@media", i) === i) {
|
|
state = "atBlock";
|
|
mediaRule = new CSSOM.CSSMediaRule();
|
|
mediaRule.__starts = i;
|
|
i += "media".length;
|
|
buffer = "";
|
|
break;
|
|
} else if (token.indexOf("@supports", i) === i) {
|
|
state = "conditionBlock";
|
|
supportsRule = new CSSOM.CSSSupportsRule();
|
|
supportsRule.__starts = i;
|
|
i += "supports".length;
|
|
buffer = "";
|
|
break;
|
|
} else if (token.indexOf("@host", i) === i) {
|
|
state = "hostRule-begin";
|
|
i += "host".length;
|
|
hostRule = new CSSOM.CSSHostRule();
|
|
hostRule.__starts = i;
|
|
buffer = "";
|
|
break;
|
|
} else if (token.indexOf("@import", i) === i) {
|
|
state = "importRule-begin";
|
|
i += "import".length;
|
|
buffer += "@import";
|
|
break;
|
|
} else if (token.indexOf("@font-face", i) === i) {
|
|
state = "fontFaceRule-begin";
|
|
i += "font-face".length;
|
|
fontFaceRule = new CSSOM.CSSFontFaceRule();
|
|
fontFaceRule.__starts = i;
|
|
buffer = "";
|
|
break;
|
|
} else {
|
|
atKeyframesRegExp.lastIndex = i;
|
|
var matchKeyframes = atKeyframesRegExp.exec(token);
|
|
if (matchKeyframes && matchKeyframes.index === i) {
|
|
state = "keyframesRule-begin";
|
|
keyframesRule = new CSSOM.CSSKeyframesRule();
|
|
keyframesRule.__starts = i;
|
|
keyframesRule._vendorPrefix = matchKeyframes[1]; // Will come out as undefined if no prefix was found
|
|
i += matchKeyframes[0].length - 1;
|
|
buffer = "";
|
|
break;
|
|
} else if (state === "selector") {
|
|
state = "atRule";
|
|
}
|
|
}
|
|
buffer += character;
|
|
break;
|
|
|
|
case "{":
|
|
if (state === "selector" || state === "atRule") {
|
|
styleRule.selectorText = buffer.trim();
|
|
styleRule.style.__starts = i;
|
|
buffer = "";
|
|
state = "before-name";
|
|
} else if (state === "atBlock") {
|
|
mediaRule.media.mediaText = buffer.trim();
|
|
|
|
if (parentRule) {
|
|
ancestorRules.push(parentRule);
|
|
}
|
|
|
|
currentScope = parentRule = mediaRule;
|
|
mediaRule.parentStyleSheet = styleSheet;
|
|
buffer = "";
|
|
state = "before-selector";
|
|
} else if (state === "conditionBlock") {
|
|
supportsRule.conditionText = buffer.trim();
|
|
|
|
if (parentRule) {
|
|
ancestorRules.push(parentRule);
|
|
}
|
|
|
|
currentScope = parentRule = supportsRule;
|
|
supportsRule.parentStyleSheet = styleSheet;
|
|
buffer = "";
|
|
state = "before-selector";
|
|
} else if (state === "hostRule-begin") {
|
|
if (parentRule) {
|
|
ancestorRules.push(parentRule);
|
|
}
|
|
|
|
currentScope = parentRule = hostRule;
|
|
hostRule.parentStyleSheet = styleSheet;
|
|
buffer = "";
|
|
state = "before-selector";
|
|
} else if (state === "fontFaceRule-begin") {
|
|
if (parentRule) {
|
|
ancestorRules.push(parentRule);
|
|
fontFaceRule.parentRule = parentRule;
|
|
}
|
|
fontFaceRule.parentStyleSheet = styleSheet;
|
|
styleRule = fontFaceRule;
|
|
buffer = "";
|
|
state = "before-name";
|
|
} else if (state === "keyframesRule-begin") {
|
|
keyframesRule.name = buffer.trim();
|
|
if (parentRule) {
|
|
ancestorRules.push(parentRule);
|
|
keyframesRule.parentRule = parentRule;
|
|
}
|
|
keyframesRule.parentStyleSheet = styleSheet;
|
|
currentScope = parentRule = keyframesRule;
|
|
buffer = "";
|
|
state = "keyframeRule-begin";
|
|
} else if (state === "keyframeRule-begin") {
|
|
styleRule = new CSSOM.CSSKeyframeRule();
|
|
styleRule.keyText = buffer.trim();
|
|
styleRule.__starts = i;
|
|
buffer = "";
|
|
state = "before-name";
|
|
} else if (state === "documentRule-begin") {
|
|
// FIXME: what if this '{' is in the url text of the match function?
|
|
documentRule.matcher.matcherText = buffer.trim();
|
|
if (parentRule) {
|
|
ancestorRules.push(parentRule);
|
|
documentRule.parentRule = parentRule;
|
|
}
|
|
currentScope = parentRule = documentRule;
|
|
documentRule.parentStyleSheet = styleSheet;
|
|
buffer = "";
|
|
state = "before-selector";
|
|
}
|
|
break;
|
|
|
|
case ":":
|
|
if (state === "name") {
|
|
name = buffer.trim();
|
|
buffer = "";
|
|
state = "before-value";
|
|
} else {
|
|
buffer += character;
|
|
}
|
|
break;
|
|
|
|
case "(":
|
|
if (state === 'value') {
|
|
// ie css expression mode
|
|
if (buffer.trim() === 'expression') {
|
|
var info = (new CSSOM.CSSValueExpression(token, i)).parse();
|
|
|
|
if (info.error) {
|
|
parseError(info.error);
|
|
} else {
|
|
buffer += info.expression;
|
|
i = info.idx;
|
|
}
|
|
} else {
|
|
state = 'value-parenthesis';
|
|
//always ensure this is reset to 1 on transition
|
|
//from value to value-parenthesis
|
|
valueParenthesisDepth = 1;
|
|
buffer += character;
|
|
}
|
|
} else if (state === 'value-parenthesis') {
|
|
valueParenthesisDepth++;
|
|
buffer += character;
|
|
} else {
|
|
buffer += character;
|
|
}
|
|
break;
|
|
|
|
case ")":
|
|
if (state === 'value-parenthesis') {
|
|
valueParenthesisDepth--;
|
|
if (valueParenthesisDepth === 0) state = 'value';
|
|
}
|
|
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":
|
|
styleRule.style.setProperty(name, buffer.trim(), priority);
|
|
priority = "";
|
|
buffer = "";
|
|
state = "before-name";
|
|
break;
|
|
case "atRule":
|
|
buffer = "";
|
|
state = "before-selector";
|
|
break;
|
|
case "importRule":
|
|
importRule = new CSSOM.CSSImportRule();
|
|
importRule.parentStyleSheet = importRule.styleSheet.parentStyleSheet = styleSheet;
|
|
importRule.cssText = buffer + character;
|
|
styleSheet.cssRules.push(importRule);
|
|
buffer = "";
|
|
state = "before-selector";
|
|
break;
|
|
default:
|
|
buffer += character;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case "}":
|
|
switch (state) {
|
|
case "value":
|
|
styleRule.style.setProperty(name, buffer.trim(), priority);
|
|
priority = "";
|
|
/* falls through */
|
|
case "before-name":
|
|
case "name":
|
|
styleRule.__ends = i + 1;
|
|
if (parentRule) {
|
|
styleRule.parentRule = parentRule;
|
|
}
|
|
styleRule.parentStyleSheet = styleSheet;
|
|
currentScope.cssRules.push(styleRule);
|
|
buffer = "";
|
|
if (currentScope.constructor === CSSOM.CSSKeyframesRule) {
|
|
state = "keyframeRule-begin";
|
|
} else {
|
|
state = "before-selector";
|
|
}
|
|
break;
|
|
case "keyframeRule-begin":
|
|
case "before-selector":
|
|
case "selector":
|
|
// End of media/supports/document rule.
|
|
if (!parentRule) {
|
|
parseError("Unexpected }");
|
|
}
|
|
|
|
// Handle rules nested in @media or @supports
|
|
hasAncestors = ancestorRules.length > 0;
|
|
|
|
while (ancestorRules.length > 0) {
|
|
parentRule = ancestorRules.pop();
|
|
|
|
if (
|
|
parentRule.constructor.name === "CSSMediaRule"
|
|
|| parentRule.constructor.name === "CSSSupportsRule"
|
|
) {
|
|
prevScope = currentScope;
|
|
currentScope = parentRule;
|
|
currentScope.cssRules.push(prevScope);
|
|
break;
|
|
}
|
|
|
|
if (ancestorRules.length === 0) {
|
|
hasAncestors = false;
|
|
}
|
|
}
|
|
|
|
if (!hasAncestors) {
|
|
currentScope.__ends = i + 1;
|
|
styleSheet.cssRules.push(currentScope);
|
|
currentScope = styleSheet;
|
|
parentRule = null;
|
|
}
|
|
|
|
buffer = "";
|
|
state = "before-selector";
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
switch (state) {
|
|
case "before-selector":
|
|
state = "selector";
|
|
styleRule = new CSSOM.CSSStyleRule();
|
|
styleRule.__starts = i;
|
|
break;
|
|
case "before-name":
|
|
state = "name";
|
|
break;
|
|
case "before-value":
|
|
state = "value";
|
|
break;
|
|
case "importRule-begin":
|
|
state = "importRule";
|
|
break;
|
|
}
|
|
buffer += character;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return styleSheet;
|
|
};
|
|
|
|
|
|
//.CommonJS
|
|
exports.parse = CSSOM.parse;
|
|
// The following modules cannot be included sooner due to the mutual dependency with parse.js
|
|
CSSOM.CSSStyleSheet = require("./CSSStyleSheet").CSSStyleSheet;
|
|
CSSOM.CSSStyleRule = require("./CSSStyleRule").CSSStyleRule;
|
|
CSSOM.CSSImportRule = require("./CSSImportRule").CSSImportRule;
|
|
CSSOM.CSSMediaRule = require("./CSSMediaRule").CSSMediaRule;
|
|
CSSOM.CSSSupportsRule = require("./CSSSupportsRule").CSSSupportsRule;
|
|
CSSOM.CSSFontFaceRule = require("./CSSFontFaceRule").CSSFontFaceRule;
|
|
CSSOM.CSSHostRule = require("./CSSHostRule").CSSHostRule;
|
|
CSSOM.CSSStyleDeclaration = require('./CSSStyleDeclaration').CSSStyleDeclaration;
|
|
CSSOM.CSSKeyframeRule = require('./CSSKeyframeRule').CSSKeyframeRule;
|
|
CSSOM.CSSKeyframesRule = require('./CSSKeyframesRule').CSSKeyframesRule;
|
|
CSSOM.CSSValueExpression = require('./CSSValueExpression').CSSValueExpression;
|
|
CSSOM.CSSDocumentRule = require('./CSSDocumentRule').CSSDocumentRule;
|
|
///CommonJS
|