CSSValueExpression.js
1 //.CommonJS 2 var CSSOM = { 3 CSSValue: require('./CSSValue').CSSValue 4 }; 5 ///CommonJS 6 7 8 /** 9 * @constructor 10 * @see http://msdn.microsoft.com/en-us/library/ms537634(v=vs.85).aspx 11 * 12 */ 13 CSSOM.CSSValueExpression = function CSSValueExpression(token, idx) { 14 this._token = token; 15 this._idx = idx; 16 }; 17 18 CSSOM.CSSValueExpression.prototype = new CSSOM.CSSValue(); 19 CSSOM.CSSValueExpression.prototype.constructor = CSSOM.CSSValueExpression; 20 21 /** 22 * parse css expression() value 23 * 24 * @return {Object} 25 * - error: 26 * or 27 * - idx: 28 * - expression: 29 * 30 * Example: 31 * 32 * .selector { 33 * zoom: expression(documentElement.clientWidth > 1000 ? '1000px' : 'auto'); 34 * } 35 */ 36 CSSOM.CSSValueExpression.prototype.parse = function() { 37 var token = this._token, 38 idx = this._idx; 39 40 var character = '', 41 expression = '', 42 error = '', 43 info, 44 paren = []; 45 46 47 for (; ; ++idx) { 48 character = token.charAt(idx); 49 50 // end of token 51 if (character === '') { 52 error = 'css expression error: unfinished expression!'; 53 break; 54 } 55 56 switch(character) { 57 case '(': 58 paren.push(character); 59 expression += character; 60 break; 61 62 case ')': 63 paren.pop(character); 64 expression += character; 65 break; 66 67 case '/': 68 if ((info = this._parseJSComment(token, idx))) { // comment? 69 if (info.error) { 70 error = 'css expression error: unfinished comment in expression!'; 71 } else { 72 idx = info.idx; 73 // ignore the comment 74 } 75 } else if ((info = this._parseJSRexExp(token, idx))) { // regexp 76 idx = info.idx; 77 expression += info.text; 78 } else { // other 79 expression += character; 80 } 81 break; 82 83 case "'": 84 case '"': 85 info = this._parseJSString(token, idx, character); 86 if (info) { // string 87 idx = info.idx; 88 expression += info.text; 89 } else { 90 expression += character; 91 } 92 break; 93 94 default: 95 expression += character; 96 break; 97 } 98 99 if (error) { 100 break; 101 } 102 103 // end of expression 104 if (paren.length === 0) { 105 break; 106 } 107 } 108 109 var ret; 110 if (error) { 111 ret = { 112 error: error 113 }; 114 } else { 115 ret = { 116 idx: idx, 117 expression: expression 118 }; 119 } 120 121 return ret; 122 }; 123 124 125 /** 126 * 127 * @return {Object|false} 128 * - idx: 129 * - text: 130 * or 131 * - error: 132 * or 133 * false 134 * 135 */ 136 CSSOM.CSSValueExpression.prototype._parseJSComment = function(token, idx) { 137 var nextChar = token.charAt(idx + 1), 138 text; 139 140 if (nextChar === '/' || nextChar === '*') { 141 var startIdx = idx, 142 endIdx, 143 commentEndChar; 144 145 if (nextChar === '/') { // line comment 146 commentEndChar = '\n'; 147 } else if (nextChar === '*') { // block comment 148 commentEndChar = '*/'; 149 } 150 151 endIdx = token.indexOf(commentEndChar, startIdx + 1 + 1); 152 if (endIdx !== -1) { 153 endIdx = endIdx + commentEndChar.length - 1; 154 text = token.substring(idx, endIdx + 1); 155 return { 156 idx: endIdx, 157 text: text 158 }; 159 } else { 160 var error = 'css expression error: unfinished comment in expression!'; 161 return { 162 error: error 163 }; 164 } 165 } else { 166 return false; 167 } 168 }; 169 170 171 /** 172 * 173 * @return {Object|false} 174 * - idx: 175 * - text: 176 * or 177 * false 178 * 179 */ 180 CSSOM.CSSValueExpression.prototype._parseJSString = function(token, idx, sep) { 181 var endIdx = this._findMatchedIdx(token, idx, sep), 182 text; 183 184 if (endIdx === -1) { 185 return false; 186 } else { 187 text = token.substring(idx, endIdx + sep.length); 188 189 return { 190 idx: endIdx, 191 text: text 192 }; 193 } 194 }; 195 196 197 /** 198 * parse regexp in css expression 199 * 200 * @return {Object|false} 201 * - idx: 202 * - regExp: 203 * or 204 * false 205 */ 206 207 /* 208 209 all legal RegExp 210 211 /a/ 212 (/a/) 213 [/a/] 214 [12, /a/] 215 216 !/a/ 217 218 +/a/ 219 -/a/ 220 * /a/ 221 / /a/ 222 %/a/ 223 224 ===/a/ 225 !==/a/ 226 ==/a/ 227 !=/a/ 228 >/a/ 229 >=/a/ 230 </a/ 231 <=/a/ 232 233 &/a/ 234 |/a/ 235 ^/a/ 236 ~/a/ 237 <</a/ 238 >>/a/ 239 >>>/a/ 240 241 &&/a/ 242 ||/a/ 243 ?/a/ 244 =/a/ 245 ,/a/ 246 247 delete /a/ 248 in /a/ 249 instanceof /a/ 250 new /a/ 251 typeof /a/ 252 void /a/ 253 254 */ 255 CSSOM.CSSValueExpression.prototype._parseJSRexExp = function(token, idx) { 256 var before = token.substring(0, idx).replace(/\s+$/, ""), 257 legalRegx = [ 258 /^$/, 259 /\($/, 260 /\[$/, 261 /\!$/, 262 /\+$/, 263 /\-$/, 264 /\*$/, 265 /\/\s+/, 266 /\%$/, 267 /\=$/, 268 /\>$/, 269 /<$/, 270 /\&$/, 271 /\|$/, 272 /\^$/, 273 /\~$/, 274 /\?$/, 275 /\,$/, 276 /delete$/, 277 /in$/, 278 /instanceof$/, 279 /new$/, 280 /typeof$/, 281 /void$/ 282 ]; 283 284 var isLegal = legalRegx.some(function(reg) { 285 return reg.test(before); 286 }); 287 288 if (!isLegal) { 289 return false; 290 } else { 291 var sep = '/'; 292 293 // same logic as string 294 return this._parseJSString(token, idx, sep); 295 } 296 }; 297 298 299 /** 300 * 301 * find next sep(same line) index in `token` 302 * 303 * @return {Number} 304 * 305 */ 306 CSSOM.CSSValueExpression.prototype._findMatchedIdx = function(token, idx, sep) { 307 var startIdx = idx, 308 endIdx; 309 310 var NOT_FOUND = -1; 311 312 while(true) { 313 endIdx = token.indexOf(sep, startIdx + 1); 314 315 if (endIdx === -1) { // not found 316 endIdx = NOT_FOUND; 317 break; 318 } else { 319 var text = token.substring(idx + 1, endIdx), 320 matched = text.match(/\\+$/); 321 if (!matched || matched[0] % 2 === 0) { // not escaped 322 break; 323 } else { 324 startIdx = endIdx; 325 } 326 } 327 } 328 329 // boundary must be in the same line(js sting or regexp) 330 var nextNewLineIdx = token.indexOf('\n', idx + 1); 331 if (nextNewLineIdx < endIdx) { 332 endIdx = NOT_FOUND; 333 } 334 335 336 return endIdx; 337 }; 338 339 340 341 342 //.CommonJS 343 exports.CSSValueExpression = CSSOM.CSSValueExpression; 344 ///CommonJS