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 @@
+