index.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. 'use strict';
  2. // http://www.w3.org/TR/CSS21/grammar.html
  3. // https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027
  4. var COMMENT_REGEX = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g;
  5. var NEWLINE_REGEX = /\n/g;
  6. var WHITESPACE_REGEX = /^\s*/;
  7. // declaration
  8. var PROPERTY_REGEX = /^(\*?[-#/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/;
  9. var COLON_REGEX = /^:\s*/;
  10. var VALUE_REGEX = /^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};])+)/;
  11. var SEMICOLON_REGEX = /^[;\s]*/;
  12. // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill
  13. var TRIM_REGEX = /^\s+|\s+$/g;
  14. // strings
  15. var NEWLINE = '\n';
  16. var FORWARD_SLASH = '/';
  17. var ASTERISK = '*';
  18. var EMPTY_STRING = '';
  19. // types
  20. var TYPE_COMMENT = 'comment';
  21. var TYPE_DECLARATION = 'declaration';
  22. /**
  23. * @param {String} style
  24. * @param {Object} [options]
  25. * @return {Object[]}
  26. * @throws {TypeError}
  27. * @throws {Error}
  28. */
  29. function index (style, options) {
  30. if (typeof style !== 'string') {
  31. throw new TypeError('First argument must be a string');
  32. }
  33. if (!style) return [];
  34. options = options || {};
  35. /**
  36. * Positional.
  37. */
  38. var lineno = 1;
  39. var column = 1;
  40. /**
  41. * Update lineno and column based on `str`.
  42. *
  43. * @param {String} str
  44. */
  45. function updatePosition(str) {
  46. var lines = str.match(NEWLINE_REGEX);
  47. if (lines) lineno += lines.length;
  48. var i = str.lastIndexOf(NEWLINE);
  49. column = ~i ? str.length - i : column + str.length;
  50. }
  51. /**
  52. * Mark position and patch `node.position`.
  53. *
  54. * @return {Function}
  55. */
  56. function position() {
  57. var start = { line: lineno, column: column };
  58. return function (node) {
  59. node.position = new Position(start);
  60. whitespace();
  61. return node;
  62. };
  63. }
  64. /**
  65. * Store position information for a node.
  66. *
  67. * @constructor
  68. * @property {Object} start
  69. * @property {Object} end
  70. * @property {undefined|String} source
  71. */
  72. function Position(start) {
  73. this.start = start;
  74. this.end = { line: lineno, column: column };
  75. this.source = options.source;
  76. }
  77. /**
  78. * Non-enumerable source string.
  79. */
  80. Position.prototype.content = style;
  81. /**
  82. * Error `msg`.
  83. *
  84. * @param {String} msg
  85. * @throws {Error}
  86. */
  87. function error(msg) {
  88. var err = new Error(
  89. options.source + ':' + lineno + ':' + column + ': ' + msg
  90. );
  91. err.reason = msg;
  92. err.filename = options.source;
  93. err.line = lineno;
  94. err.column = column;
  95. err.source = style;
  96. if (options.silent) ; else {
  97. throw err;
  98. }
  99. }
  100. /**
  101. * Match `re` and return captures.
  102. *
  103. * @param {RegExp} re
  104. * @return {undefined|Array}
  105. */
  106. function match(re) {
  107. var m = re.exec(style);
  108. if (!m) return;
  109. var str = m[0];
  110. updatePosition(str);
  111. style = style.slice(str.length);
  112. return m;
  113. }
  114. /**
  115. * Parse whitespace.
  116. */
  117. function whitespace() {
  118. match(WHITESPACE_REGEX);
  119. }
  120. /**
  121. * Parse comments.
  122. *
  123. * @param {Object[]} [rules]
  124. * @return {Object[]}
  125. */
  126. function comments(rules) {
  127. var c;
  128. rules = rules || [];
  129. while ((c = comment())) {
  130. if (c !== false) {
  131. rules.push(c);
  132. }
  133. }
  134. return rules;
  135. }
  136. /**
  137. * Parse comment.
  138. *
  139. * @return {Object}
  140. * @throws {Error}
  141. */
  142. function comment() {
  143. var pos = position();
  144. if (FORWARD_SLASH != style.charAt(0) || ASTERISK != style.charAt(1)) return;
  145. var i = 2;
  146. while (
  147. EMPTY_STRING != style.charAt(i) &&
  148. (ASTERISK != style.charAt(i) || FORWARD_SLASH != style.charAt(i + 1))
  149. ) {
  150. ++i;
  151. }
  152. i += 2;
  153. if (EMPTY_STRING === style.charAt(i - 1)) {
  154. return error('End of comment missing');
  155. }
  156. var str = style.slice(2, i - 2);
  157. column += 2;
  158. updatePosition(str);
  159. style = style.slice(i);
  160. column += 2;
  161. return pos({
  162. type: TYPE_COMMENT,
  163. comment: str
  164. });
  165. }
  166. /**
  167. * Parse declaration.
  168. *
  169. * @return {Object}
  170. * @throws {Error}
  171. */
  172. function declaration() {
  173. var pos = position();
  174. // prop
  175. var prop = match(PROPERTY_REGEX);
  176. if (!prop) return;
  177. comment();
  178. // :
  179. if (!match(COLON_REGEX)) return error("property missing ':'");
  180. // val
  181. var val = match(VALUE_REGEX);
  182. var ret = pos({
  183. type: TYPE_DECLARATION,
  184. property: trim(prop[0].replace(COMMENT_REGEX, EMPTY_STRING)),
  185. value: val
  186. ? trim(val[0].replace(COMMENT_REGEX, EMPTY_STRING))
  187. : EMPTY_STRING
  188. });
  189. // ;
  190. match(SEMICOLON_REGEX);
  191. return ret;
  192. }
  193. /**
  194. * Parse declarations.
  195. *
  196. * @return {Object[]}
  197. */
  198. function declarations() {
  199. var decls = [];
  200. comments(decls);
  201. // declarations
  202. var decl;
  203. while ((decl = declaration())) {
  204. if (decl !== false) {
  205. decls.push(decl);
  206. comments(decls);
  207. }
  208. }
  209. return decls;
  210. }
  211. whitespace();
  212. return declarations();
  213. }
  214. /**
  215. * Trim `str`.
  216. *
  217. * @param {String} str
  218. * @return {String}
  219. */
  220. function trim(str) {
  221. return str ? str.replace(TRIM_REGEX, EMPTY_STRING) : EMPTY_STRING;
  222. }
  223. module.exports = index;
  224. //# sourceMappingURL=index.js.map