index.mjs 5.0 KB

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