cssminify 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/CHANGES.md +3 -0
- data/Gemfile +4 -0
- data/LICENSE.md +22 -0
- data/README.md +60 -0
- data/Rakefile +1 -0
- data/cssminify.gemspec +32 -0
- data/lib/cssminify.rb +34 -0
- data/lib/cssminify/cssmin.rb +379 -0
- data/lib/cssminify/version.rb +5 -0
- data/spec/cssminify_spec.rb +257 -0
- data/spec/sample.css +100 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/tests/background-position.css +2 -0
- data/spec/tests/background-position.css.min +1 -0
- data/spec/tests/border-none.css +5 -0
- data/spec/tests/border-none.css.min +1 -0
- data/spec/tests/box-model-hack.css +9 -0
- data/spec/tests/box-model-hack.css.min +1 -0
- data/spec/tests/bug2527974.css +10 -0
- data/spec/tests/bug2527974.css.min +1 -0
- data/spec/tests/bug2527991.css +19 -0
- data/spec/tests/bug2527991.css.min +1 -0
- data/spec/tests/bug2527998.css +4 -0
- data/spec/tests/bug2527998.css.min +1 -0
- data/spec/tests/bug2528034.css +5 -0
- data/spec/tests/bug2528034.css.min +1 -0
- data/spec/tests/charset-media.css +9 -0
- data/spec/tests/charset-media.css.min +1 -0
- data/spec/tests/color-simple.css +8 -0
- data/spec/tests/color-simple.css.min +1 -0
- data/spec/tests/color.css +46 -0
- data/spec/tests/color.css.min +1 -0
- data/spec/tests/comment.css +3 -0
- data/spec/tests/comment.css.min +1 -0
- data/spec/tests/concat-charset.css +15 -0
- data/spec/tests/concat-charset.css.min +1 -0
- data/spec/tests/dataurl-base64-doublequotes.css +23 -0
- data/spec/tests/dataurl-base64-doublequotes.css.min +1 -0
- data/spec/tests/dataurl-base64-eof.css +10 -0
- data/spec/tests/dataurl-base64-eof.css.min +1 -0
- data/spec/tests/dataurl-base64-linebreakindata.css +34 -0
- data/spec/tests/dataurl-base64-linebreakindata.css.min +1 -0
- data/spec/tests/dataurl-base64-noquotes.css +26 -0
- data/spec/tests/dataurl-base64-noquotes.css.min +1 -0
- data/spec/tests/dataurl-base64-singlequotes.css +23 -0
- data/spec/tests/dataurl-base64-singlequotes.css.min +1 -0
- data/spec/tests/dataurl-base64-twourls.css +27 -0
- data/spec/tests/dataurl-base64-twourls.css.min +1 -0
- data/spec/tests/dataurl-dbquote-font.css +30 -0
- data/spec/tests/dataurl-dbquote-font.css.min +5 -0
- data/spec/tests/dataurl-nonbase64-doublequotes.css +13 -0
- data/spec/tests/dataurl-nonbase64-doublequotes.css.min +1 -0
- data/spec/tests/dataurl-nonbase64-noquotes.css +11 -0
- data/spec/tests/dataurl-nonbase64-noquotes.css.min +1 -0
- data/spec/tests/dataurl-nonbase64-singlequotes.css +15 -0
- data/spec/tests/dataurl-nonbase64-singlequotes.css.min +2 -0
- data/spec/tests/dataurl-noquote-multiline-font.css +31 -0
- data/spec/tests/dataurl-noquote-multiline-font.css.min +3 -0
- data/spec/tests/dataurl-realdata-doublequotes.css +90 -0
- data/spec/tests/dataurl-realdata-doublequotes.css.min +1 -0
- data/spec/tests/dataurl-realdata-noquotes.css +90 -0
- data/spec/tests/dataurl-realdata-noquotes.css.min +1 -0
- data/spec/tests/dataurl-realdata-singlequotes.css +90 -0
- data/spec/tests/dataurl-realdata-singlequotes.css.min +1 -0
- data/spec/tests/dataurl-realdata-yuiapp.css +106 -0
- data/spec/tests/dataurl-realdata-yuiapp.css.min +1 -0
- data/spec/tests/dataurl-singlequote-font.css +30 -0
- data/spec/tests/dataurl-singlequote-font.css.min +3 -0
- data/spec/tests/decimals.css +3 -0
- data/spec/tests/decimals.css.min +1 -0
- data/spec/tests/dollar-header.css +7 -0
- data/spec/tests/dollar-header.css.min +3 -0
- data/spec/tests/font-face.css +6 -0
- data/spec/tests/font-face.css.min +1 -0
- data/spec/tests/ie5mac.css +5 -0
- data/spec/tests/ie5mac.css.min +1 -0
- data/spec/tests/media-empty-class.css +16 -0
- data/spec/tests/media-empty-class.css.min +1 -0
- data/spec/tests/media-multi.css +3 -0
- data/spec/tests/media-multi.css.min +1 -0
- data/spec/tests/media-test.css +3 -0
- data/spec/tests/media-test.css.min +1 -0
- data/spec/tests/opacity-filter.css +14 -0
- data/spec/tests/opacity-filter.css.min +1 -0
- data/spec/tests/preserve-case.css +15 -0
- data/spec/tests/preserve-case.css.min +1 -0
- data/spec/tests/preserve-new-line.css +6 -0
- data/spec/tests/preserve-new-line.css.min +3 -0
- data/spec/tests/preserve-strings.css +7 -0
- data/spec/tests/preserve-strings.css.min +1 -0
- data/spec/tests/pseudo-first.css +16 -0
- data/spec/tests/pseudo-first.css.min +1 -0
- data/spec/tests/pseudo.css +4 -0
- data/spec/tests/pseudo.css.min +1 -0
- data/spec/tests/special-comments.css +13 -0
- data/spec/tests/special-comments.css.min +9 -0
- data/spec/tests/star-underscore-hacks.css +5 -0
- data/spec/tests/star-underscore-hacks.css.min +1 -0
- data/spec/tests/string-in-comment.css +8 -0
- data/spec/tests/string-in-comment.css.min +1 -0
- data/spec/tests/webkit-transform.css +2 -0
- data/spec/tests/webkit-transform.css.min +1 -0
- data/spec/tests/zeros.css +6 -0
- data/spec/tests/zeros.css.min +1 -0
- metadata +275 -0
data/.gitignore
ADDED
data/CHANGES.md
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Matthias Siegel (matthias.siegel@gmail.com)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
Note: the included Ruby port of YUI compressor has a different license. See the [source code](https://github.com/matthiassiegel/cssminify/blob/master/lib/cssminify/cssmin.rb) for details.
|
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# CSSminify
|
2
|
+
CSS minification with YUI compressor, but as native Ruby port.
|
3
|
+
|
4
|
+
The CSSminify gem provides CSS compression using YUI compressor. Instead of wrapping around the Java or Javascript version of YUI compressor it uses a native [Ruby port](https://github.com/matthiassiegel/cssmin) of the CSS engine. Therefore this gem has no dependencies except Bundler.
|
5
|
+
|
6
|
+
In basic benchmarks the Ruby version performed about as good as the Java jar. It currently passes all CSS test cases included with the YUI compressor Java source code.
|
7
|
+
|
8
|
+
Main motivation for the Ruby port and this gem was to reduce dependencies like Java.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
Install CSSminify from RubyGems:
|
12
|
+
|
13
|
+
gem install cssminify
|
14
|
+
|
15
|
+
Or include it in your project's Gemfile:
|
16
|
+
|
17
|
+
gem 'cssminify'
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
require 'cssminify'
|
22
|
+
|
23
|
+
CSSminify.compress('/* a comment */ .test { display: block; }')
|
24
|
+
# => minified CSS
|
25
|
+
|
26
|
+
CSSminify.compress(File.read('path/to/styles.css'))
|
27
|
+
# => minified CSS
|
28
|
+
|
29
|
+
CSSminify.compress(File.open('path/to/styles.css'))
|
30
|
+
# => minified CSS
|
31
|
+
|
32
|
+
# Alternatively use instance method...
|
33
|
+
compressor = CSSminify.new
|
34
|
+
compressor.compress(File.read("path/to/styles.css"))
|
35
|
+
# => minified CSS
|
36
|
+
|
37
|
+
Files or strings are acceptable as input.
|
38
|
+
|
39
|
+
You can pass in a second argument to control the maximum output line length (default 5000 characters):
|
40
|
+
|
41
|
+
CSSminify.compress(File.read("path/to/styles.css"), 200)
|
42
|
+
|
43
|
+
Note: in most cases line length will only be approximated.
|
44
|
+
|
45
|
+
## Rails asset pipeline
|
46
|
+
Rails 3.1 integrated [Sprockets](https://github.com/sstephenson/sprockets) to provide asset packaging and minimising out of the box. For CSS compression it relies on the [yui-compressor gem](https://github.com/sstephenson/ruby-yui-compressor) which requires Java. To use CSSminify instead, edit your config/application.rb file:
|
47
|
+
|
48
|
+
config.assets.css_compressor = CSSminify.new
|
49
|
+
|
50
|
+
## Changelog
|
51
|
+
See [CHANGES](https://github.com/matthiassiegel/cssminify/blob/master/CHANGES.md).
|
52
|
+
|
53
|
+
## Copyright
|
54
|
+
|
55
|
+
### CSSminify gem and documentation
|
56
|
+
Copyright (c) 2012 Matthias Siegel (matthias.siegel@gmail.com)
|
57
|
+
See [LICENSE](https://github.com/matthiassiegel/cssminify/blob/master/LICENSE.md) for details.
|
58
|
+
|
59
|
+
### YUI compressor
|
60
|
+
See [file](https://github.com/matthiassiegel/cssminify/blob/master/lib/cssminify/cssmin.rb).
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/cssminify.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "cssminify/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "cssminify"
|
7
|
+
s.version = CSSminify::VERSION
|
8
|
+
s.author = "Matthias Siegel"
|
9
|
+
s.email = "matthias.siegel@gmail.com"
|
10
|
+
s.homepage = "https://github.com/matthiassiegel/cssminify"
|
11
|
+
s.summary = "CSS minification with YUI compressor, but as native Ruby port."
|
12
|
+
s.description = <<-EOF
|
13
|
+
The CSSminify gem provides CSS compression using YUI compressor. Instead of wrapping around the Java or Javascript version of YUI compressor it uses a native Ruby port of the CSS engine. Therefore this gem has no dependencies except Bundler.
|
14
|
+
EOF
|
15
|
+
|
16
|
+
s.extra_rdoc_files = [
|
17
|
+
"CHANGES.md",
|
18
|
+
"LICENSE.md",
|
19
|
+
"README.md"
|
20
|
+
]
|
21
|
+
|
22
|
+
s.license = "MIT"
|
23
|
+
s.rubyforge_project = "cssminify"
|
24
|
+
|
25
|
+
s.files = `git ls-files`.split("\n")
|
26
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
27
|
+
s.require_paths = ["lib"]
|
28
|
+
|
29
|
+
s.add_development_dependency "rspec"
|
30
|
+
|
31
|
+
s.add_runtime_dependency "bundler", "~> 1.0"
|
32
|
+
end
|
data/lib/cssminify.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require "cssminify/cssmin"
|
4
|
+
require "cssminify/version"
|
5
|
+
|
6
|
+
class CSSminify
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
#
|
13
|
+
# Compress CSS with YUI
|
14
|
+
#
|
15
|
+
# @param [String, #read] CSS String or IO-like object that supports #read
|
16
|
+
# @param [Integer] length Maximum line length
|
17
|
+
# @return [String] Compressed CSS
|
18
|
+
def self.compress(source, length = 5000)
|
19
|
+
self.new.compress(source, length)
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
#
|
24
|
+
# Compress CSS with YUI
|
25
|
+
#
|
26
|
+
# @param [String, #read] CSS String or IO-like object that supports #read
|
27
|
+
# @param [Integer] length Maximum line length
|
28
|
+
# @return [String] Compressed CSS
|
29
|
+
def compress(source = '', length = 5000)
|
30
|
+
source = source.respond_to?(:read) ? source.read : source.to_s
|
31
|
+
|
32
|
+
CssCompressor.compress(source, length)
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,379 @@
|
|
1
|
+
#
|
2
|
+
# cssmin.rb - 1.0.0
|
3
|
+
# Author: Matthias Siegel - https://github.com/matthiassiegel/cssmin
|
4
|
+
# This is a Ruby port of the CSS minification tool
|
5
|
+
# distributed with YUICompressor, based on the original Java
|
6
|
+
# code and the Javascript port by Stoyan Stefanov.
|
7
|
+
# Permission is hereby granted to use the Ruby version under the same
|
8
|
+
# conditions as YUICompressor (original YUICompressor note below).
|
9
|
+
#
|
10
|
+
#
|
11
|
+
# YUI Compressor
|
12
|
+
# http://developer.yahoo.com/yui/compressor/
|
13
|
+
# Author: Julien Lecomte - http://www.julienlecomte.net/
|
14
|
+
# Author: Isaac Schlueter - http://foohack.com/
|
15
|
+
# Author: Stoyan Stefanov - http://phpied.com/
|
16
|
+
# Copyright (c) 2011 Yahoo! Inc. All rights reserved.
|
17
|
+
# The copyrights embodied in the content of this file are licensed
|
18
|
+
# by Yahoo! Inc. under the BSD (revised) open source license.
|
19
|
+
#
|
20
|
+
|
21
|
+
module CssCompressor
|
22
|
+
|
23
|
+
def self.compress(css, linebreakpos = 5000)
|
24
|
+
|
25
|
+
#
|
26
|
+
# Support for various input types
|
27
|
+
#
|
28
|
+
if css.respond_to?(:read)
|
29
|
+
css = css.read
|
30
|
+
elsif css.respond_to?(:path)
|
31
|
+
css = File.read(css.path)
|
32
|
+
end
|
33
|
+
|
34
|
+
@@preservedTokens = []
|
35
|
+
comments = []
|
36
|
+
startIndex = 0
|
37
|
+
|
38
|
+
#
|
39
|
+
# Extract data urls
|
40
|
+
#
|
41
|
+
css = extractDataUrls(css)
|
42
|
+
|
43
|
+
#
|
44
|
+
# Collect all comment blocks...
|
45
|
+
#
|
46
|
+
while (startIndex = css.index(/\/\*/, startIndex)) != nil
|
47
|
+
endIndex = css.index(/\*\//, startIndex + 2)
|
48
|
+
endIndex = css.length if endIndex.nil?
|
49
|
+
|
50
|
+
comments << css[(startIndex + 2)...endIndex]
|
51
|
+
|
52
|
+
css = css[0...(startIndex + 2)] + '___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_' + (comments.length - 1).to_s + '___' + css[endIndex..-1]
|
53
|
+
startIndex += 2
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Preserve strings so their content doesn't get accidentally minified
|
58
|
+
#
|
59
|
+
css = css.gsub(/("([^\\"]|\\.|\\)*")|('([^\\']|\\.|\\)*')/) { |match|
|
60
|
+
quote = match[0, 1]
|
61
|
+
match = match[1...-1]
|
62
|
+
|
63
|
+
#
|
64
|
+
# Maybe the string contains a comment-like substring?
|
65
|
+
# One, maybe more? put'em back then
|
66
|
+
#
|
67
|
+
unless match.index('___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_').nil?
|
68
|
+
comments.each_index { |i| match = match.gsub(/___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_#{i}___/, comments[i]) }
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# Minify alpha opacity in filter strings
|
73
|
+
#
|
74
|
+
match = match.gsub(/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/i, 'alpha(opacity=')
|
75
|
+
|
76
|
+
@@preservedTokens << match
|
77
|
+
|
78
|
+
quote + "___YUICSSMIN_PRESERVED_TOKEN_" + (@@preservedTokens.length - 1).to_s + "___" + quote
|
79
|
+
}
|
80
|
+
|
81
|
+
#
|
82
|
+
# Strings are safe, now wrestle the comments
|
83
|
+
#
|
84
|
+
for i in 0...comments.length
|
85
|
+
token = comments[i]
|
86
|
+
placeholder = /___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_#{i}___/
|
87
|
+
|
88
|
+
#
|
89
|
+
# ! in the first position of the comment means preserve
|
90
|
+
# so push to the preserved tokens keeping the !
|
91
|
+
#
|
92
|
+
if token[0] === '!'
|
93
|
+
@@preservedTokens << token
|
94
|
+
css = css.gsub(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (@@preservedTokens.length - 1).to_s + "___")
|
95
|
+
next
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# \ in the last position looks like hack for Mac/IE5
|
100
|
+
# shorten that to /*\*/ and the next one to /**/
|
101
|
+
#
|
102
|
+
if token[(token.length - 1)..-1] === '\\'
|
103
|
+
@@preservedTokens << '\\'
|
104
|
+
css = css.gsub(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (@@preservedTokens.length - 1).to_s + "___")
|
105
|
+
|
106
|
+
i += 1 # Attn: advancing the loop
|
107
|
+
|
108
|
+
@@preservedTokens << ''
|
109
|
+
css = css.gsub(/___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_#{i}___/, "___YUICSSMIN_PRESERVED_TOKEN_" + (@@preservedTokens.length - 1).to_s + "___")
|
110
|
+
next
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# Keep empty comments after child selectors (IE7 hack)
|
115
|
+
# E.g. html >/**/ body
|
116
|
+
#
|
117
|
+
if token.length == 0
|
118
|
+
startIndex = css.index(placeholder)
|
119
|
+
|
120
|
+
if startIndex and startIndex > 2
|
121
|
+
if css[startIndex - 3] === '>'
|
122
|
+
@@preservedTokens << ''
|
123
|
+
css = css.gsub(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (@@preservedTokens.length - 1).to_s + "___")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
# In all other cases kill the comment
|
130
|
+
#
|
131
|
+
css = css.gsub(/\/\*___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_#{i}___\*\//, '')
|
132
|
+
end
|
133
|
+
|
134
|
+
#
|
135
|
+
# Shorten all whitespace strings to single spaces
|
136
|
+
#
|
137
|
+
css = css.gsub(/\s+/, ' ')
|
138
|
+
|
139
|
+
#
|
140
|
+
# Remove the spaces before the things that should not have spaces before them.
|
141
|
+
# But, be careful not to turn "p :link {...}" into "p:link{...}"
|
142
|
+
# Swap out any pseudo-class colons with the token, and then swap back.
|
143
|
+
#
|
144
|
+
css = css.gsub(/(^|\})(([^\{:])+:)+([^\{]*\{)/) { |m| m.gsub(/:/, '___YUICSSMIN_PSEUDOCLASSCOLON___') }
|
145
|
+
css = css.gsub(/\s+([!{};:>+\(\)\],])/) { $1.to_s }
|
146
|
+
css = css.gsub(/___YUICSSMIN_PSEUDOCLASSCOLON___/, ':')
|
147
|
+
|
148
|
+
#
|
149
|
+
# Retain space for special IE6 cases
|
150
|
+
#
|
151
|
+
css = css.gsub(/:first-(line|letter)(\{|,)/) { ":first-#{$1.to_s} #{$2.to_s}" }
|
152
|
+
|
153
|
+
#
|
154
|
+
# No space after the end of a preserved comment
|
155
|
+
#
|
156
|
+
css = css.gsub(/\*\/\s+/, '*/')
|
157
|
+
|
158
|
+
#
|
159
|
+
# If there is a @charset, then only allow one, and push to the top of the file.
|
160
|
+
#
|
161
|
+
css = css.gsub(/^(.*)(@charset "[^"]*";)/i) { $2.to_s + $1.to_s }
|
162
|
+
css = css.gsub(/^(\s*@charset [^;]+;\s*)+/i) { $1.to_s }
|
163
|
+
|
164
|
+
#
|
165
|
+
# Put the space back in some cases, to support stuff like
|
166
|
+
# @media screen and (-webkit-min-device-pixel-ratio:0){
|
167
|
+
#
|
168
|
+
css = css.gsub(/\band\(/i, 'and (')
|
169
|
+
|
170
|
+
#
|
171
|
+
# Remove the spaces after the things that should not have spaces after them.
|
172
|
+
#
|
173
|
+
css = css.gsub(/([!{}:;>+\(\[,])\s+/) { $1.to_s }
|
174
|
+
|
175
|
+
#
|
176
|
+
# Remove unnecessary semicolons
|
177
|
+
#
|
178
|
+
css = css.gsub(/;+\}/, '}')
|
179
|
+
|
180
|
+
#
|
181
|
+
# Replace 0(px,em,%) with 0
|
182
|
+
#
|
183
|
+
css = css.gsub(/([\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)/i) { $1.to_s + $2.to_s }
|
184
|
+
|
185
|
+
#
|
186
|
+
# Replace 0 0 0 0; with 0
|
187
|
+
#
|
188
|
+
css = css.gsub(/:0 0 0 0(;|\})/) { ':0' + $1.to_s }
|
189
|
+
css = css.gsub(/:0 0 0(;|\})/) { ':0' + $1.to_s }
|
190
|
+
css = css.gsub(/:0 0(;|\})/) { ':0' + $1.to_s }
|
191
|
+
|
192
|
+
#
|
193
|
+
# Replace background-position:0; with background-position:0 0;
|
194
|
+
# Same for transform-origin
|
195
|
+
#
|
196
|
+
css = css.gsub(/(background-position|transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin):0(;|\})/i) { "#{$1.to_s.downcase}:0 0#{$2.to_s}" }
|
197
|
+
|
198
|
+
#
|
199
|
+
# Replace 0.6 to .6, but only when preceded by : or a white-space
|
200
|
+
#
|
201
|
+
css = css.gsub(/(:|\s)0+\.(\d+)/) { "#{$1.to_s}.#{$2.to_s}" }
|
202
|
+
|
203
|
+
#
|
204
|
+
# Shorten colors from rgb(51,102,153) to #336699
|
205
|
+
# This makes it more likely that it'll get further compressed in the next step.
|
206
|
+
#
|
207
|
+
css = css.gsub(/rgb\s*\(\s*([0-9,\s]+)\s*\)/i) {
|
208
|
+
rgbcolors = $1.to_s.split(',')
|
209
|
+
i = 0
|
210
|
+
|
211
|
+
while i < rgbcolors.length
|
212
|
+
rgbcolors[i] = rgbcolors[i].to_i.to_s(16)
|
213
|
+
|
214
|
+
if rgbcolors[i].length === 1
|
215
|
+
rgbcolors[i] = '0' + rgbcolors[i]
|
216
|
+
end
|
217
|
+
|
218
|
+
i += 1
|
219
|
+
end
|
220
|
+
|
221
|
+
'#' + rgbcolors.join('')
|
222
|
+
}
|
223
|
+
|
224
|
+
#
|
225
|
+
# Shorten colors from #AABBCC to #ABC. Note that we want to make sure
|
226
|
+
# the color is not preceded by either ", " or =. Indeed, the property
|
227
|
+
# filter: chroma(color="#FFFFFF");
|
228
|
+
# would become
|
229
|
+
# filter: chroma(color="#FFF");
|
230
|
+
# which makes the filter break in IE.
|
231
|
+
# We also want to make sure we're only compressing #AABBCC patterns inside { }, not id selectors ( #FAABAC {} )
|
232
|
+
# We also want to avoid compressing invalid values (e.g. #AABBCCD to #ABCD)
|
233
|
+
#
|
234
|
+
rest = css
|
235
|
+
new_css = ''
|
236
|
+
|
237
|
+
while m = rest.match(/(\=\s*?["']?)?#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])(:?\}|[^0-9a-fA-F{][^{]*?\})/i) do
|
238
|
+
#
|
239
|
+
# Normal color value
|
240
|
+
#
|
241
|
+
if m[1].nil?
|
242
|
+
#
|
243
|
+
# Color value that can be shortened
|
244
|
+
#
|
245
|
+
if m[2].to_s.downcase == m[3].to_s.downcase && m[4].to_s.downcase == m[5].to_s.downcase && m[6].to_s.downcase == m[7].to_s.downcase
|
246
|
+
color = '#' + m[2].to_s.downcase + m[4].to_s.downcase + m[6].to_s.downcase
|
247
|
+
shift = 7
|
248
|
+
|
249
|
+
#
|
250
|
+
# Color value that can't be shortened
|
251
|
+
#
|
252
|
+
else
|
253
|
+
color = '#' + m[2].to_s.downcase + m[3].to_s.downcase + m[4].to_s.downcase + m[5].to_s.downcase + m[6].to_s.downcase + m[7].to_s.downcase
|
254
|
+
shift = 7
|
255
|
+
end
|
256
|
+
|
257
|
+
#
|
258
|
+
# Filter property that needs to stay the way it is
|
259
|
+
#
|
260
|
+
else
|
261
|
+
color = m[1].to_s + '#' + m[2].to_s + m[3].to_s + m[4].to_s + m[5].to_s + m[6].to_s + m[7].to_s
|
262
|
+
shift = color.length
|
263
|
+
end
|
264
|
+
|
265
|
+
new_css += m.pre_match + color
|
266
|
+
rest.slice!(rest.index(m.pre_match || 0), m.pre_match.length + shift)
|
267
|
+
|
268
|
+
end
|
269
|
+
css = new_css + rest
|
270
|
+
|
271
|
+
#
|
272
|
+
# border: none -> border:0
|
273
|
+
#
|
274
|
+
css = css.gsub(/(border|border-top|border-right|border-bottom|border-right|outline|background):none(;|\})/i) { "#{$1.to_s.downcase}:0#{$2.to_s}" }
|
275
|
+
|
276
|
+
#
|
277
|
+
# Shorter opacity IE filter
|
278
|
+
#
|
279
|
+
css = css.gsub(/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/i, 'alpha(opacity=')
|
280
|
+
|
281
|
+
#
|
282
|
+
# Remove empty rules.
|
283
|
+
#
|
284
|
+
css = css.gsub(/[^\};\{\/]+\{\}/, '')
|
285
|
+
|
286
|
+
#
|
287
|
+
# Some source control tools don't like it when files containing lines longer
|
288
|
+
# than, say 8000 characters, are checked in. The linebreak option is used in
|
289
|
+
# that case to split long lines after a specific column.
|
290
|
+
#
|
291
|
+
if linebreakpos
|
292
|
+
startIndex = 0
|
293
|
+
i = 0
|
294
|
+
while i < css.length
|
295
|
+
i = i + 1
|
296
|
+
if css[i - 1] === '}' && i - startIndex > linebreakpos
|
297
|
+
css = css.slice(0, i) + "\n" + css.slice(i, css.length - i)
|
298
|
+
startIndex = i
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
#
|
304
|
+
# Replace multiple semi-colons in a row by a single one
|
305
|
+
#
|
306
|
+
css = css.gsub(/;;+/, ';')
|
307
|
+
|
308
|
+
#
|
309
|
+
# Restore preserved comments and strings
|
310
|
+
#
|
311
|
+
for i in 0...@@preservedTokens.length do
|
312
|
+
css = css.gsub(/___YUICSSMIN_PRESERVED_TOKEN_#{i}___/) { @@preservedTokens[i] }
|
313
|
+
end
|
314
|
+
|
315
|
+
#
|
316
|
+
# Remove leading and trailing whitespace
|
317
|
+
#
|
318
|
+
css = css.chomp.strip
|
319
|
+
end
|
320
|
+
|
321
|
+
|
322
|
+
|
323
|
+
|
324
|
+
private
|
325
|
+
|
326
|
+
|
327
|
+
#
|
328
|
+
# Utility method to replace all data urls with tokens before we start
|
329
|
+
# compressing, to avoid performance issues running some of the subsequent
|
330
|
+
# regexes against large strings chunks.
|
331
|
+
#
|
332
|
+
# @param [String] css The input css
|
333
|
+
# @returns [String] The processed css
|
334
|
+
#
|
335
|
+
def self.extractDataUrls(css)
|
336
|
+
new_css = ''
|
337
|
+
|
338
|
+
while m = css.match(/url\(\s*(["']?)data\:/i) do
|
339
|
+
startIndex = m.begin(0) + 4 # "url(".length()
|
340
|
+
terminator = m[1] # ', " or empty (not quoted)
|
341
|
+
terminator = ')' if terminator.empty?
|
342
|
+
foundTerminator = false
|
343
|
+
endIndex = m.end(0) - 1
|
344
|
+
|
345
|
+
while !foundTerminator && endIndex + 1 <= css.length
|
346
|
+
endIndex = css.index(terminator, endIndex + 1)
|
347
|
+
|
348
|
+
#
|
349
|
+
# endIndex == 0 doesn't really apply here
|
350
|
+
#
|
351
|
+
if endIndex > 0 && css[endIndex - 1] != '\\'
|
352
|
+
foundTerminator = true
|
353
|
+
endIndex = css.index(')', endIndex) if terminator != ')'
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
new_css += css[0...m.begin(0)]
|
358
|
+
|
359
|
+
if foundTerminator
|
360
|
+
token = css[startIndex...endIndex]
|
361
|
+
token = token.gsub(/\s+/, '')
|
362
|
+
@@preservedTokens << token
|
363
|
+
|
364
|
+
new_css += "url(___YUICSSMIN_PRESERVED_TOKEN_" + (@@preservedTokens.length - 1).to_s + "___)"
|
365
|
+
|
366
|
+
#
|
367
|
+
# No end terminator found, re-add the whole match
|
368
|
+
#
|
369
|
+
else
|
370
|
+
new_css += css[m.begin(0)...m.end(0)]
|
371
|
+
end
|
372
|
+
|
373
|
+
css = css[(endIndex + 1)..-1]
|
374
|
+
end
|
375
|
+
|
376
|
+
css = new_css + css
|
377
|
+
end
|
378
|
+
|
379
|
+
end
|