505 lines
13 KiB
JavaScript
505 lines
13 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,
|
||
|
"containerBlock": true,
|
||
|
"conditionBlock": true,
|
||
|
'documentRule-begin': true
|
||
|
};
|
||
|
|
||
|
var styleSheet = new CSSOM.CSSStyleSheet();
|
||
|
|
||
|
// @type CSSStyleSheet|CSSMediaRule|CSSContainerRule|CSSSupportsRule|CSSFontFaceRule|CSSKeyframesRule|CSSDocumentRule
|
||
|
var currentScope = styleSheet;
|
||
|
|
||
|
// @type CSSMediaRule|CSSContainerRule|CSSSupportsRule|CSSKeyframesRule|CSSDocumentRule
|
||
|
var parentRule;
|
||
|
|
||
|
var ancestorRules = [];
|
||
|
var hasAncestors = false;
|
||
|
var prevScope;
|
||
|
|
||
|
var name, priority="", styleRule, mediaRule, containerRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule;
|
||
|
|
||
|
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("@container", i) === i) {
|
||
|
state = "containerBlock";
|
||
|
containerRule = new CSSOM.CSSContainerRule();
|
||
|
containerRule.__starts = i;
|
||
|
i += "container".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("@starting-style", i) === i) {
|
||
|
state = "startingStyleRule-begin";
|
||
|
i += "starting-style".length;
|
||
|
startingStyleRule = new CSSOM.CSSStartingStyleRule();
|
||
|
startingStyleRule.__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 === "containerBlock") {
|
||
|
containerRule.containerText = buffer.trim();
|
||
|
|
||
|
if (parentRule) {
|
||
|
ancestorRules.push(parentRule);
|
||
|
}
|
||
|
currentScope = parentRule = containerRule;
|
||
|
containerRule.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 === "startingStyleRule-begin") {
|
||
|
if (parentRule) {
|
||
|
ancestorRules.push(parentRule);
|
||
|
}
|
||
|
|
||
|
currentScope = parentRule = startingStyleRule;
|
||
|
startingStyleRule.parentStyleSheet = styleSheet;
|
||
|
buffer = "";
|
||
|
state = "before-selector";
|
||
|
|
||
|
} else if (state === "fontFaceRule-begin") {
|
||
|
if (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"
|
||
|
|| parentRule.constructor.name === "CSSContainerRule"
|
||
|
|| parentRule.constructor.name === "CSSStartingStyleRule"
|
||
|
) {
|
||
|
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.CSSGroupingRule = require("./CSSGroupingRule").CSSGroupingRule;
|
||
|
CSSOM.CSSMediaRule = require("./CSSMediaRule").CSSMediaRule;
|
||
|
CSSOM.CSSContainerRule = require("./CSSContainerRule").CSSContainerRule;
|
||
|
CSSOM.CSSConditionRule = require("./CSSConditionRule").CSSConditionRule;
|
||
|
CSSOM.CSSSupportsRule = require("./CSSSupportsRule").CSSSupportsRule;
|
||
|
CSSOM.CSSFontFaceRule = require("./CSSFontFaceRule").CSSFontFaceRule;
|
||
|
CSSOM.CSSHostRule = require("./CSSHostRule").CSSHostRule;
|
||
|
CSSOM.CSSStartingStyleRule = require("./CSSStartingStyleRule").CSSStartingStyleRule;
|
||
|
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
|