parse.js 14 KB

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