You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

344 lines
5.2 KiB

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