cssminify 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. data/.gitignore +9 -0
  2. data/CHANGES.md +3 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.md +22 -0
  5. data/README.md +60 -0
  6. data/Rakefile +1 -0
  7. data/cssminify.gemspec +32 -0
  8. data/lib/cssminify.rb +34 -0
  9. data/lib/cssminify/cssmin.rb +379 -0
  10. data/lib/cssminify/version.rb +5 -0
  11. data/spec/cssminify_spec.rb +257 -0
  12. data/spec/sample.css +100 -0
  13. data/spec/spec_helper.rb +4 -0
  14. data/spec/tests/background-position.css +2 -0
  15. data/spec/tests/background-position.css.min +1 -0
  16. data/spec/tests/border-none.css +5 -0
  17. data/spec/tests/border-none.css.min +1 -0
  18. data/spec/tests/box-model-hack.css +9 -0
  19. data/spec/tests/box-model-hack.css.min +1 -0
  20. data/spec/tests/bug2527974.css +10 -0
  21. data/spec/tests/bug2527974.css.min +1 -0
  22. data/spec/tests/bug2527991.css +19 -0
  23. data/spec/tests/bug2527991.css.min +1 -0
  24. data/spec/tests/bug2527998.css +4 -0
  25. data/spec/tests/bug2527998.css.min +1 -0
  26. data/spec/tests/bug2528034.css +5 -0
  27. data/spec/tests/bug2528034.css.min +1 -0
  28. data/spec/tests/charset-media.css +9 -0
  29. data/spec/tests/charset-media.css.min +1 -0
  30. data/spec/tests/color-simple.css +8 -0
  31. data/spec/tests/color-simple.css.min +1 -0
  32. data/spec/tests/color.css +46 -0
  33. data/spec/tests/color.css.min +1 -0
  34. data/spec/tests/comment.css +3 -0
  35. data/spec/tests/comment.css.min +1 -0
  36. data/spec/tests/concat-charset.css +15 -0
  37. data/spec/tests/concat-charset.css.min +1 -0
  38. data/spec/tests/dataurl-base64-doublequotes.css +23 -0
  39. data/spec/tests/dataurl-base64-doublequotes.css.min +1 -0
  40. data/spec/tests/dataurl-base64-eof.css +10 -0
  41. data/spec/tests/dataurl-base64-eof.css.min +1 -0
  42. data/spec/tests/dataurl-base64-linebreakindata.css +34 -0
  43. data/spec/tests/dataurl-base64-linebreakindata.css.min +1 -0
  44. data/spec/tests/dataurl-base64-noquotes.css +26 -0
  45. data/spec/tests/dataurl-base64-noquotes.css.min +1 -0
  46. data/spec/tests/dataurl-base64-singlequotes.css +23 -0
  47. data/spec/tests/dataurl-base64-singlequotes.css.min +1 -0
  48. data/spec/tests/dataurl-base64-twourls.css +27 -0
  49. data/spec/tests/dataurl-base64-twourls.css.min +1 -0
  50. data/spec/tests/dataurl-dbquote-font.css +30 -0
  51. data/spec/tests/dataurl-dbquote-font.css.min +5 -0
  52. data/spec/tests/dataurl-nonbase64-doublequotes.css +13 -0
  53. data/spec/tests/dataurl-nonbase64-doublequotes.css.min +1 -0
  54. data/spec/tests/dataurl-nonbase64-noquotes.css +11 -0
  55. data/spec/tests/dataurl-nonbase64-noquotes.css.min +1 -0
  56. data/spec/tests/dataurl-nonbase64-singlequotes.css +15 -0
  57. data/spec/tests/dataurl-nonbase64-singlequotes.css.min +2 -0
  58. data/spec/tests/dataurl-noquote-multiline-font.css +31 -0
  59. data/spec/tests/dataurl-noquote-multiline-font.css.min +3 -0
  60. data/spec/tests/dataurl-realdata-doublequotes.css +90 -0
  61. data/spec/tests/dataurl-realdata-doublequotes.css.min +1 -0
  62. data/spec/tests/dataurl-realdata-noquotes.css +90 -0
  63. data/spec/tests/dataurl-realdata-noquotes.css.min +1 -0
  64. data/spec/tests/dataurl-realdata-singlequotes.css +90 -0
  65. data/spec/tests/dataurl-realdata-singlequotes.css.min +1 -0
  66. data/spec/tests/dataurl-realdata-yuiapp.css +106 -0
  67. data/spec/tests/dataurl-realdata-yuiapp.css.min +1 -0
  68. data/spec/tests/dataurl-singlequote-font.css +30 -0
  69. data/spec/tests/dataurl-singlequote-font.css.min +3 -0
  70. data/spec/tests/decimals.css +3 -0
  71. data/spec/tests/decimals.css.min +1 -0
  72. data/spec/tests/dollar-header.css +7 -0
  73. data/spec/tests/dollar-header.css.min +3 -0
  74. data/spec/tests/font-face.css +6 -0
  75. data/spec/tests/font-face.css.min +1 -0
  76. data/spec/tests/ie5mac.css +5 -0
  77. data/spec/tests/ie5mac.css.min +1 -0
  78. data/spec/tests/media-empty-class.css +16 -0
  79. data/spec/tests/media-empty-class.css.min +1 -0
  80. data/spec/tests/media-multi.css +3 -0
  81. data/spec/tests/media-multi.css.min +1 -0
  82. data/spec/tests/media-test.css +3 -0
  83. data/spec/tests/media-test.css.min +1 -0
  84. data/spec/tests/opacity-filter.css +14 -0
  85. data/spec/tests/opacity-filter.css.min +1 -0
  86. data/spec/tests/preserve-case.css +15 -0
  87. data/spec/tests/preserve-case.css.min +1 -0
  88. data/spec/tests/preserve-new-line.css +6 -0
  89. data/spec/tests/preserve-new-line.css.min +3 -0
  90. data/spec/tests/preserve-strings.css +7 -0
  91. data/spec/tests/preserve-strings.css.min +1 -0
  92. data/spec/tests/pseudo-first.css +16 -0
  93. data/spec/tests/pseudo-first.css.min +1 -0
  94. data/spec/tests/pseudo.css +4 -0
  95. data/spec/tests/pseudo.css.min +1 -0
  96. data/spec/tests/special-comments.css +13 -0
  97. data/spec/tests/special-comments.css.min +9 -0
  98. data/spec/tests/star-underscore-hacks.css +5 -0
  99. data/spec/tests/star-underscore-hacks.css.min +1 -0
  100. data/spec/tests/string-in-comment.css +8 -0
  101. data/spec/tests/string-in-comment.css.min +1 -0
  102. data/spec/tests/webkit-transform.css +2 -0
  103. data/spec/tests/webkit-transform.css.min +1 -0
  104. data/spec/tests/zeros.css +6 -0
  105. data/spec/tests/zeros.css.min +1 -0
  106. metadata +275 -0
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ coverage
6
+ rdoc
7
+ doc
8
+ .yardoc
9
+ .rspec
data/CHANGES.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 1.0.0
2
+
3
+ First release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in cssminify.gemspec
4
+ gemspec
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