diff --git a/.gitignore b/.gitignore index 697f01c..7837f62 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /lib/index.js /build/ /node_modules/ + +*.swp diff --git a/lib/CSSValue.js b/lib/CSSValue.js new file mode 100644 index 0000000..f2facb1 --- /dev/null +++ b/lib/CSSValue.js @@ -0,0 +1,43 @@ +//.CommonJS +var CSSOM = {}; +///CommonJS + + +/** + * @constructor + * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSValue + * + * TODO: add if needed + */ +CSSOM.CSSValue = function CSSValue() { +}; + +CSSOM.CSSValue.prototype = { + constructor: CSSOM.CSSValue, + + // @see: http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSValue + set cssText() { + var name = this._getConstructorName(); + + throw new Exception('DOMException: property "cssText" of "' + name + '" is readonly!'); + }, + + get cssText() { + var name = this._getConstructorName(); + + throw new Exception('getter "cssText" of "' + name + '" is not implemented!'); + }, + + _getConstructorName: function() { + var s = this.constructor.toString(), + c = s.match(/function\s([^\(]+)/), + name = c[1]; + + return name; + } +}; + + +//.CommonJS +exports.CSSValue = CSSOM.CSSValue; +///CommonJS diff --git a/lib/CSSValueExpression.js b/lib/CSSValueExpression.js new file mode 100644 index 0000000..b6d4756 --- /dev/null +++ b/lib/CSSValueExpression.js @@ -0,0 +1,344 @@ +//.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 { + 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/ + + 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).trimRight(), + 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 diff --git a/lib/parse.js b/lib/parse.js index fbcdc76..ef68df7 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -11,14 +11,14 @@ CSSOM.parse = function parse(token) { var i = 0; /** - "before-selector" or - "selector" or - "atRule" or - "atBlock" or - "before-name" or - "name" or - "before-value" or - "value" + "before-selector" or + "selector" or + "atRule" or + "atBlock" or + "before-name" or + "name" or + "before-value" or + "value" */ var state = "before-selector"; @@ -220,15 +220,28 @@ CSSOM.parse = function parse(token) { case '(': if (state === 'value') { - index = token.indexOf(')', i + 1); - if (index === -1) { - parseError('Unmatched "("'); + // 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 { + index = token.indexOf(')', i + 1); + if (index === -1) { + parseError('Unmatched "("'); + } + buffer += token.slice(i, index + 1); + i = index; } - buffer += token.slice(i, index + 1); - i = index; } else { buffer += character; } + break; case "!": @@ -341,4 +354,5 @@ CSSOM.CSSFontFaceRule = require("./CSSFontFaceRule").CSSFontFaceRule; CSSOM.CSSStyleDeclaration = require('./CSSStyleDeclaration').CSSStyleDeclaration; CSSOM.CSSKeyframeRule = require('./CSSKeyframeRule').CSSKeyframeRule; CSSOM.CSSKeyframesRule = require('./CSSKeyframesRule').CSSKeyframesRule; +CSSOM.CSSValueExpression = require('./CSSValueExpression').CSSValueExpression; ///CommonJS diff --git a/spec/CSSValueExpression.spec.js b/spec/CSSValueExpression.spec.js new file mode 100644 index 0000000..7ace60b --- /dev/null +++ b/spec/CSSValueExpression.spec.js @@ -0,0 +1,51 @@ +describe('CSSOM', function() { +describe('CSSValueExpression', function() { + + var END = '__EOL__'; + + + var cssExpressionValue = [ +"(function(hash){", +" if (!hash.match(/#[0-9a-f]{3,6}/g)) {", +" hash = hash.substr(1);", +" if (!hash) {", +" hash = '#ccc';", +" }", +" }", + +" var n1 = 4/5;", + +" // hello line comment", + +" var n2 = 5/6;", + +" var r1 = /hello ( /img;", + +" // hello line comment", + +" /* hello block comment */", + +" return hash;", +"}(location.hash))", + +END + + ].join('\n'); + + + + given(cssExpressionValue, function(token) { + var i = 0; + + var info = (new CSSOM.CSSValueExpression(token, i)).parse(); + + expect(info.idx).toBeDefined(); + + var end = token.substr(info.idx + 1); + end = end.trim(); + expect(end).toBe(END); + }); + + +}); +}); diff --git a/spec/index.html b/spec/index.html index b66aa53..455d3ed 100644 --- a/spec/index.html +++ b/spec/index.html @@ -21,6 +21,7 @@ +