parse.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. //.CommonJS
  2. var CSSOM = {};
  3. ///CommonJS
  4. /**
  5. * @param {string} token
  6. */
  7. CSSOM.parse = function parse(token) {
  8. var i = 0;
  9. /**
  10. "before-selector" or
  11. "selector" or
  12. "atRule" or
  13. "atBlock" or
  14. "conditionBlock" or
  15. "before-name" or
  16. "name" or
  17. "before-value" or
  18. "value"
  19. */
  20. var state = "before-selector";
  21. var index;
  22. var buffer = "";
  23. var valueParenthesisDepth = 0;
  24. var SIGNIFICANT_WHITESPACE = {
  25. "selector": true,
  26. "value": true,
  27. "value-parenthesis": true,
  28. "atRule": true,
  29. "importRule-begin": true,
  30. "importRule": true,
  31. "atBlock": true,
  32. "conditionBlock": true,
  33. 'documentRule-begin': true
  34. };
  35. var styleSheet = new CSSOM.CSSStyleSheet();
  36. // @type CSSStyleSheet|CSSMediaRule|CSSSupportsRule|CSSFontFaceRule|CSSKeyframesRule|CSSDocumentRule
  37. var currentScope = styleSheet;
  38. // @type CSSMediaRule|CSSSupportsRule|CSSKeyframesRule|CSSDocumentRule
  39. var parentRule;
  40. var ancestorRules = [];
  41. var hasAncestors = false;
  42. var prevScope;
  43. var name, priority="", styleRule, mediaRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule;
  44. var atKeyframesRegExp = /@(-(?:\w+-)+)?keyframes/g;
  45. var parseError = function(message) {
  46. var lines = token.substring(0, i).split('\n');
  47. var lineCount = lines.length;
  48. var charCount = lines.pop().length + 1;
  49. var error = new Error(message + ' (line ' + lineCount + ', char ' + charCount + ')');
  50. error.line = lineCount;
  51. /* jshint sub : true */
  52. error['char'] = charCount;
  53. error.styleSheet = styleSheet;
  54. throw error;
  55. };
  56. for (var character; (character = token.charAt(i)); i++) {
  57. switch (character) {
  58. case " ":
  59. case "\t":
  60. case "\r":
  61. case "\n":
  62. case "\f":
  63. if (SIGNIFICANT_WHITESPACE[state]) {
  64. buffer += character;
  65. }
  66. break;
  67. // String
  68. case '"':
  69. index = i + 1;
  70. do {
  71. index = token.indexOf('"', index) + 1;
  72. if (!index) {
  73. parseError('Unmatched "');
  74. }
  75. } while (token[index - 2] === '\\');
  76. buffer += token.slice(i, index);
  77. i = index - 1;
  78. switch (state) {
  79. case 'before-value':
  80. state = 'value';
  81. break;
  82. case 'importRule-begin':
  83. state = 'importRule';
  84. break;
  85. }
  86. break;
  87. case "'":
  88. index = i + 1;
  89. do {
  90. index = token.indexOf("'", index) + 1;
  91. if (!index) {
  92. parseError("Unmatched '");
  93. }
  94. } while (token[index - 2] === '\\');
  95. buffer += token.slice(i, index);
  96. i = index - 1;
  97. switch (state) {
  98. case 'before-value':
  99. state = 'value';
  100. break;
  101. case 'importRule-begin':
  102. state = 'importRule';
  103. break;
  104. }
  105. break;
  106. // Comment
  107. case "/":
  108. if (token.charAt(i + 1) === "*") {
  109. i += 2;
  110. index = token.indexOf("*/", i);
  111. if (index === -1) {
  112. parseError("Missing */");
  113. } else {
  114. i = index + 1;
  115. }
  116. } else {
  117. buffer += character;
  118. }
  119. if (state === "importRule-begin") {
  120. buffer += " ";
  121. state = "importRule";
  122. }
  123. break;
  124. // At-rule
  125. case "@":
  126. if (token.indexOf("@-moz-document", i) === i) {
  127. state = "documentRule-begin";
  128. documentRule = new CSSOM.CSSDocumentRule();
  129. documentRule.__starts = i;
  130. i += "-moz-document".length;
  131. buffer = "";
  132. break;
  133. } else if (token.indexOf("@media", i) === i) {
  134. state = "atBlock";
  135. mediaRule = new CSSOM.CSSMediaRule();
  136. mediaRule.__starts = i;
  137. i += "media".length;
  138. buffer = "";
  139. break;
  140. } else if (token.indexOf("@supports", i) === i) {
  141. state = "conditionBlock";
  142. supportsRule = new CSSOM.CSSSupportsRule();
  143. supportsRule.__starts = i;
  144. i += "supports".length;
  145. buffer = "";
  146. break;
  147. } else if (token.indexOf("@host", i) === i) {
  148. state = "hostRule-begin";
  149. i += "host".length;
  150. hostRule = new CSSOM.CSSHostRule();
  151. hostRule.__starts = i;
  152. buffer = "";
  153. break;
  154. } else if (token.indexOf("@import", i) === i) {
  155. state = "importRule-begin";
  156. i += "import".length;
  157. buffer += "@import";
  158. break;
  159. } else if (token.indexOf("@font-face", i) === i) {
  160. state = "fontFaceRule-begin";
  161. i += "font-face".length;
  162. fontFaceRule = new CSSOM.CSSFontFaceRule();
  163. fontFaceRule.__starts = i;
  164. buffer = "";
  165. break;
  166. } else {
  167. atKeyframesRegExp.lastIndex = i;
  168. var matchKeyframes = atKeyframesRegExp.exec(token);
  169. if (matchKeyframes && matchKeyframes.index === i) {
  170. state = "keyframesRule-begin";
  171. keyframesRule = new CSSOM.CSSKeyframesRule();
  172. keyframesRule.__starts = i;
  173. keyframesRule._vendorPrefix = matchKeyframes[1]; // Will come out as undefined if no prefix was found
  174. i += matchKeyframes[0].length - 1;
  175. buffer = "";
  176. break;
  177. } else if (state === "selector") {
  178. state = "atRule";
  179. }
  180. }
  181. buffer += character;
  182. break;
  183. case "{":
  184. if (state === "selector" || state === "atRule") {
  185. styleRule.selectorText = buffer.trim();
  186. styleRule.style.__starts = i;
  187. buffer = "";
  188. state = "before-name";
  189. } else if (state === "atBlock") {
  190. mediaRule.media.mediaText = buffer.trim();
  191. if (parentRule) {
  192. ancestorRules.push(parentRule);
  193. }
  194. currentScope = parentRule = mediaRule;
  195. mediaRule.parentStyleSheet = styleSheet;
  196. buffer = "";
  197. state = "before-selector";
  198. } else if (state === "conditionBlock") {
  199. supportsRule.conditionText = buffer.trim();
  200. if (parentRule) {
  201. ancestorRules.push(parentRule);
  202. }
  203. currentScope = parentRule = supportsRule;
  204. supportsRule.parentStyleSheet = styleSheet;
  205. buffer = "";
  206. state = "before-selector";
  207. } else if (state === "hostRule-begin") {
  208. if (parentRule) {
  209. ancestorRules.push(parentRule);
  210. }
  211. currentScope = parentRule = hostRule;
  212. hostRule.parentStyleSheet = styleSheet;
  213. buffer = "";
  214. state = "before-selector";
  215. } else if (state === "fontFaceRule-begin") {
  216. if (parentRule) {
  217. ancestorRules.push(parentRule);
  218. fontFaceRule.parentRule = parentRule;
  219. }
  220. fontFaceRule.parentStyleSheet = styleSheet;
  221. styleRule = fontFaceRule;
  222. buffer = "";
  223. state = "before-name";
  224. } else if (state === "keyframesRule-begin") {
  225. keyframesRule.name = buffer.trim();
  226. if (parentRule) {
  227. ancestorRules.push(parentRule);
  228. keyframesRule.parentRule = parentRule;
  229. }
  230. keyframesRule.parentStyleSheet = styleSheet;
  231. currentScope = parentRule = keyframesRule;
  232. buffer = "";
  233. state = "keyframeRule-begin";
  234. } else if (state === "keyframeRule-begin") {
  235. styleRule = new CSSOM.CSSKeyframeRule();
  236. styleRule.keyText = buffer.trim();
  237. styleRule.__starts = i;
  238. buffer = "";
  239. state = "before-name";
  240. } else if (state === "documentRule-begin") {
  241. // FIXME: what if this '{' is in the url text of the match function?
  242. documentRule.matcher.matcherText = buffer.trim();
  243. if (parentRule) {
  244. ancestorRules.push(parentRule);
  245. documentRule.parentRule = parentRule;
  246. }
  247. currentScope = parentRule = documentRule;
  248. documentRule.parentStyleSheet = styleSheet;
  249. buffer = "";
  250. state = "before-selector";
  251. }
  252. break;
  253. case ":":
  254. if (state === "name") {
  255. name = buffer.trim();
  256. buffer = "";
  257. state = "before-value";
  258. } else {
  259. buffer += character;
  260. }
  261. break;
  262. case "(":
  263. if (state === 'value') {
  264. // ie css expression mode
  265. if (buffer.trim() === 'expression') {
  266. var info = (new CSSOM.CSSValueExpression(token, i)).parse();
  267. if (info.error) {
  268. parseError(info.error);
  269. } else {
  270. buffer += info.expression;
  271. i = info.idx;
  272. }
  273. } else {
  274. state = 'value-parenthesis';
  275. //always ensure this is reset to 1 on transition
  276. //from value to value-parenthesis
  277. valueParenthesisDepth = 1;
  278. buffer += character;
  279. }
  280. } else if (state === 'value-parenthesis') {
  281. valueParenthesisDepth++;
  282. buffer += character;
  283. } else {
  284. buffer += character;
  285. }
  286. break;
  287. case ")":
  288. if (state === 'value-parenthesis') {
  289. valueParenthesisDepth--;
  290. if (valueParenthesisDepth === 0) state = 'value';
  291. }
  292. buffer += character;
  293. break;
  294. case "!":
  295. if (state === "value" && token.indexOf("!important", i) === i) {
  296. priority = "important";
  297. i += "important".length;
  298. } else {
  299. buffer += character;
  300. }
  301. break;
  302. case ";":
  303. switch (state) {
  304. case "value":
  305. styleRule.style.setProperty(name, buffer.trim(), priority);
  306. priority = "";
  307. buffer = "";
  308. state = "before-name";
  309. break;
  310. case "atRule":
  311. buffer = "";
  312. state = "before-selector";
  313. break;
  314. case "importRule":
  315. importRule = new CSSOM.CSSImportRule();
  316. importRule.parentStyleSheet = importRule.styleSheet.parentStyleSheet = styleSheet;
  317. importRule.cssText = buffer + character;
  318. styleSheet.cssRules.push(importRule);
  319. buffer = "";
  320. state = "before-selector";
  321. break;
  322. default:
  323. buffer += character;
  324. break;
  325. }
  326. break;
  327. case "}":
  328. switch (state) {
  329. case "value":
  330. styleRule.style.setProperty(name, buffer.trim(), priority);
  331. priority = "";
  332. /* falls through */
  333. case "before-name":
  334. case "name":
  335. styleRule.__ends = i + 1;
  336. if (parentRule) {
  337. styleRule.parentRule = parentRule;
  338. }
  339. styleRule.parentStyleSheet = styleSheet;
  340. currentScope.cssRules.push(styleRule);
  341. buffer = "";
  342. if (currentScope.constructor === CSSOM.CSSKeyframesRule) {
  343. state = "keyframeRule-begin";
  344. } else {
  345. state = "before-selector";
  346. }
  347. break;
  348. case "keyframeRule-begin":
  349. case "before-selector":
  350. case "selector":
  351. // End of media/supports/document rule.
  352. if (!parentRule) {
  353. parseError("Unexpected }");
  354. }
  355. // Handle rules nested in @media or @supports
  356. hasAncestors = ancestorRules.length > 0;
  357. while (ancestorRules.length > 0) {
  358. parentRule = ancestorRules.pop();
  359. if (
  360. parentRule.constructor.name === "CSSMediaRule"
  361. || parentRule.constructor.name === "CSSSupportsRule"
  362. ) {
  363. prevScope = currentScope;
  364. currentScope = parentRule;
  365. currentScope.cssRules.push(prevScope);
  366. break;
  367. }
  368. if (ancestorRules.length === 0) {
  369. hasAncestors = false;
  370. }
  371. }
  372. if (!hasAncestors) {
  373. currentScope.__ends = i + 1;
  374. styleSheet.cssRules.push(currentScope);
  375. currentScope = styleSheet;
  376. parentRule = null;
  377. }
  378. buffer = "";
  379. state = "before-selector";
  380. break;
  381. }
  382. break;
  383. default:
  384. switch (state) {
  385. case "before-selector":
  386. state = "selector";
  387. styleRule = new CSSOM.CSSStyleRule();
  388. styleRule.__starts = i;
  389. break;
  390. case "before-name":
  391. state = "name";
  392. break;
  393. case "before-value":
  394. state = "value";
  395. break;
  396. case "importRule-begin":
  397. state = "importRule";
  398. break;
  399. }
  400. buffer += character;
  401. break;
  402. }
  403. }
  404. return styleSheet;
  405. };
  406. //.CommonJS
  407. exports.parse = CSSOM.parse;
  408. // The following modules cannot be included sooner due to the mutual dependency with parse.js
  409. CSSOM.CSSStyleSheet = require("./CSSStyleSheet").CSSStyleSheet;
  410. CSSOM.CSSStyleRule = require("./CSSStyleRule").CSSStyleRule;
  411. CSSOM.CSSImportRule = require("./CSSImportRule").CSSImportRule;
  412. CSSOM.CSSMediaRule = require("./CSSMediaRule").CSSMediaRule;
  413. CSSOM.CSSSupportsRule = require("./CSSSupportsRule").CSSSupportsRule;
  414. CSSOM.CSSFontFaceRule = require("./CSSFontFaceRule").CSSFontFaceRule;
  415. CSSOM.CSSHostRule = require("./CSSHostRule").CSSHostRule;
  416. CSSOM.CSSStyleDeclaration = require('./CSSStyleDeclaration').CSSStyleDeclaration;
  417. CSSOM.CSSKeyframeRule = require('./CSSKeyframeRule').CSSKeyframeRule;
  418. CSSOM.CSSKeyframesRule = require('./CSSKeyframesRule').CSSKeyframesRule;
  419. CSSOM.CSSValueExpression = require('./CSSValueExpression').CSSValueExpression;
  420. CSSOM.CSSDocumentRule = require('./CSSDocumentRule').CSSDocumentRule;
  421. ///CommonJS