index.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. module.exports = function(css){
  2. /**
  3. * Parse stylesheet.
  4. */
  5. function stylesheet() {
  6. return { stylesheet: { rules: rules() }};
  7. }
  8. /**
  9. * Opening brace.
  10. */
  11. function open() {
  12. return match(/^{\s*/);
  13. }
  14. /**
  15. * Closing brace.
  16. */
  17. function close() {
  18. return match(/^}\s*/);
  19. }
  20. /**
  21. * Parse ruleset.
  22. */
  23. function rules() {
  24. var node;
  25. var rules = [];
  26. whitespace();
  27. comments();
  28. while (css[0] != '}' && (node = atrule() || rule())) {
  29. comments();
  30. rules.push(node);
  31. }
  32. return rules;
  33. }
  34. /**
  35. * Match `re` and return captures.
  36. */
  37. function match(re) {
  38. var m = re.exec(css);
  39. if (!m) return;
  40. css = css.slice(m[0].length);
  41. return m;
  42. }
  43. /**
  44. * Parse whitespace.
  45. */
  46. function whitespace() {
  47. match(/^\s*/);
  48. }
  49. /**
  50. * Parse comments;
  51. */
  52. function comments() {
  53. while (comment()) ;
  54. }
  55. /**
  56. * Parse comment.
  57. */
  58. function comment() {
  59. if ('/' == css[0] && '*' == css[1]) {
  60. var i = 2;
  61. while ('*' != css[i] || '/' != css[i + 1]) ++i;
  62. i += 2;
  63. css = css.slice(i);
  64. whitespace();
  65. return true;
  66. }
  67. }
  68. /**
  69. * Parse selector.
  70. */
  71. function selector() {
  72. var m = match(/^([^{]+)/);
  73. if (!m) return;
  74. return m[0].trim().split(/\s*,\s*/);
  75. }
  76. /**
  77. * Parse declaration.
  78. */
  79. function declaration() {
  80. // prop
  81. var prop = match(/^(\*?[-\w]+)\s*/);
  82. if (!prop) return;
  83. prop = prop[0];
  84. // :
  85. if (!match(/^:\s*/)) return;
  86. // val
  87. var val = match(/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)\s*/);
  88. if (!val) return;
  89. val = val[0].trim();
  90. // ;
  91. match(/^[;\s]*/);
  92. return { property: prop, value: val };
  93. }
  94. /**
  95. * Parse keyframe.
  96. */
  97. function keyframe() {
  98. var m;
  99. var vals = [];
  100. while (m = match(/^(from|to|\d+%|\.\d+%|\d+\.\d+%)\s*/)) {
  101. vals.push(m[1]);
  102. match(/^,\s*/);
  103. }
  104. if (!vals.length) return;
  105. return {
  106. values: vals,
  107. declarations: declarations()
  108. };
  109. }
  110. /**
  111. * Parse keyframes.
  112. */
  113. function keyframes() {
  114. var m = match(/^@([-\w]+)?keyframes */);
  115. if (!m) return;
  116. var vendor = m[1];
  117. // identifier
  118. var m = match(/^([-\w]+)\s*/);
  119. if (!m) return;
  120. var name = m[1];
  121. if (!open()) return;
  122. comments();
  123. var frame;
  124. var frames = [];
  125. while (frame = keyframe()) {
  126. frames.push(frame);
  127. comments();
  128. }
  129. if (!close()) return;
  130. return {
  131. name: name,
  132. vendor: vendor,
  133. keyframes: frames
  134. };
  135. }
  136. /**
  137. * Parse media.
  138. */
  139. function media() {
  140. var m = match(/^@media *([^{]+)/);
  141. if (!m) return;
  142. var media = m[1].trim();
  143. if (!open()) return;
  144. comments();
  145. var style = rules();
  146. if (!close()) return;
  147. return { media: media, rules: style };
  148. }
  149. /**
  150. * Parse import
  151. */
  152. function atimport() {
  153. return _atrule('import')
  154. }
  155. /**
  156. * Parse charset
  157. */
  158. function atcharset() {
  159. return _atrule('charset');
  160. }
  161. /**
  162. * Parse non-block at-rules
  163. */
  164. function _atrule(name) {
  165. var m = match(new RegExp('^@' + name + ' *([^;\\n]+);\\s*'));
  166. if (!m) return;
  167. var ret = {}
  168. ret[name] = m[1].trim();
  169. return ret;
  170. }
  171. /**
  172. * Parse declarations.
  173. */
  174. function declarations() {
  175. var decls = [];
  176. if (!open()) return;
  177. comments();
  178. // declarations
  179. var decl;
  180. while (decl = declaration()) {
  181. decls.push(decl);
  182. comments();
  183. }
  184. if (!close()) return;
  185. return decls;
  186. }
  187. /**
  188. * Parse at rule.
  189. */
  190. function atrule() {
  191. return keyframes()
  192. || media()
  193. || atimport()
  194. || atcharset();
  195. }
  196. /**
  197. * Parse rule.
  198. */
  199. function rule() {
  200. var sel = selector();
  201. if (!sel) return;
  202. comments();
  203. return { selectors: sel, declarations: declarations() };
  204. }
  205. return stylesheet();
  206. };