csso-rails 0.0.1

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.
@@ -0,0 +1,6 @@
1
+ .rvmrc
2
+ *.gem
3
+ .bundle
4
+ *.rbc
5
+ .yardoc
6
+ Gemfile.lock
@@ -0,0 +1,3 @@
1
+ [submodule "lib/less/js"]
2
+ path = lib/less/js
3
+ url = https://github.com/cloudhead/less.js.git
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
@@ -0,0 +1,52 @@
1
+ csso-rails: Stylesheet Optimizer(CSSO) for Rails Asset pipeline
2
+ =======
3
+
4
+ Ruby adapter for <https://github.com/afelix/csso>
5
+
6
+ about
7
+ -----
8
+ CSSO does structure-optimization for css.
9
+ (readme and tests/comparison - coming later)
10
+ Css is usually reduced more that in half in uncompressed and around 15% in gzipped.
11
+
12
+ usage
13
+ ------
14
+
15
+ From ruby:
16
+ require 'csso'
17
+ Csso.optimize("a{ color: #FF0000; }")
18
+ # produces "a{color:red}"
19
+
20
+ In Rails 3.1+:
21
+ add this gem to your gemfile, and that's it!
22
+ gem 'csso-rails', :git => ''
23
+ Upon including it becomes the default compressor,
24
+ More explicit way - set in config: config.assets.css_compressor = :csso
25
+
26
+ In command line:
27
+ ruby_csso non_optimized.css > optimized.css
28
+
29
+
30
+ MIT-License
31
+ -------
32
+
33
+ > Original CSSO code - Copyright (C) 2011 by Sergey Kryzhanovsky
34
+ > ruby gem - Copyright(C) 2012 Vasily Fedoseyev
35
+
36
+ Permission is hereby granted, free of charge, to any person obtaining a copy
37
+ of this software and associated documentation files (the "Software"), to deal
38
+ in the Software without restriction, including without limitation the rights
39
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
40
+ copies of the Software, and to permit persons to whom the Software is
41
+ furnished to do so, subject to the following conditions:
42
+
43
+ The above copyright notice and this permission notice shall be included in
44
+ all copies or substantial portions of the Software.
45
+
46
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
47
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
48
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
49
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
50
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
51
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
52
+ THE SOFTWARE.
@@ -0,0 +1,9 @@
1
+ require 'bundler'
2
+ require 'bundler/setup'
3
+ require "rspec/core/rake_task"
4
+
5
+ Bundler::GemHelper.install_tasks
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
9
+
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'csso-rails'
4
+
5
+ def help
6
+ puts <<-HLP
7
+ csso- CSS Optimizer (ruby bindings by vasfed)
8
+ Usage:
9
+ $ ruby_csso < style.css - read stdin and output to stdout
10
+ $ ruby_csso file1.css [file2.css [...]] - read all files and output to stdout (concatenating, no optimization between files yet)
11
+ --help - print this help
12
+ --maniac - enable maniac mode (optimize file multiple times until optimization stops to give any results)
13
+ HLP
14
+ end
15
+
16
+ maniac = ARGV.delete('--maniac') && true || false
17
+
18
+ if ($stdin.tty? && ARGV.empty?) || ARGV.delete('-h') || ARGV.delete('--help')
19
+ help
20
+ else
21
+ # Well, lets read those files
22
+ css = ARGF.read
23
+
24
+ puts Csso.optimize(css, maniac)
25
+ end
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "csso/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "csso-rails"
7
+ s.version = Csso::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Vasily Fedoseyev"]
10
+ s.email = ["vasilyfedoseyev@gmail.com"]
11
+ s.homepage = "https://github.com/Vasfed/csso-rails"
12
+ s.summary = %q{CSS Stylesheet optimizer/compressor for Rails}
13
+ s.description = %q{Invoke the CSSO from Ruby}
14
+
15
+ s.rubyforge_project = "csso-rails"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ js_root = 'lib/csso/js'
19
+ Dir.chdir(js_root) do
20
+ s.files += `git ls-files`.split("\n").map {|f| File.join(js_root,f)}
21
+ end
22
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
23
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
24
+ s.require_paths = ["lib"]
25
+
26
+ s.add_dependency "therubyracer", "~> 0.9.9"
27
+ s.add_dependency "commonjs", "~> 0.2.0"
28
+
29
+ s.add_development_dependency "rake"
30
+ s.add_development_dependency "rspec", "~> 2.0"
31
+ end
@@ -0,0 +1,7 @@
1
+ require 'csso'
2
+
3
+ if defined?(Rails)
4
+ require 'csso/rails'
5
+ else
6
+ require 'csso'
7
+ end
@@ -0,0 +1,42 @@
1
+ require 'v8'
2
+ require 'csso/version'
3
+ require 'csso/utils'
4
+ require 'csso/loader'
5
+
6
+ module Csso
7
+
8
+ @loader = Csso::Loader.new
9
+ @csso = @loader.require('cssoapi')
10
+
11
+ def self.js_api
12
+ @csso
13
+ end
14
+
15
+ def self.optimize(css, maniac_mode=false, structural_optimization=true)
16
+ if maniac_mode
17
+ maniac_mode = 4 unless maniac_mode.is_a?(Fixnum) && maniac_mode > 0
18
+ begin
19
+ prev_css = css
20
+ css = Optimizer.new.optimize(css, structural_optimization)
21
+ maniac_mode -= 1
22
+ end while maniac_mode > 0 && prev_css != css
23
+ css
24
+ else
25
+ Optimizer.new.optimize(css, structural_optimization)
26
+ end
27
+ end
28
+
29
+
30
+ class Optimizer
31
+ include CallJS
32
+
33
+ def optimize(css, structural_optimization=true)
34
+ return nil unless css.is_a?(String)
35
+ return css if css.size <= 3
36
+ calljs do
37
+ Csso.js_api['justDoIt'].call(css, !structural_optimization)
38
+ end
39
+ end
40
+ end
41
+
42
+ end
@@ -0,0 +1,1357 @@
1
+ function TRBL(name, imp) {
2
+ this.name = TRBL.extractMain(name);
3
+ this.sides = {
4
+ 'top': null,
5
+ 'right': null,
6
+ 'bottom': null,
7
+ 'left': null
8
+ };
9
+ this.imp = imp ? 4 : 0;
10
+ }
11
+
12
+ TRBL.props = {
13
+ 'margin': 1,
14
+ 'margin-top': 1,
15
+ 'margin-right': 1,
16
+ 'margin-bottom': 1,
17
+ 'margin-left': 1,
18
+ 'padding': 1,
19
+ 'padding-top': 1,
20
+ 'padding-right': 1,
21
+ 'padding-bottom': 1,
22
+ 'padding-left': 1
23
+ };
24
+
25
+ TRBL.extractMain = function(name) {
26
+ var i = name.indexOf('-');
27
+ return i === -1 ? name : name.substr(0, i);
28
+ };
29
+
30
+ TRBL.prototype.impSum = function() {
31
+ var imp = 0, n = 0;
32
+ for (var k in this.sides) {
33
+ if (this.sides[k]) {
34
+ n++;
35
+ if (this.sides[k].imp) imp++;
36
+ }
37
+ }
38
+ return imp === n ? imp : 0;
39
+ };
40
+
41
+ TRBL.prototype.add = function(name, sValue, tValue, imp) {
42
+ var s = this.sides,
43
+ i, x, side, a = [], last,
44
+ imp = imp ? 1 : 0,
45
+ wasUnary = false;
46
+ if ((i = name.lastIndexOf('-')) !== -1) {
47
+ side = name.substr(i + 1);
48
+ if (side in s) {
49
+ if (!s[side]) {
50
+ s[side] = { s: imp ? sValue.substring(0, sValue.length - 10) : sValue, t: [tValue[0]], imp: imp };
51
+ if (tValue[0][1] === 'unary') s[side].t.push(tValue[1]);
52
+ }
53
+ return true;
54
+ }
55
+ } else if (name === this.name) {
56
+ for (i = 0; i < tValue.length; i++) {
57
+ x = tValue[i];
58
+ last = a[a.length - 1];
59
+ switch(x[1]) {
60
+ case 'unary':
61
+ a.push({ s: x[2], t: [x], imp: imp });
62
+ wasUnary = true;
63
+ break;
64
+ case 'number':
65
+ case 'ident':
66
+ if (wasUnary) {
67
+ last.t.push(x);
68
+ last.s += x[2];
69
+ } else {
70
+ a.push({ s: x[2], t: [x], imp: imp });
71
+ }
72
+ wasUnary = false;
73
+ break;
74
+ case 'percentage':
75
+ if (wasUnary) {
76
+ last.t.push(x);
77
+ last.s += x[2][2] + '%';
78
+ } else {
79
+ a.push({ s: x[2][2] + '%', t: [x], imp: imp });
80
+ }
81
+ wasUnary = false;
82
+ break;
83
+ case 'dimension':
84
+ if (wasUnary) {
85
+ last.t.push(x);
86
+ last.s += x[2][2] + x[3][2];
87
+ } else {
88
+ a.push({ s: x[2][2] + x[3][2], t: [x], imp: imp });
89
+ }
90
+ wasUnary = false;
91
+ break;
92
+ case 's':
93
+ case 'comment':
94
+ case 'important':
95
+ break;
96
+ default:
97
+ return false;
98
+ }
99
+ }
100
+
101
+ if (a.length > 4) return false;
102
+
103
+ if (!a[1]) a[1] = a[0];
104
+ if (!a[2]) a[2] = a[0];
105
+ if (!a[3]) a[3] = a[1];
106
+
107
+ if (!s.top) s.top = a[0];
108
+ if (!s.right) s.right = a[1];
109
+ if (!s.bottom) s.bottom = a[2];
110
+ if (!s.left) s.left = a[3];
111
+
112
+ return true;
113
+ }
114
+ };
115
+
116
+ TRBL.prototype.isOkToMinimize = function() {
117
+ var s = this.sides,
118
+ imp;
119
+
120
+ if (!!(s.top && s.right && s.bottom && s.left)) {
121
+ imp = s.top.imp + s.right.imp + s.bottom.imp + s.left.imp;
122
+ return (imp === 0 || imp === 4 || imp === this.imp);
123
+ }
124
+ return false;
125
+ };
126
+
127
+ TRBL.prototype.getValue = function() {
128
+ var s = this.sides,
129
+ a = [s.top, s.right, s.bottom, s.left],
130
+ r = [{}, 'value'];
131
+
132
+ if (s.left.s === s.right.s) {
133
+ a.length--;
134
+ if (s.bottom.s === s.top.s) {
135
+ a.length--;
136
+ if (s.right.s === s.top.s) {
137
+ a.length--;
138
+ }
139
+ }
140
+ }
141
+
142
+ for (var i = 0; i < a.length - 1; i++) {
143
+ r = r.concat(a[i].t);
144
+ r.push([{ s: ' ' }, 's', ' ']);
145
+ }
146
+ r = r.concat(a[i].t);
147
+
148
+ if (this.imp) r.push([{ s: '!important'}, 'important']);
149
+
150
+ return r;
151
+ };
152
+
153
+ TRBL.prototype.getProperty = function() {
154
+ return [{ s: this.name }, 'property', [{ s: this.name }, 'ident', this.name]];
155
+ };
156
+
157
+ TRBL.prototype.getString = function() {
158
+ var p = this.getProperty(),
159
+ v = this.getValue().slice(2),
160
+ r = p[0].s + ':';
161
+
162
+ for (var i = 0; i < v.length; i++) r += v[i][0].s;
163
+
164
+ return r;
165
+ };
166
+ function CSSOCompressor() {}
167
+
168
+ CSSOCompressor.prototype.init = function() {
169
+ this.props = {};
170
+ this.shorts = {};
171
+ this.ccrules = {}; // clean comment rules — special case to resolve ambiguity
172
+ this.crules = {}; // compress rules
173
+ this.prules = {}; // prepare rules
174
+ this.frrules = {}; // freeze ruleset rules
175
+ this.msrules = {}; // mark shorthands rules
176
+ this.csrules = {}; // clean shorthands rules
177
+ this.rbrules = {}; // restructure block rules
178
+ this.rjrules = {}; // rejoin ruleset rules
179
+ this.rrrules = {}; // restructure ruleset rules
180
+ this.frules = {}; // finalize rules
181
+
182
+ this.initRules(this.crules, this.defCCfg);
183
+ this.initRules(this.ccrules, this.cleanCfg);
184
+ this.initRules(this.frrules, this.frCfg);
185
+ this.initRules(this.prules, this.preCfg);
186
+ this.initRules(this.msrules, this.msCfg);
187
+ this.initRules(this.csrules, this.csCfg);
188
+ this.initRules(this.rbrules, this.defRBCfg);
189
+ this.initRules(this.rjrules, this.defRJCfg);
190
+ this.initRules(this.rrrules, this.defRRCfg);
191
+ this.initRules(this.frules, this.defFCfg);
192
+ };
193
+
194
+ CSSOCompressor.prototype.initRules = function(r, cfg) {
195
+ var o = this.order,
196
+ p = this.profile,
197
+ x, i, k,
198
+ t = [];
199
+
200
+ for (i = 0; i < o.length; i++) if (o[i] in cfg) t.push(o[i]);
201
+
202
+ if (!t.length) t = o;
203
+ for (i = 0; i < t.length; i++) {
204
+ x = p[t[i]];
205
+ for (k in x) r[k] ? r[k].push(t[i]) : r[k] = [t[i]];
206
+ }
207
+ };
208
+
209
+ CSSOCompressor.prototype.cleanCfg = {
210
+ 'cleanComment': 1
211
+ };
212
+
213
+ CSSOCompressor.prototype.defCCfg = {
214
+ 'cleanCharset': 1,
215
+ 'cleanImport': 1,
216
+ 'cleanWhitespace': 1,
217
+ 'cleanDecldelim': 1,
218
+ 'compressNumber': 1,
219
+ 'compressColor': 1,
220
+ 'compressDimension': 1,
221
+ 'compressString': 1,
222
+ 'compressFontWeight': 1,
223
+ 'compressFont': 1,
224
+ 'compressBackground': 1,
225
+ 'cleanEmpty': 1
226
+ };
227
+
228
+ CSSOCompressor.prototype.defRBCfg = {
229
+ 'restructureBlock': 1
230
+ };
231
+
232
+ CSSOCompressor.prototype.defRJCfg = {
233
+ 'rejoinRuleset': 1,
234
+ 'cleanEmpty': 1
235
+ };
236
+
237
+ CSSOCompressor.prototype.defRRCfg = {
238
+ 'restructureRuleset': 1,
239
+ 'cleanEmpty': 1
240
+ };
241
+
242
+ CSSOCompressor.prototype.defFCfg = {
243
+ 'cleanEmpty': 1,
244
+ 'delimSelectors': 1,
245
+ 'delimBlocks': 1
246
+ };
247
+
248
+ CSSOCompressor.prototype.preCfg = {
249
+ 'destroyDelims': 1,
250
+ 'preTranslate': 1
251
+ };
252
+
253
+ CSSOCompressor.prototype.msCfg = {
254
+ 'markShorthands': 1
255
+ };
256
+
257
+ CSSOCompressor.prototype.frCfg = {
258
+ 'freezeRulesets': 1
259
+ };
260
+
261
+ CSSOCompressor.prototype.csCfg = {
262
+ 'cleanShorthands': 1,
263
+ 'cleanEmpty': 1
264
+ };
265
+
266
+ CSSOCompressor.prototype.order = [
267
+ 'cleanCharset',
268
+ 'cleanImport',
269
+ 'cleanComment',
270
+ 'cleanWhitespace',
271
+ 'compressNumber',
272
+ 'compressColor',
273
+ 'compressDimension',
274
+ 'compressString',
275
+ 'compressFontWeight',
276
+ 'compressFont',
277
+ 'compressBackground',
278
+ 'freezeRulesets',
279
+ 'destroyDelims',
280
+ 'preTranslate',
281
+ 'markShorthands',
282
+ 'cleanShorthands',
283
+ 'restructureBlock',
284
+ 'rejoinRuleset',
285
+ 'restructureRuleset',
286
+ 'cleanEmpty',
287
+ 'delimSelectors',
288
+ 'delimBlocks'
289
+ ];
290
+
291
+ CSSOCompressor.prototype.profile = {
292
+ 'cleanCharset': {
293
+ 'atrules': 1
294
+ },
295
+ 'cleanImport': {
296
+ 'atrules': 1
297
+ },
298
+ 'cleanWhitespace': {
299
+ 's': 1
300
+ },
301
+ 'compressNumber': {
302
+ 'number': 1
303
+ },
304
+ 'compressColor': {
305
+ 'vhash': 1,
306
+ 'funktion': 1,
307
+ 'ident': 1
308
+ },
309
+ 'compressDimension': {
310
+ 'dimension': 1
311
+ },
312
+ 'compressString': {
313
+ 'string': 1
314
+ },
315
+ 'compressFontWeight': {
316
+ 'declaration': 1
317
+ },
318
+ 'compressFont': {
319
+ 'declaration': 1
320
+ },
321
+ 'compressBackground': {
322
+ 'declaration': 1
323
+ },
324
+ 'cleanComment': {
325
+ 'comment': 1
326
+ },
327
+ 'cleanDecldelim': {
328
+ 'block': 1
329
+ },
330
+ 'cleanEmpty': {
331
+ 'ruleset': 1,
332
+ 'atruleb': 1,
333
+ 'atruler': 1
334
+ },
335
+ 'destroyDelims': {
336
+ 'decldelim': 1,
337
+ 'delim': 1
338
+ },
339
+ 'preTranslate': {
340
+ 'declaration': 1,
341
+ 'property': 1,
342
+ 'simpleselector': 1,
343
+ 'filter': 1,
344
+ 'value': 1,
345
+ 'number': 1,
346
+ 'percentage': 1,
347
+ 'dimension': 1,
348
+ 'ident': 1
349
+ },
350
+ 'restructureBlock': {
351
+ 'block': 1
352
+ },
353
+ 'rejoinRuleset': {
354
+ 'ruleset': 1
355
+ },
356
+ 'restructureRuleset': {
357
+ 'ruleset': 1
358
+ },
359
+ 'delimSelectors': {
360
+ 'selector': 1
361
+ },
362
+ 'delimBlocks': {
363
+ 'block': 1
364
+ },
365
+ 'markShorthands': {
366
+ 'block': 1
367
+ },
368
+ 'cleanShorthands': {
369
+ 'declaration': 1
370
+ },
371
+ 'freezeRulesets': {
372
+ 'ruleset': 1
373
+ }
374
+ };
375
+
376
+ CSSOCompressor.prototype.isContainer = function(o) {
377
+ if (Array.isArray(o)) {
378
+ for (var i = 0; i < o.length; i++) if (Array.isArray(o[i])) return true;
379
+ }
380
+ };
381
+
382
+ CSSOCompressor.prototype.process = function(rules, token, container, i, path) {
383
+ var rule = token[1];
384
+ if (rule && rules[rule]) {
385
+ var r = rules[rule],
386
+ x1 = token, x2,
387
+ o = this.order, k;
388
+ for (var k = 0; k < r.length; k++) {
389
+ x2 = this[r[k]](x1, rule, container, i, path);
390
+ if (x2 === null) return null;
391
+ else if (x2 !== undefined) x1 = x2;
392
+ }
393
+ }
394
+ return x1;
395
+ };
396
+
397
+ CSSOCompressor.prototype.compress = function(tree, ro) {
398
+ this.init();
399
+ this.info = typeof tree[0] !== 'string';
400
+
401
+ var x = this.info ? tree : this.injectInfo([tree])[0],
402
+ l0, l1 = 100000000000, ls,
403
+ x0, x1, xs;
404
+
405
+ // compression without restructure
406
+ x = this.walk(this.ccrules, x, '/0');
407
+ x = this.walk(this.crules, x, '/0');
408
+ x = this.walk(this.prules, x, '/0');
409
+ x = this.walk(this.frrules, x, '/0');
410
+ ls = translator.translate(cleanInfo(x)).length;
411
+
412
+ if (!ro) { // restructure ON
413
+ xs = this.copyArray(x);
414
+ this.disjoin(x);
415
+ x = this.walk(this.msrules, x, '/0');
416
+ x = this.walk(this.csrules, x, '/0');
417
+ x = this.walk(this.rbrules, x, '/0');
418
+ do {
419
+ l0 = l1;
420
+ x0 = this.copyArray(x);
421
+ x = this.walk(this.rjrules, x, '/0');
422
+ x = this.walk(this.rrrules, x, '/0');
423
+ l1 = translator.translate(cleanInfo(x)).length;
424
+ x1 = this.copyArray(x);
425
+ } while (l0 > l1);
426
+ if (ls < l0 && ls < l1) x = xs;
427
+ else if (l0 < l1) x = x0;
428
+ }
429
+
430
+ x = this.walk(this.frules, x, '/0');
431
+
432
+ return this.info ? x : cleanInfo(x);
433
+ };
434
+
435
+ CSSOCompressor.prototype.injectInfo = function(token) {
436
+ var t;
437
+ for (var i = token.length - 1; i > -1; i--) {
438
+ t = token[i];
439
+ if (t && Array.isArray(t)) {
440
+ if (this.isContainer(t)) t = this.injectInfo(t);
441
+ t.splice(0, 0, {});
442
+ }
443
+ }
444
+ return token;
445
+ };
446
+
447
+ CSSOCompressor.prototype.disjoin = function(container) {
448
+ var t, s, r, sr;
449
+ for (var i = container.length - 1; i > -1; i--) {
450
+ t = container[i];
451
+ if (t && Array.isArray(t)) {
452
+ if (t[1] === 'ruleset') {
453
+ s = t[2];
454
+ if (s.length > 3) {
455
+ sr = s.slice(0, 2);
456
+ for (var k = s.length - 1; k > 1; k--) {
457
+ r = this.copyArray(t);
458
+ r[2] = sr.concat([s[k]]);
459
+ r[2][0].s = s[k][0].s;
460
+ container.splice(i + 1, 0, r);
461
+ }
462
+ container.splice(i, 1);
463
+ }
464
+ }
465
+ }
466
+ if (this.isContainer(t)) this.disjoin(t);
467
+ }
468
+ };
469
+
470
+ CSSOCompressor.prototype.walk = function(rules, container, path) {
471
+ var t, x;
472
+ for (var i = container.length - 1; i > -1; i--) {
473
+ t = container[i];
474
+ if (t && Array.isArray(t)) {
475
+ if (this.isContainer(t)) t = this.walk(rules, t, path + '/' + i); // go inside
476
+ if (t === null) container.splice(i, 1);
477
+ else {
478
+ if (x = this.process(rules, t, container, i, path)) container[i] = x; // compressed not null
479
+ else if (x === null) container.splice(i, 1); // null is the mark to delete token
480
+ }
481
+ }
482
+ }
483
+ return container.length ? container : null;
484
+ };
485
+
486
+ CSSOCompressor.prototype.freezeRulesets = function(token, rule, container, i) {
487
+ var info = token[0],
488
+ selector = token[2];
489
+
490
+ info.freeze = this.freezeNeeded(selector);
491
+ info.freezeID = this.selectorSignature(selector);
492
+ info.pseudoID = this.composePseudoID(selector);
493
+ this.markSimplePseudo(selector);
494
+
495
+ return token;
496
+ };
497
+
498
+ CSSOCompressor.prototype.markSimplePseudo = function(selector) {
499
+ var ss, sg = {};
500
+
501
+ for (var i = 2; i < selector.length; i++) {
502
+ ss = selector[i];
503
+ ss[0].pseudo = this.containsPseudo(ss);
504
+ ss[0].sg = sg;
505
+ sg[ss[0].s] = 1;
506
+ }
507
+ };
508
+
509
+ CSSOCompressor.prototype.composePseudoID = function(selector) {
510
+ var a = [], ss;
511
+
512
+ for (var i = 2; i < selector.length; i++) {
513
+ ss = selector[i];
514
+ if (this.containsPseudo(ss)) {
515
+ a.push(ss[0].s);
516
+ }
517
+ }
518
+
519
+ a.sort();
520
+
521
+ return a.join(',');
522
+ };
523
+
524
+ CSSOCompressor.prototype.containsPseudo = function(sselector) {
525
+ for (var j = 2; j < sselector.length; j++) {
526
+ switch (sselector[j][1]) {
527
+ case 'pseudoc':
528
+ case 'pseudoe':
529
+ case 'nthselector':
530
+ if (!(sselector[j][2][2] in this.notFPClasses)) return true;
531
+ }
532
+ }
533
+ };
534
+
535
+ CSSOCompressor.prototype.selectorSignature = function(selector) {
536
+ var a = [];
537
+
538
+ for (var i = 2; i < selector.length; i++) {
539
+ a.push(translator.translate(cleanInfo(selector[i])));
540
+ }
541
+
542
+ a.sort();
543
+
544
+ return a.join(',');
545
+ };
546
+
547
+ CSSOCompressor.prototype.pseudoSelectorSignature = function(selector, exclude) {
548
+ var a = [], b = {}, ss, wasExclude = false;
549
+ exclude = exclude || {};
550
+
551
+ for (var i = 2; i < selector.length; i++) {
552
+ ss = selector[i];
553
+ for (var j = 2; j < ss.length; j++) {
554
+ switch (ss[j][1]) {
555
+ case 'pseudoc':
556
+ case 'pseudoe':
557
+ case 'nthselector':
558
+ if (!(ss[j][2][2] in exclude)) b[ss[j][2][2]] = 1;
559
+ else wasExclude = true;
560
+ break;
561
+ }
562
+ }
563
+ }
564
+
565
+ for (var k in b) a.push(k);
566
+
567
+ a.sort();
568
+
569
+ return a.join(',') + wasExclude;
570
+ };
571
+
572
+ CSSOCompressor.prototype.notFPClasses = {
573
+ 'link': 1,
574
+ 'visited': 1,
575
+ 'hover': 1,
576
+ 'active': 1,
577
+ 'first-letter': 1,
578
+ 'first-line': 1
579
+ };
580
+
581
+ CSSOCompressor.prototype.notFPElements = {
582
+ 'first-letter': 1,
583
+ 'first-line': 1
584
+ };
585
+
586
+ CSSOCompressor.prototype.freezeNeeded = function(selector) {
587
+ var ss;
588
+ for (var i = 2; i < selector.length; i++) {
589
+ ss = selector[i];
590
+ for (var j = 2; j < ss.length; j++) {
591
+ switch (ss[j][1]) {
592
+ case 'pseudoc':
593
+ if (!(ss[j][2][2] in this.notFPClasses)) return true;
594
+ break;
595
+ case 'pseudoe':
596
+ if (!(ss[j][2][2] in this.notFPElements)) return true;
597
+ break;
598
+ case 'nthselector':
599
+ return true;
600
+ break;
601
+ }
602
+ }
603
+ }
604
+ return false;
605
+ };
606
+
607
+ CSSOCompressor.prototype.cleanCharset = function(token, rule, container, i) {
608
+ if (token[2][2][2] === 'charset') {
609
+ for (i = i - 1; i > 1; i--) {
610
+ if (container[i][1] !== 's' && container[i][1] !== 'comment') return null;
611
+ }
612
+ }
613
+ };
614
+
615
+ CSSOCompressor.prototype.cleanImport = function(token, rule, container, i) {
616
+ var x;
617
+ for (i = i - 1; i > 1; i--) {
618
+ x = container[i][1];
619
+ if (x !== 's' && x !== 'comment') {
620
+ if (x === 'atrules') {
621
+ x = container[i][2][2][2];
622
+ if (x !== 'import' && x !== 'charset') return null;
623
+ } else return null;
624
+ }
625
+ }
626
+ };
627
+
628
+ CSSOCompressor.prototype.cleanComment = function(token, rule, container, i) {
629
+ var pr = ((container[1] === 'braces' && i === 4) ||
630
+ (container[1] !== 'braces' && i === 2)) ? null : container[i - 1][1],
631
+ nr = i === container.length - 1 ? null : container[i + 1][1];
632
+
633
+ if (nr !== null && pr !== null) {
634
+ if (this._cleanComment(nr) || this._cleanComment(pr)) return null;
635
+ } else return null;
636
+ };
637
+
638
+ CSSOCompressor.prototype._cleanComment = function(r) {
639
+ switch(r) {
640
+ case 's':
641
+ case 'operator':
642
+ case 'attrselector':
643
+ case 'block':
644
+ case 'decldelim':
645
+ case 'ruleset':
646
+ case 'declaration':
647
+ case 'atruleb':
648
+ case 'atrules':
649
+ case 'atruler':
650
+ case 'important':
651
+ case 'nth':
652
+ case 'combinator':
653
+ return true;
654
+ }
655
+ };
656
+
657
+ CSSOCompressor.prototype.nextToken = function(container, type, i, exactly) {
658
+ var t, r;
659
+ for (; i < container.length; i++) {
660
+ t = container[i];
661
+ if (Array.isArray(t)) {
662
+ r = t[1];
663
+ if (r === type) return t;
664
+ else if (exactly && r !== 's') return;
665
+ }
666
+ }
667
+ };
668
+
669
+ CSSOCompressor.prototype.cleanWhitespace = function(token, rule, container, i) {
670
+ var pr = ((container[1] === 'braces' && i === 4) ||
671
+ (container[1] !== 'braces' && i === 2)) ? null : container[i - 1][1],
672
+ nr = i === container.length - 1 ? null : container[i + 1][1];
673
+
674
+ if (nr === 'unknown') token[2] = '\n';
675
+ else {
676
+ if (!(container[1] === 'atrulerq' && !pr) && !this.issue16(container, i)) {
677
+ if (nr !== null && pr !== null) {
678
+ if (this._cleanWhitespace(nr, false) || this._cleanWhitespace(pr, true)) return null;
679
+ } else return null;
680
+ }
681
+
682
+ token[2] = ' ';
683
+ }
684
+
685
+ return token;
686
+ };
687
+
688
+ // See https://github.com/afelix/csso/issues/16
689
+ CSSOCompressor.prototype.issue16 = function(container, i) {
690
+ return (i !== 2 && i !== container.length - 1 && container[i - 1][1] === 'uri');
691
+ };
692
+
693
+ CSSOCompressor.prototype._cleanWhitespace = function(r, left) {
694
+ switch(r) {
695
+ case 's':
696
+ case 'operator':
697
+ case 'attrselector':
698
+ case 'block':
699
+ case 'decldelim':
700
+ case 'ruleset':
701
+ case 'declaration':
702
+ case 'atruleb':
703
+ case 'atrules':
704
+ case 'atruler':
705
+ case 'important':
706
+ case 'nth':
707
+ case 'combinator':
708
+ return true;
709
+ }
710
+ if (left) {
711
+ switch(r) {
712
+ case 'funktion':
713
+ case 'braces':
714
+ case 'uri':
715
+ return true;
716
+ }
717
+ }
718
+ };
719
+
720
+ CSSOCompressor.prototype.cleanDecldelim = function(token) {
721
+ for (var i = token.length - 1; i > 1; i--) {
722
+ if (token[i][1] === 'decldelim' &&
723
+ token[i + 1][1] !== 'declaration') token.splice(i, 1);
724
+ }
725
+ if (token[2][1] === 'decldelim') token.splice(2, 1);
726
+ return token;
727
+ };
728
+
729
+ CSSOCompressor.prototype.compressNumber = function(token) {
730
+ var x = token[2];
731
+
732
+ if (/^0*/.test(x)) x = x.replace(/^0+/, '');
733
+ if (/\.0*$/.test(x)) x = x.replace(/\.0*$/, '');
734
+ if (/\..*[1-9]+0+$/.test(x)) x = x.replace(/0+$/, '');
735
+ if (x === '.' || x === '') x = '0';
736
+
737
+ token[2] = x;
738
+ token[0].s = x;
739
+ return token;
740
+ };
741
+
742
+ CSSOCompressor.prototype.compressColor = function(token, rule, container, i) {
743
+ switch(rule) {
744
+ case 'vhash':
745
+ return this.compressHashColor(token);
746
+ case 'funktion':
747
+ return this.compressFunctionColor(token);
748
+ case 'ident':
749
+ return this.compressIdentColor(token, rule, container, i);
750
+ }
751
+ };
752
+
753
+ CSSOCompressor.prototype.compressIdentColor = function(token, rule, container) {
754
+ var map = { 'yellow': 'ff0',
755
+ 'fuchsia': 'f0f',
756
+ 'white': 'fff',
757
+ 'black': '000',
758
+ 'blue': '00f',
759
+ 'aqua': '0ff' },
760
+ allow = { 'value': 1, 'functionBody': 1 },
761
+ _x = token[2].toLowerCase();
762
+
763
+ if (container[1] in allow && _x in map) return [{}, 'vhash', map[_x]];
764
+ };
765
+
766
+ CSSOCompressor.prototype.compressHashColor = function(token) {
767
+ return this._compressHashColor(token[2], token[0]);
768
+ };
769
+
770
+ CSSOCompressor.prototype._compressHashColor = function(x, info) {
771
+ var map = { 'f00': 'red',
772
+ 'c0c0c0': 'silver',
773
+ '808080': 'gray',
774
+ '800000': 'maroon',
775
+ '800080': 'purple',
776
+ '008000': 'green',
777
+ '808000': 'olive',
778
+ '000080': 'navy',
779
+ '008080': 'teal'},
780
+ _x = x;
781
+ x = x.toLowerCase();
782
+
783
+ if (x.length === 6 &&
784
+ x.charAt(0) === x.charAt(1) &&
785
+ x.charAt(2) === x.charAt(3) &&
786
+ x.charAt(4) === x.charAt(5)) x = x.charAt(0) + x.charAt(2) + x.charAt(4);
787
+
788
+ return map[x] ? [info, 'string', map[x]] : [info, 'vhash', (x.length < _x.length ? x : _x)];
789
+ };
790
+
791
+ CSSOCompressor.prototype.compressFunctionColor = function(token) {
792
+ var i, v = [], t, h = '', body;
793
+
794
+ if (token[2][2] === 'rgb') {
795
+ body = token[3];
796
+ for (i = 2; i < body.length; i++) {
797
+ t = body[i][1];
798
+ if (t === 'number') v.push(body[i]);
799
+ else if (t !== 'operator') { v = []; break }
800
+ }
801
+ if (v.length === 3) {
802
+ h += (t = Number(v[0][2]).toString(16)).length === 1 ? '0' + t : t;
803
+ h += (t = Number(v[1][2]).toString(16)).length === 1 ? '0' + t : t;
804
+ h += (t = Number(v[2][2]).toString(16)).length === 1 ? '0' + t : t;
805
+ if (h.length === 6) return this._compressHashColor(h, {});
806
+ }
807
+ }
808
+ };
809
+
810
+ CSSOCompressor.prototype.compressDimension = function(token) {
811
+ if (token[2][2] === '0') return token[2];
812
+ };
813
+
814
+ CSSOCompressor.prototype.compressString = function(token, rule, container) {
815
+ var s = token[2], r = '', c;
816
+ for (var i = 0; i < s.length; i++) {
817
+ c = s.charAt(i);
818
+ if (c === '\\' && s.charAt(i + 1) === '\n') i++;
819
+ else r += c;
820
+ }
821
+ if (container[1] === 'attrib' && /^('|")[a-zA-Z0-9]*('|")$/.test(r)) {
822
+ r = r.substring(1, r.length - 1);
823
+ }
824
+ if (s.length !== r.length) return [{}, 'string', r];
825
+ };
826
+
827
+ CSSOCompressor.prototype.compressFontWeight = function(token) {
828
+ var p = token[2],
829
+ v = token[3];
830
+ if (p[2][2].indexOf('font-weight') !== -1 && v[2][1] === 'ident') {
831
+ if (v[2][2] === 'normal') v[2] = [{}, 'number', '400'];
832
+ else if (v[2][2] === 'bold') v[2] = [{}, 'number', '700'];
833
+ return token;
834
+ }
835
+ };
836
+
837
+ CSSOCompressor.prototype.compressFont = function(token) {
838
+ var p = token[2],
839
+ v = token[3],
840
+ i, x, t;
841
+ if (/font$/.test(p[2][2]) && v.length) {
842
+ v.splice(2, 0, [{}, 's', '']);
843
+ for (i = v.length - 1; i > 2; i--) {
844
+ x = v[i];
845
+ if (x[1] === 'ident') {
846
+ x = x[2];
847
+ if (x === 'bold') v[i] = [{}, 'number', '700'];
848
+ else if (x === 'normal') {
849
+ t = v[i - 1];
850
+ if (t[1] === 'operator' && t[2] === '/') v.splice(--i, 2);
851
+ else v.splice(i, 1);
852
+ if (v[i - 1][1] === 's') v.splice(--i, 1);
853
+ }
854
+ else if (x === 'medium' && v[i + 1] && v[i + 1][2] !== '/') {
855
+ v.splice(i, 1);
856
+ if (v[i - 1][1] === 's') v.splice(--i, 1);
857
+ }
858
+ }
859
+ }
860
+ if (v.length > 2 && v[2][1] === 's') v.splice(2, 1);
861
+ if (v.length === 2) v.push([{}, 'ident', 'normal']);
862
+ return token;
863
+ }
864
+ };
865
+
866
+ CSSOCompressor.prototype.compressBackground = function(token) {
867
+ var p = token[2],
868
+ v = token[3],
869
+ i, x, t,
870
+ n = v[v.length - 1][1] === 'important' ? 3 : 2;
871
+ if (/background$/.test(p[2][2]) && v.length) {
872
+ v.splice(2, 0, [{}, 's', '']);
873
+ for (i = v.length - 1; i > n; i--) {
874
+ x = v[i];
875
+ if (x[1] === 'ident') {
876
+ x = x[2];
877
+ if (x === 'transparent' || x === 'none' || x === 'repeat' || x === 'scroll') {
878
+ v.splice(i, 1);
879
+ if (v[i - 1][1] === 's') v.splice(--i, 1);
880
+ }
881
+ }
882
+ }
883
+ if (v.length > 2 && v[2][1] === 's') v.splice(2, 1);
884
+ if (v.length === 2) v.splice(2, 0, [{}, 'number', '0'], [{}, 's', ' '], [{}, 'number', '0']);
885
+ return token;
886
+ }
887
+ };
888
+
889
+ CSSOCompressor.prototype.cleanEmpty = function(token, rule) {
890
+ switch(rule) {
891
+ case 'ruleset':
892
+ if (token[3].length === 2) return null;
893
+ break;
894
+ case 'atruleb':
895
+ if (token[token.length - 1].length < 3) return null;
896
+ break;
897
+ case 'atruler':
898
+ if (token[4].length < 3) return null;
899
+ break;
900
+ }
901
+ };
902
+
903
+ CSSOCompressor.prototype.destroyDelims = function() {
904
+ return null;
905
+ };
906
+
907
+ CSSOCompressor.prototype.preTranslate = function(token) {
908
+ token[0].s = translator.translate(cleanInfo(token));
909
+ return token;
910
+ };
911
+
912
+ CSSOCompressor.prototype.markShorthands = function(token, rule, container, j, path) {
913
+ if (container[1] === 'ruleset') {
914
+ var selector = container[2][2][0].s,
915
+ freeze = container[0].freeze,
916
+ freezeID = container[0].freezeID;
917
+ } else {
918
+ var selector = '',
919
+ freeze = false,
920
+ freezeID = 'fake';
921
+ }
922
+ var x, p, v, imp, s, key,
923
+ pre = this.pathUp(path) + '/' + (freeze ? '&' + freezeID + '&' : '') + selector + '/';
924
+
925
+ for (var i = token.length - 1; i > -1; i--) {
926
+ x = token[i];
927
+ if (x[1] === 'declaration') {
928
+ v = x[3];
929
+ imp = v[v.length - 1][1] === 'important';
930
+ p = x[2][0].s;
931
+ x[0].id = path + '/' + i;
932
+ if (p in TRBL.props) {
933
+ key = pre + TRBL.extractMain(p);
934
+ if (s = this.shorts[key]) {
935
+ if (imp && !s.imp) s.invalid = true;
936
+ } else {
937
+ s = new TRBL(p, imp);
938
+ x[0].replaceByShort = true;
939
+ x[0].shorthandKey = key;
940
+ }
941
+ if (!s.invalid) {
942
+ s.add(p, v[0].s, v.slice(2), imp);
943
+ this.shorts[key] = s;
944
+ x[0].removeByShort = true;
945
+ x[0].shorthandKey = key;
946
+ }
947
+ }
948
+ }
949
+ }
950
+ return token;
951
+ };
952
+
953
+ CSSOCompressor.prototype.cleanShorthands = function(token) {
954
+ if (token[0].removeByShort || token[0].replaceByShort) {
955
+ var s, t;
956
+
957
+ s = this.shorts[token[0].shorthandKey];
958
+
959
+ if (!s.invalid && s.isOkToMinimize()) {
960
+ if (token[0].replaceByShort) {
961
+ t = [{}, 'declaration', s.getProperty(), s.getValue()];
962
+ t[0].s = translator.translate(cleanInfo(t));
963
+ return t;
964
+ } else return null;
965
+ }
966
+ }
967
+ };
968
+
969
+ CSSOCompressor.prototype.restructureBlock = function(token, rule, container, j, path) {
970
+ if (container[1] === 'ruleset') {
971
+ var props = this.props,
972
+ isPseudo = container[2][2][0].pseudo,
973
+ selector = container[2][2][0].s,
974
+ freeze = container[0].freeze,
975
+ freezeID = container[0].freezeID,
976
+ pseudoID = container[0].pseudoID,
977
+ sg = container[2][2][0].sg;
978
+ } else {
979
+ var props = {},
980
+ isPseudo = false,
981
+ selector = '',
982
+ freeze = false,
983
+ freezeID = 'fake',
984
+ pseudoID = 'fake',
985
+ sg = {};
986
+ }
987
+ var x, p, v, imp, t,
988
+ pre = this.pathUp(path) + '/' + selector + '/',
989
+ ppre;
990
+
991
+ for (var i = token.length - 1; i > -1; i--) {
992
+ x = token[i];
993
+ if (x[1] === 'declaration') {
994
+ v = x[3];
995
+ imp = v[v.length - 1][1] === 'important';
996
+ p = x[2][0].s;
997
+ ppre = this.buildPPre(pre, p, v, x, freeze);
998
+ x[0].id = path + '/' + i;
999
+ if (p !== 'src' && (t = props[ppre])) { // see https://github.com/afelix/csso/issues/50 about 'src'
1000
+ if ((isPseudo && freezeID === t.freezeID) || // pseudo from equal selectors group
1001
+ (!isPseudo && pseudoID === t.pseudoID) || // not pseudo from equal pseudo signature group
1002
+ (isPseudo && pseudoID === t.pseudoID && this.hashInHash(sg, t.sg))) { // pseudo from covered selectors group
1003
+ if (imp && !t.imp) {
1004
+ props[ppre] = { block: token, imp: imp, id: x[0].id, sg: sg,
1005
+ freeze: freeze, path: path, freezeID: freezeID, pseudoID: pseudoID };
1006
+ this.deleteProperty(t.block, t.id);
1007
+ } else {
1008
+ token.splice(i, 1);
1009
+ }
1010
+ }
1011
+ } else if (this.needless(p, props, pre, imp, v, x, freeze)) {
1012
+ token.splice(i, 1);
1013
+ } else {
1014
+ props[ppre] = { block: token, imp: imp, id: x[0].id, sg: sg,
1015
+ freeze: freeze, path: path, freezeID: freezeID, pseudoID: pseudoID };
1016
+ }
1017
+ }
1018
+ }
1019
+ return token;
1020
+ };
1021
+
1022
+ CSSOCompressor.prototype.buildPPre = function(pre, p, v, d, freeze) {
1023
+ var fp = freeze ? 'ft:' : 'ff:';
1024
+ if (p.indexOf('background') !== -1) return fp + pre + d[0].s;
1025
+
1026
+ var _v = v.slice(2),
1027
+ colorMark = [
1028
+ 0, // ident, vhash, rgb
1029
+ 0, // hsl
1030
+ 0, // hsla
1031
+ 0 // rgba
1032
+ ];
1033
+
1034
+ for (var i = 0; i < _v.length; i++) {
1035
+ switch(_v[i][1]) {
1036
+ case 'vhash':
1037
+ case 'ident':
1038
+ colorMark[0] = 1; break;
1039
+ case 'funktion':
1040
+ switch(_v[i][2][2]) {
1041
+ case 'rgb':
1042
+ colorMark[0] = 1; break;
1043
+ case 'hsl':
1044
+ colorMark[1] = 1; break;
1045
+ case 'hsla':
1046
+ colorMark[2] = 1; break;
1047
+ case 'rgba':
1048
+ colorMark[3] = 1; break;
1049
+ }
1050
+ break;
1051
+ }
1052
+ }
1053
+
1054
+ return fp + pre + p + colorMark.join('');
1055
+ };
1056
+
1057
+ CSSOCompressor.prototype.deleteProperty = function(block, id) {
1058
+ var d;
1059
+ for (var i = block.length - 1; i > 1; i--) {
1060
+ d = block[i];
1061
+ if (Array.isArray(d) && d[1] === 'declaration' && d[0].id === id) {
1062
+ block.splice(i, 1);
1063
+ return;
1064
+ }
1065
+ }
1066
+ };
1067
+
1068
+ CSSOCompressor.prototype.nlTable = {
1069
+ 'border-width': ['border'],
1070
+ 'border-style': ['border'],
1071
+ 'border-color': ['border'],
1072
+ 'border-top': ['border'],
1073
+ 'border-right': ['border'],
1074
+ 'border-bottom': ['border'],
1075
+ 'border-left': ['border'],
1076
+ 'border-top-width': ['border-top', 'border-width', 'border'],
1077
+ 'border-right-width': ['border-right', 'border-width', 'border'],
1078
+ 'border-bottom-width': ['border-bottom', 'border-width', 'border'],
1079
+ 'border-left-width': ['border-left', 'border-width', 'border'],
1080
+ 'border-top-style': ['border-top', 'border-style', 'border'],
1081
+ 'border-right-style': ['border-right', 'border-style', 'border'],
1082
+ 'border-bottom-style': ['border-bottom', 'border-style', 'border'],
1083
+ 'border-left-style': ['border-left', 'border-style', 'border'],
1084
+ 'border-top-color': ['border-top', 'border-color', 'border'],
1085
+ 'border-right-color': ['border-right', 'border-color', 'border'],
1086
+ 'border-bottom-color': ['border-bottom', 'border-color', 'border'],
1087
+ 'border-left-color': ['border-left', 'border-color', 'border'],
1088
+ 'margin-top': ['margin'],
1089
+ 'margin-right': ['margin'],
1090
+ 'margin-bottom': ['margin'],
1091
+ 'margin-left': ['margin'],
1092
+ 'padding-top': ['padding'],
1093
+ 'padding-right': ['padding'],
1094
+ 'padding-bottom': ['padding'],
1095
+ 'padding-left': ['padding'],
1096
+ 'font-style': ['font'],
1097
+ 'font-variant': ['font'],
1098
+ 'font-weight': ['font'],
1099
+ 'font-size': ['font'],
1100
+ 'font-family': ['font'],
1101
+ 'list-style-type': ['list-style'],
1102
+ 'list-style-position': ['list-style'],
1103
+ 'list-style-image': ['list-style']
1104
+ };
1105
+
1106
+ CSSOCompressor.prototype.needless = function(name, props, pre, imp, v, d, freeze) {
1107
+ var hack = name.charAt(0);
1108
+ if (hack === '*' || hack === '_') name = name.substr(1);
1109
+ else if (hack === '/' && name.charAt(1) === '/') {
1110
+ hack = '//';
1111
+ name = name.substr(2);
1112
+ } else hack = '';
1113
+
1114
+ var vendor = name.charAt(0), i;
1115
+ if (vendor === '-') {
1116
+ if ((i = name.indexOf('-', 2)) !== -1) vendor = name.substr(0, i + 1);
1117
+ } else vendor = '';
1118
+
1119
+ var prop = name.substr(vendor.length),
1120
+ x, t, ppre;
1121
+ if (prop in this.nlTable) {
1122
+ x = this.nlTable[prop];
1123
+ for (i = 0; i < x.length; i++) {
1124
+ ppre = this.buildPPre(pre, hack + vendor + x[i], v, d, freeze);
1125
+ if (t = props[ppre]) return (!imp || t.imp);
1126
+ }
1127
+ }
1128
+ };
1129
+
1130
+ CSSOCompressor.prototype.rejoinRuleset = function(token, rule, container, i) {
1131
+ var p = (i === 2 || container[i - 1][1] === 'unknown') ? null : container[i - 1],
1132
+ ps = p ? p[2].slice(2) : [],
1133
+ pb = p ? p[3].slice(2) : [],
1134
+ ts = token[2].slice(2),
1135
+ tb = token[3].slice(2),
1136
+ ph, th, r;
1137
+
1138
+ if (!tb.length) return null;
1139
+
1140
+ if (ps.length && pb.length) {
1141
+ // try to join by selectors
1142
+ ph = this.getHash(ps);
1143
+ th = this.getHash(ts);
1144
+ if (this.equalHash(th, ph)) {
1145
+ p[3] = p[3].concat(token[3].splice(2));
1146
+ return null;
1147
+ }
1148
+ if (this.okToJoinByProperties(token, p)) {
1149
+ // try to join by properties
1150
+ r = this.analyze(token, p);
1151
+ if (!r.ne1.length && !r.ne2.length) {
1152
+ p[2] = this.cleanSelector(p[2].concat(token[2].splice(2)));
1153
+ p[2][0].s = translator.translate(cleanInfo(p[2]));
1154
+ return null;
1155
+ }
1156
+ }
1157
+ }
1158
+ };
1159
+
1160
+ CSSOCompressor.prototype.okToJoinByProperties = function(r0, r1) {
1161
+ var i0 = r0[0], i1 = r1[0];
1162
+
1163
+ // same frozen ruleset
1164
+ if (i0.freezeID === i1.freezeID) return true;
1165
+
1166
+ // same pseudo-classes in selectors
1167
+ if (i0.pseudoID === i1.pseudoID) return true;
1168
+
1169
+ // different frozen rulesets
1170
+ if (i0.freeze && i1.freeze) {
1171
+ return this.pseudoSelectorSignature(r0[2], this.allowedPClasses) === this.pseudoSelectorSignature(r1[2], this.allowedPClasses);
1172
+ }
1173
+
1174
+ // is it frozen at all?
1175
+ return !(i0.freeze || i1.freeze);
1176
+ };
1177
+
1178
+ CSSOCompressor.prototype.allowedPClasses = {
1179
+ 'after': 1,
1180
+ 'before': 1
1181
+ };
1182
+
1183
+ CSSOCompressor.prototype.containsOnlyAllowedPClasses = function(selector) {
1184
+ var ss;
1185
+ for (var i = 2; i < selector.length; i++) {
1186
+ ss = selector[i];
1187
+ for (var j = 2; j < ss.length; j++) {
1188
+ if (ss[j][1] == 'pseudoc' || ss[j][1] == 'pseudoe') {
1189
+ if (!(ss[j][2][2] in this.allowedPClasses)) return false;
1190
+ }
1191
+ }
1192
+ }
1193
+ return true;
1194
+ };
1195
+
1196
+ CSSOCompressor.prototype.restructureRuleset = function(token, rule, container, i) {
1197
+ var p = (i === 2 || container[i - 1][1] === 'unknown') ? null : container[i - 1],
1198
+ ps = p ? p[2].slice(2) : [],
1199
+ pb = p ? p[3].slice(2) : [],
1200
+ tb = token[3].slice(2),
1201
+ r, nr;
1202
+
1203
+ if (!tb.length) return null;
1204
+
1205
+ if (ps.length && pb.length) {
1206
+ // try to join by properties
1207
+ r = this.analyze(token, p);
1208
+ if (r.eq.length && (r.ne1.length || r.ne2.length)) {
1209
+ if (r.ne1.length && !r.ne2.length) { // p in token
1210
+ var ns = token[2].slice(2), // TODO: copypaste
1211
+ nss = translator.translate(cleanInfo(token[2])),
1212
+ sl = nss.length + // selector length
1213
+ ns.length - 1, // delims length
1214
+ bl = this.calcLength(r.eq) + // declarations length
1215
+ r.eq.length - 1; // decldelims length
1216
+ if (sl < bl) {
1217
+ p[2] = this.cleanSelector(p[2].concat(token[2].slice(2)));
1218
+ token[3].splice(2);
1219
+ token[3] = token[3].concat(r.ne1);
1220
+ return token;
1221
+ }
1222
+ } else if (r.ne2.length && !r.ne1.length) { // token in p
1223
+ var ns = p[2].slice(2),
1224
+ nss = translator.translate(cleanInfo(p[2])),
1225
+ sl = nss.length + // selector length
1226
+ ns.length - 1, // delims length
1227
+ bl = this.calcLength(r.eq) + // declarations length
1228
+ r.eq.length - 1; // decldelims length
1229
+ if (sl < bl) {
1230
+ token[2] = this.cleanSelector(p[2].concat(token[2].slice(2)));
1231
+ p[3].splice(2);
1232
+ p[3] = p[3].concat(r.ne2);
1233
+ return token;
1234
+ }
1235
+ } else { // extract equal block?
1236
+ var ns = this.cleanSelector(p[2].concat(token[2].slice(2))),
1237
+ nss = translator.translate(cleanInfo(ns)),
1238
+ rl = nss.length + // selector length
1239
+ ns.length - 1 + // delims length
1240
+ 2, // braces length
1241
+ bl = this.calcLength(r.eq) + // declarations length
1242
+ r.eq.length - 1; // decldelims length
1243
+
1244
+ if (bl >= rl) { // ok, it's good enough to extract
1245
+ ns[0].s = nss;
1246
+ nr = [{f:0, l:0}, 'ruleset', ns, [{f:0,l:0}, 'block'].concat(r.eq)];
1247
+ token[3].splice(2);
1248
+ token[3] = token[3].concat(r.ne1);
1249
+ p[3].splice(2);
1250
+ p[3] = p[3].concat(r.ne2);
1251
+ container.splice(i, 0, nr);
1252
+ return nr;
1253
+ }
1254
+ }
1255
+ }
1256
+ }
1257
+ };
1258
+
1259
+ CSSOCompressor.prototype.calcLength = function(tokens) {
1260
+ var r = 0;
1261
+ for (var i = 0; i < tokens.length; i++) r += tokens[i][0].s.length;
1262
+ return r;
1263
+ };
1264
+
1265
+ CSSOCompressor.prototype.cleanSelector = function(token) {
1266
+ if (token.length === 2) return null;
1267
+ var h = {}, s;
1268
+ for (var i = 2; i < token.length; i++) {
1269
+ s = token[i][0].s;
1270
+ if (s in h) token.splice(i, 1), i--;
1271
+ else h[s] = 1;
1272
+ }
1273
+
1274
+ return token;
1275
+ };
1276
+
1277
+ CSSOCompressor.prototype.analyze = function(r1, r2) {
1278
+ var b1 = r1[3], b2 = r2[3],
1279
+ d1 = b1.slice(2), d2 = b2.slice(2),
1280
+ r = { eq: [], ne1: [], ne2: [] },
1281
+ h1, h2, i, x;
1282
+
1283
+ h1 = this.getHash(d1);
1284
+ h2 = this.getHash(d2);
1285
+
1286
+ for (i = 0; i < d1.length; i++) {
1287
+ x = d1[i];
1288
+ if (x[0].s in h2) r.eq.push(x);
1289
+ else r.ne1.push(x);
1290
+ }
1291
+
1292
+ for (i = 0; i < d2.length; i++) {
1293
+ x = d2[i];
1294
+ if (!(x[0].s in h1)) r.ne2.push(x);
1295
+ }
1296
+
1297
+ return r;
1298
+ };
1299
+
1300
+ CSSOCompressor.prototype.equalHash = function(h0, h1) {
1301
+ var k;
1302
+ for (k in h0) if (!(k in h1)) return false;
1303
+ for (k in h1) if (!(k in h0)) return false;
1304
+ return true;
1305
+ };
1306
+
1307
+ CSSOCompressor.prototype.getHash = function(tokens) {
1308
+ var r = {};
1309
+ for (var i = 0; i < tokens.length; i++) r[tokens[i][0].s] = 1;
1310
+ return r;
1311
+ };
1312
+
1313
+ CSSOCompressor.prototype.hashInHash = function(h0, h1) {
1314
+ for (var k in h0) if (!(k in h1)) return false;
1315
+ return true;
1316
+ };
1317
+
1318
+ CSSOCompressor.prototype.delimSelectors = function(token) {
1319
+ for (var i = token.length - 1; i > 2; i--) {
1320
+ token.splice(i, 0, [{}, 'delim']);
1321
+ }
1322
+ };
1323
+
1324
+ CSSOCompressor.prototype.delimBlocks = function(token) {
1325
+ for (var i = token.length - 1; i > 2; i--) {
1326
+ token.splice(i, 0, [{}, 'decldelim']);
1327
+ }
1328
+ };
1329
+
1330
+ CSSOCompressor.prototype.copyArray = function(a) {
1331
+ var r = [], t;
1332
+
1333
+ for (var i = 0; i < a.length; i++) {
1334
+ t = a[i];
1335
+ if (Array.isArray(t)) r.push(this.copyArray(t));
1336
+ else if (typeof t === 'object') r.push(this.copyObject(t));
1337
+ else r.push(t);
1338
+ }
1339
+
1340
+ return r;
1341
+ };
1342
+
1343
+ CSSOCompressor.prototype.copyObject = function(o) {
1344
+ var r = {};
1345
+ for (var k in o) r[k] = o[k];
1346
+ return r;
1347
+ };
1348
+
1349
+ CSSOCompressor.prototype.pathUp = function(path) {
1350
+ return path.substr(0, path.lastIndexOf('/'));
1351
+ };
1352
+ var translator = require('./translator.js').translator(),
1353
+ cleanInfo = require('./util.js').cleanInfo;
1354
+
1355
+ exports.compress = function(tree, ro) {
1356
+ return new CSSOCompressor().compress(tree, ro);
1357
+ };