CSSValueExpression.js 5.3 KB

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