UNPKG

6.82 kBJavaScriptView Raw
1(function (global, factory) {
2 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3 typeof define === 'function' && define.amd ? define(factory) :
4 (global = global || self, global.StyleToObject = factory());
5}(this, (function () { 'use strict';
6
7 // http://www.w3.org/TR/CSS21/grammar.html
8 // https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027
9 var COMMENT_REGEX = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g;
10
11 var NEWLINE_REGEX = /\n/g;
12 var WHITESPACE_REGEX = /^\s*/;
13
14 // declaration
15 var PROPERTY_REGEX = /^(\*?[-#/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/;
16 var COLON_REGEX = /^:\s*/;
17 var VALUE_REGEX = /^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};])+)/;
18 var SEMICOLON_REGEX = /^[;\s]*/;
19
20 // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill
21 var TRIM_REGEX = /^\s+|\s+$/g;
22
23 // strings
24 var NEWLINE = '\n';
25 var FORWARD_SLASH = '/';
26 var ASTERISK = '*';
27 var EMPTY_STRING = '';
28
29 // types
30 var TYPE_COMMENT = 'comment';
31 var TYPE_DECLARATION = 'declaration';
32
33 /**
34 * @param {String} style
35 * @param {Object} [options]
36 * @return {Object[]}
37 * @throws {TypeError}
38 * @throws {Error}
39 */
40 var inlineStyleParser = function(style, options) {
41 if (typeof style !== 'string') {
42 throw new TypeError('First argument must be a string');
43 }
44
45 if (!style) return [];
46
47 options = options || {};
48
49 /**
50 * Positional.
51 */
52 var lineno = 1;
53 var column = 1;
54
55 /**
56 * Update lineno and column based on `str`.
57 *
58 * @param {String} str
59 */
60 function updatePosition(str) {
61 var lines = str.match(NEWLINE_REGEX);
62 if (lines) lineno += lines.length;
63 var i = str.lastIndexOf(NEWLINE);
64 column = ~i ? str.length - i : column + str.length;
65 }
66
67 /**
68 * Mark position and patch `node.position`.
69 *
70 * @return {Function}
71 */
72 function position() {
73 var start = { line: lineno, column: column };
74 return function(node) {
75 node.position = new Position(start);
76 whitespace();
77 return node;
78 };
79 }
80
81 /**
82 * Store position information for a node.
83 *
84 * @constructor
85 * @property {Object} start
86 * @property {Object} end
87 * @property {undefined|String} source
88 */
89 function Position(start) {
90 this.start = start;
91 this.end = { line: lineno, column: column };
92 this.source = options.source;
93 }
94
95 /**
96 * Non-enumerable source string.
97 */
98 Position.prototype.content = style;
99
100 /**
101 * Error `msg`.
102 *
103 * @param {String} msg
104 * @throws {Error}
105 */
106 function error(msg) {
107 var err = new Error(
108 options.source + ':' + lineno + ':' + column + ': ' + msg
109 );
110 err.reason = msg;
111 err.filename = options.source;
112 err.line = lineno;
113 err.column = column;
114 err.source = style;
115
116 if (options.silent) ; else {
117 throw err;
118 }
119 }
120
121 /**
122 * Match `re` and return captures.
123 *
124 * @param {RegExp} re
125 * @return {undefined|Array}
126 */
127 function match(re) {
128 var m = re.exec(style);
129 if (!m) return;
130 var str = m[0];
131 updatePosition(str);
132 style = style.slice(str.length);
133 return m;
134 }
135
136 /**
137 * Parse whitespace.
138 */
139 function whitespace() {
140 match(WHITESPACE_REGEX);
141 }
142
143 /**
144 * Parse comments.
145 *
146 * @param {Object[]} [rules]
147 * @return {Object[]}
148 */
149 function comments(rules) {
150 var c;
151 rules = rules || [];
152 while ((c = comment())) {
153 if (c !== false) {
154 rules.push(c);
155 }
156 }
157 return rules;
158 }
159
160 /**
161 * Parse comment.
162 *
163 * @return {Object}
164 * @throws {Error}
165 */
166 function comment() {
167 var pos = position();
168 if (FORWARD_SLASH != style.charAt(0) || ASTERISK != style.charAt(1)) return;
169
170 var i = 2;
171 while (
172 EMPTY_STRING != style.charAt(i) &&
173 (ASTERISK != style.charAt(i) || FORWARD_SLASH != style.charAt(i + 1))
174 ) {
175 ++i;
176 }
177 i += 2;
178
179 if (EMPTY_STRING === style.charAt(i - 1)) {
180 return error('End of comment missing');
181 }
182
183 var str = style.slice(2, i - 2);
184 column += 2;
185 updatePosition(str);
186 style = style.slice(i);
187 column += 2;
188
189 return pos({
190 type: TYPE_COMMENT,
191 comment: str
192 });
193 }
194
195 /**
196 * Parse declaration.
197 *
198 * @return {Object}
199 * @throws {Error}
200 */
201 function declaration() {
202 var pos = position();
203
204 // prop
205 var prop = match(PROPERTY_REGEX);
206 if (!prop) return;
207 comment();
208
209 // :
210 if (!match(COLON_REGEX)) return error("property missing ':'");
211
212 // val
213 var val = match(VALUE_REGEX);
214
215 var ret = pos({
216 type: TYPE_DECLARATION,
217 property: trim(prop[0].replace(COMMENT_REGEX, EMPTY_STRING)),
218 value: val
219 ? trim(val[0].replace(COMMENT_REGEX, EMPTY_STRING))
220 : EMPTY_STRING
221 });
222
223 // ;
224 match(SEMICOLON_REGEX);
225
226 return ret;
227 }
228
229 /**
230 * Parse declarations.
231 *
232 * @return {Object[]}
233 */
234 function declarations() {
235 var decls = [];
236
237 comments(decls);
238
239 // declarations
240 var decl;
241 while ((decl = declaration())) {
242 if (decl !== false) {
243 decls.push(decl);
244 comments(decls);
245 }
246 }
247
248 return decls;
249 }
250
251 whitespace();
252 return declarations();
253 };
254
255 /**
256 * Trim `str`.
257 *
258 * @param {String} str
259 * @return {String}
260 */
261 function trim(str) {
262 return str ? str.replace(TRIM_REGEX, EMPTY_STRING) : EMPTY_STRING;
263 }
264
265 /**
266 * Parses inline style to object.
267 *
268 * @example
269 * // returns { 'line-height': '42' }
270 * StyleToObject('line-height: 42;');
271 *
272 * @param {String} style - The inline style.
273 * @param {Function} [iterator] - The iterator function.
274 * @return {null|Object}
275 */
276 function StyleToObject(style, iterator) {
277 var output = null;
278 if (!style || typeof style !== 'string') {
279 return output;
280 }
281
282 var declaration;
283 var declarations = inlineStyleParser(style);
284 var hasIterator = typeof iterator === 'function';
285 var property;
286 var value;
287
288 for (var i = 0, len = declarations.length; i < len; i++) {
289 declaration = declarations[i];
290 property = declaration.property;
291 value = declaration.value;
292
293 if (hasIterator) {
294 iterator(property, value, declaration);
295 } else if (value) {
296 output || (output = {});
297 output[property] = value;
298 }
299 }
300
301 return output;
302 }
303
304 var styleToObject = StyleToObject;
305
306 return styleToObject;
307
308})));