csso-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ };