|
|
//.CommonJS
var CSSOM = { CSSValue: require('./CSSValue').CSSValue }; ///CommonJS
/** * @constructor * @see http://msdn.microsoft.com/en-us/library/ms537634(v=vs.85).aspx
* */ CSSOM.CSSValueExpression = function CSSValueExpression(token, idx) { this._token = token; this._idx = idx; };
CSSOM.CSSValueExpression.prototype = new CSSOM.CSSValue(); CSSOM.CSSValueExpression.prototype.constructor = CSSOM.CSSValueExpression;
/** * parse css expression() value * * @return {Object} * - error: * or * - idx: * - expression: * * Example: * * .selector { * zoom: expression(documentElement.clientWidth > 1000 ? '1000px' : 'auto'); * } */ CSSOM.CSSValueExpression.prototype.parse = function() { var token = this._token, idx = this._idx;
var character = '', expression = '', error = '', info, paren = [];
for (; ; ++idx) { character = token.charAt(idx);
// end of token
if (character === '') { error = 'css expression error: unfinished expression!'; break; }
switch(character) { case '(': paren.push(character); expression += character; break;
case ')': paren.pop(character); expression += character; break;
case '/': if ((info = this._parseJSComment(token, idx))) { // comment?
if (info.error) { error = 'css expression error: unfinished comment in expression!'; } else { idx = info.idx; // ignore the comment
} } else if ((info = this._parseJSRexExp(token, idx))) { // regexp
idx = info.idx; expression += info.text; } else { // other
expression += character; } break;
case "'": case '"': info = this._parseJSString(token, idx, character); if (info) { // string
idx = info.idx; expression += info.text; } else { expression += character; } break;
default: expression += character; break; }
if (error) { break; }
// end of expression
if (paren.length === 0) { break; } }
var ret; if (error) { ret = { error: error }; } else { ret = { idx: idx, expression: expression }; }
return ret; };
/** * * @return {Object|false} * - idx: * - text: * or * - error: * or * false * */ CSSOM.CSSValueExpression.prototype._parseJSComment = function(token, idx) { var nextChar = token.charAt(idx + 1), text;
if (nextChar === '/' || nextChar === '*') { var startIdx = idx, endIdx, commentEndChar;
if (nextChar === '/') { // line comment
commentEndChar = '\n'; } else if (nextChar === '*') { // block comment
commentEndChar = '*/'; }
endIdx = token.indexOf(commentEndChar, startIdx + 1 + 1); if (endIdx !== -1) { endIdx = endIdx + commentEndChar.length - 1; text = token.substring(idx, endIdx + 1); return { idx: endIdx, text: text }; } else { var error = 'css expression error: unfinished comment in expression!'; return { error: error }; } } else { return false; } };
/** * * @return {Object|false} * - idx: * - text: * or * false * */ CSSOM.CSSValueExpression.prototype._parseJSString = function(token, idx, sep) { var endIdx = this._findMatchedIdx(token, idx, sep), text;
if (endIdx === -1) { return false; } else { text = token.substring(idx, endIdx + sep.length);
return { idx: endIdx, text: text }; } };
/** * parse regexp in css expression * * @return {Object|false} * - idx: * - regExp: * or * false */
/*
all legal RegExp /a/ (/a/) [/a/] [12, /a/]
!/a/
+/a/ -/a/ * /a/ / /a/ %/a/
===/a/ !==/a/ ==/a/ !=/a/ >/a/ >=/a/ </a/ <=/a/
&/a/ |/a/ ^/a/ ~/a/ <</a/ >>/a/ >>>/a/
&&/a/ ||/a/ ?/a/ =/a/ ,/a/
delete /a/ in /a/ instanceof /a/ new /a/ typeof /a/ void /a/
*/ CSSOM.CSSValueExpression.prototype._parseJSRexExp = function(token, idx) { var before = token.substring(0, idx).replace(/\s+$/, ""), legalRegx = [ /^$/, /\($/, /\[$/, /\!$/, /\+$/, /\-$/, /\*$/, /\/\s+/, /\%$/, /\=$/, /\>$/, /<$/, /\&$/, /\|$/, /\^$/, /\~$/, /\?$/, /\,$/, /delete$/, /in$/, /instanceof$/, /new$/, /typeof$/, /void$/ ];
var isLegal = legalRegx.some(function(reg) { return reg.test(before); });
if (!isLegal) { return false; } else { var sep = '/';
// same logic as string
return this._parseJSString(token, idx, sep); } };
/** * * find next sep(same line) index in `token` * * @return {Number} * */ CSSOM.CSSValueExpression.prototype._findMatchedIdx = function(token, idx, sep) { var startIdx = idx, endIdx;
var NOT_FOUND = -1;
while(true) { endIdx = token.indexOf(sep, startIdx + 1);
if (endIdx === -1) { // not found
endIdx = NOT_FOUND; break; } else { var text = token.substring(idx + 1, endIdx), matched = text.match(/\\+$/); if (!matched || matched[0] % 2 === 0) { // not escaped
break; } else { startIdx = endIdx; } } }
// boundary must be in the same line(js sting or regexp)
var nextNewLineIdx = token.indexOf('\n', idx + 1); if (nextNewLineIdx < endIdx) { endIdx = NOT_FOUND; }
return endIdx; };
//.CommonJS
exports.CSSValueExpression = CSSOM.CSSValueExpression; ///CommonJS
|