style-to-object 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,647 +1,308 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3
- typeof define === 'function' && define.amd ? define(factory) :
4
- (global.StyleToObject = 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
5
  }(this, (function () { 'use strict';
6
6
 
7
- // http://www.w3.org/TR/CSS21/grammar.html
8
- // https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027
9
- var commentre = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g;
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
10
 
11
- var parse = function(css, options){
12
- options = options || {};
11
+ var NEWLINE_REGEX = /\n/g;
12
+ var WHITESPACE_REGEX = /^\s*/;
13
13
 
14
- /**
15
- * Positional.
16
- */
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]*/;
17
19
 
18
- var lineno = 1;
19
- var column = 1;
20
-
21
- /**
22
- * Update lineno and column based on `str`.
23
- */
24
-
25
- function updatePosition(str) {
26
- var lines = str.match(/\n/g);
27
- if (lines) lineno += lines.length;
28
- var i = str.lastIndexOf('\n');
29
- column = ~i ? str.length - i : column + str.length;
30
- }
20
+ // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill
21
+ var TRIM_REGEX = /^\s+|\s+$/g;
31
22
 
32
- /**
33
- * Mark position and patch `node.position`.
34
- */
23
+ // strings
24
+ var NEWLINE = '\n';
25
+ var FORWARD_SLASH = '/';
26
+ var ASTERISK = '*';
27
+ var EMPTY_STRING = '';
35
28
 
36
- function position() {
37
- var start = { line: lineno, column: column };
38
- return function(node){
39
- node.position = new Position(start);
40
- whitespace();
41
- return node;
42
- };
43
- }
29
+ // types
30
+ var TYPE_COMMENT = 'comment';
31
+ var TYPE_DECLARATION = 'declaration';
44
32
 
45
33
  /**
46
- * Store position information for a node
34
+ * @param {String} style
35
+ * @param {Object} [options]
36
+ * @return {Object[]}
37
+ * @throws {TypeError}
38
+ * @throws {Error}
47
39
  */
48
-
49
- function Position(start) {
50
- this.start = start;
51
- this.end = { line: lineno, column: column };
52
- this.source = options.source;
53
- }
54
-
55
- /**
56
- * Non-enumerable source string
57
- */
58
-
59
- Position.prototype.content = css;
60
-
61
- /**
62
- * Error `msg`.
63
- */
64
-
65
- var errorsList = [];
66
-
67
- function error(msg) {
68
- var err = new Error(options.source + ':' + lineno + ':' + column + ': ' + msg);
69
- err.reason = msg;
70
- err.filename = options.source;
71
- err.line = lineno;
72
- err.column = column;
73
- err.source = css;
74
-
75
- if (options.silent) {
76
- errorsList.push(err);
77
- } else {
78
- throw err;
40
+ var inlineStyleParser = function(style, options) {
41
+ if (typeof style !== 'string') {
42
+ throw new TypeError('First argument must be a string');
79
43
  }
80
- }
81
-
82
- /**
83
- * Parse stylesheet.
84
- */
85
-
86
- function stylesheet() {
87
- var rulesList = rules();
88
44
 
89
- return {
90
- type: 'stylesheet',
91
- stylesheet: {
92
- rules: rulesList,
93
- parsingErrors: errorsList
94
- }
95
- };
96
- }
97
-
98
- /**
99
- * Opening brace.
100
- */
101
-
102
- function open() {
103
- return match(/^{\s*/);
104
- }
105
-
106
- /**
107
- * Closing brace.
108
- */
109
-
110
- function close() {
111
- return match(/^}/);
112
- }
113
-
114
- /**
115
- * Parse ruleset.
116
- */
117
-
118
- function rules() {
119
- var node;
120
- var rules = [];
121
- whitespace();
122
- comments(rules);
123
- while (css.length && css.charAt(0) != '}' && (node = atrule() || rule())) {
124
- if (node !== false) {
125
- rules.push(node);
126
- comments(rules);
127
- }
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;
128
65
  }
129
- return rules;
130
- }
131
66
 
132
- /**
133
- * Match `re` and return captures.
134
- */
135
-
136
- function match(re) {
137
- var m = re.exec(css);
138
- if (!m) return;
139
- var str = m[0];
140
- updatePosition(str);
141
- css = css.slice(str.length);
142
- return m;
143
- }
144
-
145
- /**
146
- * Parse whitespace.
147
- */
148
-
149
- function whitespace() {
150
- match(/^\s*/);
151
- }
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
+ }
152
80
 
153
- /**
154
- * Parse comments;
155
- */
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
+ }
156
94
 
157
- function comments(rules) {
158
- var c;
159
- rules = rules || [];
160
- while (c = comment()) {
161
- if (c !== false) {
162
- rules.push(c);
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;
163
118
  }
164
119
  }
165
- return rules;
166
- }
167
120
 
168
- /**
169
- * Parse comment.
170
- */
171
-
172
- function comment() {
173
- var pos = position();
174
- if ('/' != css.charAt(0) || '*' != css.charAt(1)) return;
175
-
176
- var i = 2;
177
- while ("" != css.charAt(i) && ('*' != css.charAt(i) || '/' != css.charAt(i + 1))) ++i;
178
- i += 2;
179
-
180
- if ("" === css.charAt(i-1)) {
181
- return error('End of comment missing');
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;
182
134
  }
183
135
 
184
- var str = css.slice(2, i - 2);
185
- column += 2;
186
- updatePosition(str);
187
- css = css.slice(i);
188
- column += 2;
189
-
190
- return pos({
191
- type: 'comment',
192
- comment: str
193
- });
194
- }
195
-
196
- /**
197
- * Parse selector.
198
- */
199
-
200
- function selector() {
201
- var m = match(/^([^{]+)/);
202
- if (!m) return;
203
- /* @fix Remove all comments from selectors
204
- * http://ostermiller.org/findcomment.html */
205
- return trim(m[0])
206
- .replace(/\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/+/g, '')
207
- .replace(/"(?:\\"|[^"])*"|'(?:\\'|[^'])*'/g, function(m) {
208
- return m.replace(/,/g, '\u200C');
209
- })
210
- .split(/\s*(?![^(]*\)),\s*/)
211
- .map(function(s) {
212
- return s.replace(/\u200C/g, ',');
213
- });
214
- }
215
-
216
- /**
217
- * Parse declaration.
218
- */
219
-
220
- function declaration() {
221
- var pos = position();
222
-
223
- // prop
224
- var prop = match(/^(\*?[-#\/\*\\\w]+(\[[0-9a-z_-]+\])?)\s*/);
225
- if (!prop) return;
226
- prop = trim(prop[0]);
227
-
228
- // :
229
- if (!match(/^:\s*/)) return error("property missing ':'");
230
-
231
- // val
232
- var val = match(/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)/);
233
-
234
- var ret = pos({
235
- type: 'declaration',
236
- property: prop.replace(commentre, ''),
237
- value: val ? trim(val[0]).replace(commentre, '') : ''
238
- });
239
-
240
- // ;
241
- match(/^[;\s]*/);
242
-
243
- return ret;
244
- }
245
-
246
- /**
247
- * Parse declarations.
248
- */
249
-
250
- function declarations() {
251
- var decls = [];
252
-
253
- if (!open()) return error("missing '{'");
254
- comments(decls);
136
+ /**
137
+ * Parse whitespace.
138
+ */
139
+ function whitespace() {
140
+ match(WHITESPACE_REGEX);
141
+ }
255
142
 
256
- // declarations
257
- var decl;
258
- while (decl = declaration()) {
259
- if (decl !== false) {
260
- decls.push(decl);
261
- comments(decls);
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
+ }
262
156
  }
157
+ return rules;
263
158
  }
264
159
 
265
- if (!close()) return error("missing '}'");
266
- return decls;
267
- }
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;
268
178
 
269
- /**
270
- * Parse keyframe.
271
- */
179
+ if (EMPTY_STRING === style.charAt(i - 1)) {
180
+ return error('End of comment missing');
181
+ }
272
182
 
273
- function keyframe() {
274
- var m;
275
- var vals = [];
276
- var pos = position();
183
+ var str = style.slice(2, i - 2);
184
+ column += 2;
185
+ updatePosition(str);
186
+ style = style.slice(i);
187
+ column += 2;
277
188
 
278
- while (m = match(/^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/)) {
279
- vals.push(m[1]);
280
- match(/^,\s*/);
189
+ return pos({
190
+ type: TYPE_COMMENT,
191
+ comment: str
192
+ });
281
193
  }
282
194
 
283
- if (!vals.length) return;
284
-
285
- return pos({
286
- type: 'keyframe',
287
- values: vals,
288
- declarations: declarations()
289
- });
290
- }
195
+ /**
196
+ * Parse declaration.
197
+ *
198
+ * @return {Object}
199
+ * @throws {Error}
200
+ */
201
+ function declaration() {
202
+ var pos = position();
291
203
 
292
- /**
293
- * Parse keyframes.
294
- */
204
+ // prop
205
+ var prop = match(PROPERTY_REGEX);
206
+ if (!prop) return;
207
+ comment();
295
208
 
296
- function atkeyframes() {
297
- var pos = position();
298
- var m = match(/^@([-\w]+)?keyframes\s*/);
209
+ // :
210
+ if (!match(COLON_REGEX)) return error("property missing ':'");
299
211
 
300
- if (!m) return;
301
- var vendor = m[1];
212
+ // val
213
+ var val = match(VALUE_REGEX);
302
214
 
303
- // identifier
304
- var m = match(/^([-\w]+)\s*/);
305
- if (!m) return error("@keyframes missing name");
306
- var name = m[1];
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
+ });
307
222
 
308
- if (!open()) return error("@keyframes missing '{'");
223
+ // ;
224
+ match(SEMICOLON_REGEX);
309
225
 
310
- var frame;
311
- var frames = comments();
312
- while (frame = keyframe()) {
313
- frames.push(frame);
314
- frames = frames.concat(comments());
226
+ return ret;
315
227
  }
316
228
 
317
- if (!close()) return error("@keyframes missing '}'");
318
-
319
- return pos({
320
- type: 'keyframes',
321
- name: name,
322
- vendor: vendor,
323
- keyframes: frames
324
- });
325
- }
326
-
327
- /**
328
- * Parse supports.
329
- */
330
-
331
- function atsupports() {
332
- var pos = position();
333
- var m = match(/^@supports *([^{]+)/);
334
-
335
- if (!m) return;
336
- var supports = trim(m[1]);
337
-
338
- if (!open()) return error("@supports missing '{'");
339
-
340
- var style = comments().concat(rules());
341
-
342
- if (!close()) return error("@supports missing '}'");
343
-
344
- return pos({
345
- type: 'supports',
346
- supports: supports,
347
- rules: style
348
- });
349
- }
350
-
351
- /**
352
- * Parse host.
353
- */
354
-
355
- function athost() {
356
- var pos = position();
357
- var m = match(/^@host\s*/);
358
-
359
- if (!m) return;
360
-
361
- if (!open()) return error("@host missing '{'");
362
-
363
- var style = comments().concat(rules());
364
-
365
- if (!close()) return error("@host missing '}'");
366
-
367
- return pos({
368
- type: 'host',
369
- rules: style
370
- });
371
- }
372
-
373
- /**
374
- * Parse media.
375
- */
376
-
377
- function atmedia() {
378
- var pos = position();
379
- var m = match(/^@media *([^{]+)/);
380
-
381
- if (!m) return;
382
- var media = trim(m[1]);
383
-
384
- if (!open()) return error("@media missing '{'");
385
-
386
- var style = comments().concat(rules());
387
-
388
- if (!close()) return error("@media missing '}'");
389
-
390
- return pos({
391
- type: 'media',
392
- media: media,
393
- rules: style
394
- });
395
- }
396
-
397
-
398
- /**
399
- * Parse custom-media.
400
- */
401
-
402
- function atcustommedia() {
403
- var pos = position();
404
- var m = match(/^@custom-media\s+(--[^\s]+)\s*([^{;]+);/);
405
- if (!m) return;
406
-
407
- return pos({
408
- type: 'custom-media',
409
- name: trim(m[1]),
410
- media: trim(m[2])
411
- });
412
- }
413
-
414
- /**
415
- * Parse paged media.
416
- */
417
-
418
- function atpage() {
419
- var pos = position();
420
- var m = match(/^@page */);
421
- if (!m) return;
422
-
423
- var sel = selector() || [];
424
-
425
- if (!open()) return error("@page missing '{'");
426
- var decls = comments();
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
+ }
427
247
 
428
- // declarations
429
- var decl;
430
- while (decl = declaration()) {
431
- decls.push(decl);
432
- decls = decls.concat(comments());
248
+ return decls;
433
249
  }
434
250
 
435
- if (!close()) return error("@page missing '}'");
436
-
437
- return pos({
438
- type: 'page',
439
- selectors: sel,
440
- declarations: decls
441
- });
442
- }
443
-
444
- /**
445
- * Parse document.
446
- */
447
-
448
- function atdocument() {
449
- var pos = position();
450
- var m = match(/^@([-\w]+)?document *([^{]+)/);
451
- if (!m) return;
452
-
453
- var vendor = trim(m[1]);
454
- var doc = trim(m[2]);
455
-
456
- if (!open()) return error("@document missing '{'");
457
-
458
- var style = comments().concat(rules());
459
-
460
- if (!close()) return error("@document missing '}'");
461
-
462
- return pos({
463
- type: 'document',
464
- document: doc,
465
- vendor: vendor,
466
- rules: style
467
- });
468
- }
251
+ whitespace();
252
+ return declarations();
253
+ };
469
254
 
470
255
  /**
471
- * Parse font-face.
256
+ * Trim `str`.
257
+ *
258
+ * @param {String} str
259
+ * @return {String}
472
260
  */
473
-
474
- function atfontface() {
475
- var pos = position();
476
- var m = match(/^@font-face\s*/);
477
- if (!m) return;
478
-
479
- if (!open()) return error("@font-face missing '{'");
480
- var decls = comments();
481
-
482
- // declarations
483
- var decl;
484
- while (decl = declaration()) {
485
- decls.push(decl);
486
- decls = decls.concat(comments());
487
- }
488
-
489
- if (!close()) return error("@font-face missing '}'");
490
-
491
- return pos({
492
- type: 'font-face',
493
- declarations: decls
494
- });
261
+ function trim(str) {
262
+ return str ? str.replace(TRIM_REGEX, EMPTY_STRING) : EMPTY_STRING;
495
263
  }
496
264
 
497
265
  /**
498
- * Parse import
499
- */
500
-
501
- var atimport = _compileAtrule('import');
502
-
503
- /**
504
- * Parse charset
505
- */
506
-
507
- var atcharset = _compileAtrule('charset');
508
-
509
- /**
510
- * Parse namespace
511
- */
512
-
513
- var atnamespace = _compileAtrule('namespace');
514
-
515
- /**
516
- * Parse non-block at-rules
517
- */
518
-
519
-
520
- function _compileAtrule(name) {
521
- var re = new RegExp('^@' + name + '\\s*([^;]+);');
522
- return function() {
523
- var pos = position();
524
- var m = match(re);
525
- if (!m) return;
526
- var ret = { type: name };
527
- ret[name] = m[1].trim();
528
- return pos(ret);
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;
529
280
  }
530
- }
531
-
532
- /**
533
- * Parse at rule.
534
- */
535
-
536
- function atrule() {
537
- if (css[0] != '@') return;
538
-
539
- return atkeyframes()
540
- || atmedia()
541
- || atcustommedia()
542
- || atsupports()
543
- || atimport()
544
- || atcharset()
545
- || atnamespace()
546
- || atdocument()
547
- || atpage()
548
- || athost()
549
- || atfontface();
550
- }
551
-
552
- /**
553
- * Parse rule.
554
- */
555
-
556
- function rule() {
557
- var pos = position();
558
- var sel = selector();
559
281
 
560
- if (!sel) return error('selector missing');
561
- comments();
562
-
563
- return pos({
564
- type: 'rule',
565
- selectors: sel,
566
- declarations: declarations()
567
- });
568
- }
569
-
570
- return addParent(stylesheet());
571
- };
572
-
573
- /**
574
- * Trim `str`.
575
- */
576
-
577
- function trim(str) {
578
- return str ? str.replace(/^\s+|\s+$/g, '') : '';
579
- }
580
-
581
- /**
582
- * Adds non-enumerable parent node reference to each node.
583
- */
584
-
585
- function addParent(obj, parent) {
586
- var isNode = obj && typeof obj.type === 'string';
587
- var childParent = isNode ? obj : parent;
588
-
589
- for (var k in obj) {
590
- var value = obj[k];
591
- if (Array.isArray(value)) {
592
- value.forEach(function(v) { addParent(v, childParent); });
593
- } else if (value && typeof value === 'object') {
594
- addParent(value, childParent);
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
+ }
595
299
  }
596
- }
597
300
 
598
- if (isNode) {
599
- Object.defineProperty(obj, 'parent', {
600
- configurable: true,
601
- writable: true,
602
- enumerable: false,
603
- value: parent || null
604
- });
605
- }
606
-
607
- return obj;
608
- }
609
-
610
- /**
611
- * Parses inline style.
612
- *
613
- * Example: 'color:red' => { color: 'red' }
614
- *
615
- * @param {String} style - The inline style.
616
- * @param {Function} [iterator] - The iterator function.
617
- * @return {null|Object}
618
- */
619
- var styleToObject = function parseInlineStyle(style, iterator) {
620
- if (!style || typeof style !== 'string') return null;
621
-
622
- // make sure to wrap declarations in placeholder
623
- var declarations = parse('p{' + style + '}').stylesheet.rules[0].declarations;
624
- var declaration, property, value;
625
-
626
- var output = null;
627
- var hasIterator = typeof iterator === 'function';
628
-
629
- for (var i = 0, len = declarations.length; i < len; i++) {
630
- declaration = declarations[i];
631
- property = declaration.property;
632
- value = declaration.value;
633
-
634
- if (hasIterator) {
635
- iterator(property, value, declaration);
636
- } else if (value) {
637
- output || (output = {});
638
- output[property] = value;
639
- }
301
+ return output;
640
302
  }
641
303
 
642
- return output;
643
- };
304
+ var styleToObject = StyleToObject;
644
305
 
645
- return styleToObject;
306
+ return styleToObject;
646
307
 
647
308
  })));