parse.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  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. fontFaceRule.parentRule = parentRule;
  218. }
  219. fontFaceRule.parentStyleSheet = styleSheet;
  220. styleRule = fontFaceRule;
  221. buffer = "";
  222. state = "before-name";
  223. } else if (state === "keyframesRule-begin") {
  224. keyframesRule.name = buffer.trim();
  225. if (parentRule) {
  226. ancestorRules.push(parentRule);
  227. keyframesRule.parentRule = parentRule;
  228. }
  229. keyframesRule.parentStyleSheet = styleSheet;
  230. currentScope = parentRule = keyframesRule;
  231. buffer = "";
  232. state = "keyframeRule-begin";
  233. } else if (state === "keyframeRule-begin") {
  234. styleRule = new CSSOM.CSSKeyframeRule();
  235. styleRule.keyText = buffer.trim();
  236. styleRule.__starts = i;
  237. buffer = "";
  238. state = "before-name";
  239. } else if (state === "documentRule-begin") {
  240. // FIXME: what if this '{' is in the url text of the match function?
  241. documentRule.matcher.matcherText = buffer.trim();
  242. if (parentRule) {
  243. ancestorRules.push(parentRule);
  244. documentRule.parentRule = parentRule;
  245. }
  246. currentScope = parentRule = documentRule;
  247. documentRule.parentStyleSheet = styleSheet;
  248. buffer = "";
  249. state = "before-selector";
  250. }
  251. break;
  252. case ":":
  253. if (state === "name") {
  254. name = buffer.trim();
  255. buffer = "";
  256. state = "before-value";
  257. } else {
  258. buffer += character;
  259. }
  260. break;
  261. case "(":
  262. if (state === 'value') {
  263. // ie css expression mode
  264. if (buffer.trim() === 'expression') {
  265. var info = (new CSSOM.CSSValueExpression(token, i)).parse();
  266. if (info.error) {
  267. parseError(info.error);
  268. } else {
  269. buffer += info.expression;
  270. i = info.idx;
  271. }
  272. } else {
  273. state = 'value-parenthesis';
  274. //always ensure this is reset to 1 on transition
  275. //from value to value-parenthesis
  276. valueParenthesisDepth = 1;
  277. buffer += character;
  278. }
  279. } else if (state === 'value-parenthesis') {
  280. valueParenthesisDepth++;
  281. buffer += character;
  282. } else {
  283. buffer += character;
  284. }
  285. break;
  286. case ")":
  287. if (state === 'value-parenthesis') {
  288. valueParenthesisDepth--;
  289. if (valueParenthesisDepth === 0) state = 'value';
  290. }
  291. buffer += character;
  292. break;
  293. case "!":
  294. if (state === "value" && token.indexOf("!important", i) === i) {
  295. priority = "important";
  296. i += "important".length;
  297. } else {
  298. buffer += character;
  299. }
  300. break;
  301. case ";":
  302. switch (state) {
  303. case "value":
  304. styleRule.style.setProperty(name, buffer.trim(), priority);
  305. priority = "";
  306. buffer = "";
  307. state = "before-name";
  308. break;
  309. case "atRule":
  310. buffer = "";
  311. state = "before-selector";
  312. break;
  313. case "importRule":
  314. importRule = new CSSOM.CSSImportRule();
  315. importRule.parentStyleSheet = importRule.styleSheet.parentStyleSheet = styleSheet;
  316. importRule.cssText = buffer + character;
  317. styleSheet.cssRules.push(importRule);
  318. buffer = "";
  319. state = "before-selector";
  320. break;
  321. default:
  322. buffer += character;
  323. break;
  324. }
  325. break;
  326. case "}":
  327. switch (state) {
  328. case "value":
  329. styleRule.style.setProperty(name, buffer.trim(), priority);
  330. priority = "";
  331. /* falls through */
  332. case "before-name":
  333. case "name":
  334. styleRule.__ends = i + 1;
  335. if (parentRule) {
  336. styleRule.parentRule = parentRule;
  337. }
  338. styleRule.parentStyleSheet = styleSheet;
  339. currentScope.cssRules.push(styleRule);
  340. buffer = "";
  341. if (currentScope.constructor === CSSOM.CSSKeyframesRule) {
  342. state = "keyframeRule-begin";
  343. } else {
  344. state = "before-selector";
  345. }
  346. break;
  347. case "keyframeRule-begin":
  348. case "before-selector":
  349. case "selector":
  350. // End of media/supports/document rule.
  351. if (!parentRule) {
  352. parseError("Unexpected }");
  353. }
  354. // Handle rules nested in @media or @supports
  355. hasAncestors = ancestorRules.length > 0;
  356. while (ancestorRules.length > 0) {
  357. parentRule = ancestorRules.pop();
  358. if (
  359. parentRule.constructor.name === "CSSMediaRule"
  360. || parentRule.constructor.name === "CSSSupportsRule"
  361. ) {
  362. prevScope = currentScope;
  363. currentScope = parentRule;
  364. currentScope.cssRules.push(prevScope);
  365. break;
  366. }
  367. if (ancestorRules.length === 0) {
  368. hasAncestors = false;
  369. }
  370. }
  371. if (!hasAncestors) {
  372. currentScope.__ends = i + 1;
  373. styleSheet.cssRules.push(currentScope);
  374. currentScope = styleSheet;
  375. parentRule = null;
  376. }
  377. buffer = "";
  378. state = "before-selector";
  379. break;
  380. }
  381. break;
  382. default:
  383. switch (state) {
  384. case "before-selector":
  385. state = "selector";
  386. styleRule = new CSSOM.CSSStyleRule();
  387. styleRule.__starts = i;
  388. break;
  389. case "before-name":
  390. state = "name";
  391. break;
  392. case "before-value":
  393. state = "value";
  394. break;
  395. case "importRule-begin":
  396. state = "importRule";
  397. break;
  398. }
  399. buffer += character;
  400. break;
  401. }
  402. }
  403. return styleSheet;
  404. };
  405. //.CommonJS
  406. exports.parse = CSSOM.parse;
  407. // The following modules cannot be included sooner due to the mutual dependency with parse.js
  408. CSSOM.CSSStyleSheet = require("./CSSStyleSheet").CSSStyleSheet;
  409. CSSOM.CSSStyleRule = require("./CSSStyleRule").CSSStyleRule;
  410. CSSOM.CSSImportRule = require("./CSSImportRule").CSSImportRule;
  411. CSSOM.CSSMediaRule = require("./CSSMediaRule").CSSMediaRule;
  412. CSSOM.CSSSupportsRule = require("./CSSSupportsRule").CSSSupportsRule;
  413. CSSOM.CSSFontFaceRule = require("./CSSFontFaceRule").CSSFontFaceRule;
  414. CSSOM.CSSHostRule = require("./CSSHostRule").CSSHostRule;
  415. CSSOM.CSSStyleDeclaration = require('./CSSStyleDeclaration').CSSStyleDeclaration;
  416. CSSOM.CSSKeyframeRule = require('./CSSKeyframeRule').CSSKeyframeRule;
  417. CSSOM.CSSKeyframesRule = require('./CSSKeyframesRule').CSSKeyframesRule;
  418. CSSOM.CSSValueExpression = require('./CSSValueExpression').CSSValueExpression;
  419. CSSOM.CSSDocumentRule = require('./CSSDocumentRule').CSSDocumentRule;
  420. ///CommonJS