Parser.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. //.CommonJS
  2. var CSSOM = {
  3. CSSStyleSheet: require("./CSSStyleSheet").CSSStyleSheet,
  4. CSSStyleRule: require("./CSSStyleRule").CSSStyleRule,
  5. CSSImportRule: require("./CSSImportRule").CSSImportRule,
  6. CSSMediaRule: require("./CSSMediaRule").CSSMediaRule
  7. };
  8. ///CommonJS
  9. CSSOM.Parser = function Parser() {};
  10. /**
  11. * @param {string} cssText
  12. * @param {Object} options
  13. */
  14. CSSOM.Parser.prototype.parseStyleSheet = function(cssText, options) {
  15. options = options || {};
  16. var i = options.startIndex || 0;
  17. for (var character; character = token.charAt(i); i++) {
  18. switch (character) {
  19. case " ":
  20. case "\t":
  21. case "\r":
  22. case "\n":
  23. case "\f":
  24. if (SIGNIFICANT_WHITESPACE[state]) {
  25. buffer += character;
  26. }
  27. break;
  28. }
  29. };
  30. CSSOM.Parser.prototype.parse = function(token, options) {
  31. options = options || {};
  32. var i = options.startIndex || 0;
  33. this.styleSheetStart(i);
  34. /**
  35. "before-selector" or
  36. "selector" or
  37. "atRule" or
  38. "atBlock" or
  39. "before-name" or
  40. "name" or
  41. "before-value" or
  42. "value"
  43. */
  44. var state = options.state || "before-selector";
  45. var index;
  46. var j = i;
  47. var buffer = "";
  48. var SIGNIFICANT_WHITESPACE = {
  49. "selector": true,
  50. "value": true,
  51. "atRule": true,
  52. "importRule-begin": true,
  53. "importRule": true,
  54. "atBlock": true
  55. };
  56. var styleSheet = new CSSOM.CSSStyleSheet;
  57. // @type CSSStyleSheet|CSSMediaRule
  58. var currentScope = styleSheet;
  59. var selector, name, value, priority="", styleRule, mediaRule, importRule;
  60. var declarationStarts;
  61. var declarationEnds;
  62. for (var character; character = token.charAt(i); i++) {
  63. switch (character) {
  64. case " ":
  65. case "\t":
  66. case "\r":
  67. case "\n":
  68. case "\f":
  69. if (SIGNIFICANT_WHITESPACE[state]) {
  70. buffer += character;
  71. }
  72. break;
  73. // String
  74. case '"':
  75. j = i + 1;
  76. index = token.indexOf('"', j) + 1;
  77. if (!index) {
  78. throw '" is missing';
  79. }
  80. buffer += token.slice(i, index);
  81. i = index - 1;
  82. if (state == 'before-value') {
  83. state = 'value';
  84. }
  85. break;
  86. case "'":
  87. j = i + 1;
  88. index = token.indexOf("'", j) + 1;
  89. if (!index) {
  90. throw "' is missing";
  91. }
  92. buffer += token.slice(i, index);
  93. i = index - 1;
  94. switch (state) {
  95. case 'before-value':
  96. state = 'value';
  97. break;
  98. case 'importRule-begin':
  99. state = 'importRule';
  100. break;
  101. }
  102. break;
  103. // Comment
  104. case "/":
  105. if (token.charAt(i + 1) == "*") {
  106. i += 2;
  107. index = token.indexOf("*/", i);
  108. if (index == -1) {
  109. throw SyntaxError("Missing */");
  110. } else {
  111. i = index + 1;
  112. }
  113. } else {
  114. buffer += character;
  115. }
  116. if (state == "importRule-begin") {
  117. buffer += " ";
  118. state = "importRule";
  119. }
  120. break;
  121. // At-rule
  122. case "@":
  123. if (token.indexOf("@media", i) == i) {
  124. state = "atBlock";
  125. mediaRule = new CSSOM.CSSMediaRule;
  126. mediaRule.__starts = i;
  127. i += "media".length;
  128. buffer = "";
  129. break;
  130. } else if (token.indexOf("@import", i) == i) {
  131. state = "importRule-begin";
  132. i += "import".length;
  133. buffer += "@import";
  134. break;
  135. } else if (state == "selector") {
  136. state = "atRule";
  137. }
  138. buffer += character;
  139. break;
  140. case "{":
  141. if (state == "selector" || state == "atRule") {
  142. this.selectorEnd(i, buffer);
  143. buffer = "";
  144. state = "before-name";
  145. } else if (state == "atBlock") {
  146. mediaRule.media.mediaText = buffer.trim();
  147. currentScope = mediaRule;
  148. buffer = "";
  149. state = "before-selector";
  150. }
  151. break;
  152. case ":":
  153. if (state == "name") {
  154. name = buffer;
  155. buffer = "";
  156. state = "before-value";
  157. } else {
  158. buffer += character;
  159. }
  160. break;
  161. case '(':
  162. if (state == 'value') {
  163. index = token.indexOf(')', i + 1);
  164. if (index == -1) {
  165. throw i + ': unclosed "("';
  166. }
  167. buffer += token.slice(i, index + 1);
  168. i = index;
  169. } else {
  170. buffer += character;
  171. }
  172. break;
  173. case "!":
  174. if (state == "value" && token.indexOf("!important", i) === i) {
  175. priority = "important";
  176. i += "important".length;
  177. } else {
  178. buffer += character;
  179. }
  180. break;
  181. case ";":
  182. switch (state) {
  183. case "value":
  184. this.declarationEnd(i, name, buffer, priority);
  185. priority = "";
  186. buffer = "";
  187. state = "before-name";
  188. break;
  189. case "atRule":
  190. buffer = "";
  191. state = "before-selector";
  192. break;
  193. case "importRule":
  194. importRule = new CSSOM.CSSImportRule;
  195. importRule.cssText = buffer + character;
  196. currentScope.cssRules.push(importRule);
  197. buffer = "";
  198. state = "before-selector";
  199. break;
  200. default:
  201. buffer += character;
  202. break;
  203. }
  204. break;
  205. case "}":
  206. switch (state) {
  207. case "value":
  208. this.declarationEnd(i, name, buffer, priority);
  209. // fall down
  210. case "before-name":
  211. this.styleRuleEnd(i);
  212. buffer = "";
  213. break;
  214. case "name":
  215. throw i + ": Oops";
  216. break;
  217. case "before-selector":
  218. case "selector":
  219. // End of media rule.
  220. // Nesting rules aren't supported yet
  221. if (!mediaRule) {
  222. throw "unexpected }";
  223. }
  224. mediaRule.__ends = i + 1;
  225. styleSheet.cssRules.push(mediaRule);
  226. currentScope = styleSheet;
  227. buffer = "";
  228. break;
  229. }
  230. state = "before-selector";
  231. break;
  232. default:
  233. switch (state) {
  234. case "before-selector":
  235. this.styleRuleStart(i);
  236. state = "selector";
  237. break;
  238. case "before-name":
  239. state = "name";
  240. break;
  241. case "before-value":
  242. state = "value";
  243. break;
  244. case "importRule-begin":
  245. state = "importRule";
  246. break;
  247. }
  248. buffer += character;
  249. break;
  250. }
  251. }
  252. return styleSheet;
  253. };
  254. CSSOM.Parser.prototype.compile = function() {
  255. var handlers = {
  256. styleSheetStart: this.styleSheetStart,
  257. styleRuleStart: this.styleRuleStart,
  258. selectorEnd: this.selectorEnd,
  259. declarationEnd: this.declarationEnd,
  260. styleRuleEnd: this.styleRuleEnd,
  261. styleSheetEnd: this.styleSheetEnd
  262. };
  263. var parser = this.parse.toString();
  264. for (var key in handlers) {
  265. if (!handlers.hasOwnProperty(key)) {
  266. continue;
  267. }
  268. parser = parser.replace(new RegExp('^.*' + key + '.*$', 'gm'), handlers[key].toString()
  269. .replace(/^function.+$/m, '')
  270. .replace(/^}/m, ''))
  271. .replace(/this\.?/g, '');
  272. }
  273. return parser;
  274. };
  275. CSSOM.Parser.prototype.styleSheetStart = function(i) {
  276. console.log('styleSheetStart', i);
  277. this.styleSheet = new CSSOM.CSSStyleSheet;
  278. this.scopeRules = this.styleSheet.cssRules;
  279. };
  280. CSSOM.Parser.prototype.styleRuleStart = function(i) {
  281. console.log('styleRuleStart', i);
  282. this.styleRule = new CSSOM.CSSStyleRule;
  283. this.styleRule._start = i;
  284. };
  285. CSSOM.Parser.prototype.selectorEnd = function(i, buffer) {
  286. this.styleRule.selectorText = buffer.trimRight();
  287. this.styleRule.style._start = i;
  288. };
  289. CSSOM.Parser.prototype.declarationEnd = function(name, value, priority, startIndex, endIndex) {
  290. console.log('declarationEnd', name, value, priority, startIndex, endIndex);
  291. };
  292. CSSOM.Parser.prototype.styleRuleEnd = function(i) {
  293. this.styleRule._end = i;
  294. this.scopeRules.push(this.styleRule);
  295. };
  296. CSSOM.Parser.prototype.styleSheetEnd = function(i) {
  297. return this.styleSheet;
  298. };
  299. /*
  300. Parser.prototype.nameStart = function(i) {
  301. this.nameStartIndex = i;
  302. };
  303. Parser.prototype.nameEnd = function(i, buffer) {
  304. this.name = buffer.trimRight();
  305. this.nameEndIndex = this.nameStartIndex + this.name.length;
  306. };
  307. Parser.prototype.valueStart = function(i) {
  308. this.valueStartIndex = i;
  309. };
  310. Parser.prototype.valueEnd = function(i, buffer) {
  311. var value = buffer.trimRight();
  312. this.styleRule.style.add(this.name, value, this.nameStartIndex, this.nameEndIndex, this.valueStartIndex, this.valueStartIndex + value.length);
  313. };
  314. */
  315. //.CommonJS
  316. exports.Parser = CSSOM.Parser;
  317. ///CommonJS