cssminify 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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