You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
463 lines
12 KiB
463 lines
12 KiB
//.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) { |
|
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
|
|
|