cssminify2 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.travis.yml +7 -0
  4. data/CHANGES.md +17 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.md +23 -0
  7. data/README.md +71 -0
  8. data/cssminify2.gemspec +31 -0
  9. data/lib/cssminify2.rb +34 -0
  10. data/lib/cssminify2/cssmin.rb +394 -0
  11. data/lib/cssminify2/version.rb +5 -0
  12. data/spec/cssminify_spec.rb +321 -0
  13. data/spec/sample.css +141 -0
  14. data/spec/spec_helper.rb +4 -0
  15. data/spec/tests/background-position.css +2 -0
  16. data/spec/tests/background-position.css.min +1 -0
  17. data/spec/tests/border-none.css +5 -0
  18. data/spec/tests/border-none.css.min +1 -0
  19. data/spec/tests/box-model-hack.css +9 -0
  20. data/spec/tests/box-model-hack.css.min +1 -0
  21. data/spec/tests/bug2527974.css +10 -0
  22. data/spec/tests/bug2527974.css.min +1 -0
  23. data/spec/tests/bug2527991.css +19 -0
  24. data/spec/tests/bug2527991.css.min +1 -0
  25. data/spec/tests/bug2527998.css +4 -0
  26. data/spec/tests/bug2527998.css.min +1 -0
  27. data/spec/tests/bug2528034.css +5 -0
  28. data/spec/tests/bug2528034.css.min +1 -0
  29. data/spec/tests/bug2528093.css +3 -0
  30. data/spec/tests/bug2528093.css.min +1 -0
  31. data/spec/tests/charset-media.css +9 -0
  32. data/spec/tests/charset-media.css.min +1 -0
  33. data/spec/tests/color-simple.css +8 -0
  34. data/spec/tests/color-simple.css.min +1 -0
  35. data/spec/tests/color.css +46 -0
  36. data/spec/tests/color.css.min +1 -0
  37. data/spec/tests/comment.css +3 -0
  38. data/spec/tests/comment.css.min +1 -0
  39. data/spec/tests/concat-charset.css +15 -0
  40. data/spec/tests/concat-charset.css.min +1 -0
  41. data/spec/tests/dataurl-base64-doublequotes.css +23 -0
  42. data/spec/tests/dataurl-base64-doublequotes.css.min +1 -0
  43. data/spec/tests/dataurl-base64-eof.css +10 -0
  44. data/spec/tests/dataurl-base64-eof.css.min +1 -0
  45. data/spec/tests/dataurl-base64-linebreakindata.css +34 -0
  46. data/spec/tests/dataurl-base64-linebreakindata.css.min +1 -0
  47. data/spec/tests/dataurl-base64-noquotes.css +26 -0
  48. data/spec/tests/dataurl-base64-noquotes.css.min +1 -0
  49. data/spec/tests/dataurl-base64-singlequotes.css +23 -0
  50. data/spec/tests/dataurl-base64-singlequotes.css.min +1 -0
  51. data/spec/tests/dataurl-base64-twourls.css +27 -0
  52. data/spec/tests/dataurl-base64-twourls.css.min +1 -0
  53. data/spec/tests/dataurl-dbquote-font.css +30 -0
  54. data/spec/tests/dataurl-dbquote-font.css.min +5 -0
  55. data/spec/tests/dataurl-nonbase64-doublequotes.css +13 -0
  56. data/spec/tests/dataurl-nonbase64-doublequotes.css.min +1 -0
  57. data/spec/tests/dataurl-nonbase64-noquotes.css +11 -0
  58. data/spec/tests/dataurl-nonbase64-noquotes.css.min +1 -0
  59. data/spec/tests/dataurl-nonbase64-singlequotes.css +15 -0
  60. data/spec/tests/dataurl-nonbase64-singlequotes.css.min +2 -0
  61. data/spec/tests/dataurl-noquote-multiline-font.css +31 -0
  62. data/spec/tests/dataurl-noquote-multiline-font.css.min +3 -0
  63. data/spec/tests/dataurl-realdata-doublequotes.css +90 -0
  64. data/spec/tests/dataurl-realdata-doublequotes.css.min +1 -0
  65. data/spec/tests/dataurl-realdata-noquotes.css +90 -0
  66. data/spec/tests/dataurl-realdata-noquotes.css.min +1 -0
  67. data/spec/tests/dataurl-realdata-singlequotes.css +90 -0
  68. data/spec/tests/dataurl-realdata-singlequotes.css.min +1 -0
  69. data/spec/tests/dataurl-realdata-yuiapp.css +106 -0
  70. data/spec/tests/dataurl-realdata-yuiapp.css.min +1 -0
  71. data/spec/tests/dataurl-singlequote-font.css +30 -0
  72. data/spec/tests/dataurl-singlequote-font.css.min +3 -0
  73. data/spec/tests/decimals.css +3 -0
  74. data/spec/tests/decimals.css.min +1 -0
  75. data/spec/tests/dollar-header.css +7 -0
  76. data/spec/tests/dollar-header.css.min +3 -0
  77. data/spec/tests/font-face.css +6 -0
  78. data/spec/tests/font-face.css.min +1 -0
  79. data/spec/tests/ie5mac.css +5 -0
  80. data/spec/tests/ie5mac.css.min +1 -0
  81. data/spec/tests/issue221.css +7 -0
  82. data/spec/tests/issue221.css.min +1 -0
  83. data/spec/tests/issue222.css +3 -0
  84. data/spec/tests/issue222.css.min +1 -0
  85. data/spec/tests/keyframe.css +4 -0
  86. data/spec/tests/keyframe.css.min +1 -0
  87. data/spec/tests/media-empty-class.css +16 -0
  88. data/spec/tests/media-empty-class.css.min +1 -0
  89. data/spec/tests/media-multi.css +3 -0
  90. data/spec/tests/media-multi.css.min +1 -0
  91. data/spec/tests/media-test.css +3 -0
  92. data/spec/tests/media-test.css.min +1 -0
  93. data/spec/tests/opacity-filter.css +14 -0
  94. data/spec/tests/opacity-filter.css.min +1 -0
  95. data/spec/tests/preserve-case.css +15 -0
  96. data/spec/tests/preserve-case.css.min +1 -0
  97. data/spec/tests/preserve-new-line.css +6 -0
  98. data/spec/tests/preserve-new-line.css.min +3 -0
  99. data/spec/tests/preserve-strings.css +7 -0
  100. data/spec/tests/preserve-strings.css.min +1 -0
  101. data/spec/tests/pseudo-first.css +16 -0
  102. data/spec/tests/pseudo-first.css.min +1 -0
  103. data/spec/tests/pseudo.css +4 -0
  104. data/spec/tests/pseudo.css.min +1 -0
  105. data/spec/tests/special-comments.css +13 -0
  106. data/spec/tests/special-comments.css.min +9 -0
  107. data/spec/tests/star-underscore-hacks.css +5 -0
  108. data/spec/tests/star-underscore-hacks.css.min +1 -0
  109. data/spec/tests/string-in-comment.css +8 -0
  110. data/spec/tests/string-in-comment.css.min +1 -0
  111. data/spec/tests/webkit-transform.css +2 -0
  112. data/spec/tests/webkit-transform.css.min +1 -0
  113. data/spec/tests/zeros.css +12 -0
  114. data/spec/tests/zeros.css.min +1 -0
  115. metadata +278 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4daf7fe9900f5626055e2d1bb78cb19d3be143fa
4
+ data.tar.gz: 849d83f313d0d424469bd51f178464bb5fda9ecd
5
+ SHA512:
6
+ metadata.gz: 96f3306d2ae758f89c54dd1d7f88a8d55d59468950a4eae4d4a490ebce2174cdbe47b8c1aa9f8d042b75c6a6a052655c36653450242134ef1bd941e96e3b7421
7
+ data.tar.gz: 4dbe8547ab1a6d77ab7bb12b96761da04ca8fc33bddc5c70e6934ee957dcec2cb0e5003ec721a6034f6b9fa72202abdbcc207a33e30d05b5964d7fe7a2e9db16
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/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - jruby-19mode # JRuby in 1.9 mode
6
+ - rbx-19mode
7
+ script: bundle exec rspec spec
data/CHANGES.md ADDED
@@ -0,0 +1,17 @@
1
+ ### 2.0.0 (31 Dec 2016)
2
+
3
+ * Fork, including github.com/r7com/cssminify zero value changes,
4
+ Recommit to rubygems as cssminify2
5
+
6
+ ### 1.0.2 (30 June 2012)
7
+
8
+ * Removed unnecessary Bundler dependency
9
+
10
+ ### 1.0.1 (30 June 2012)
11
+
12
+ * Updated cssmin.rb to 1.0.1
13
+ * Updated tests
14
+
15
+ ## 1.0.0 (29 June 2012)
16
+
17
+ 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,23 @@
1
+ Copyright (c) 2012 Matthias Siegel (matthias.siegel@gmail.com)
2
+ Copyright (c) 2016 Matt Spurrier (matthew@spurrier.com.au)
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ 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,71 @@
1
+ # CSSminify
2
+
3
+ CSS minification with YUI compressor, but as native Ruby port.
4
+
5
+ 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.
6
+
7
+ 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.
8
+
9
+ Main motivation for the Ruby port and this gem was to reduce dependencies like Java.
10
+
11
+ ---
12
+
13
+ **PLEASE NOTE**: This project has been forked from it's original author, with patches from r7com, and pushed to rubygems as cssminify2.
14
+
15
+ ---
16
+
17
+ ## Installation
18
+ Install CSSminify2 from RubyGems:
19
+
20
+ gem install cssminify2
21
+
22
+ Or include it in your project's Gemfile:
23
+
24
+ gem 'cssminify2'
25
+
26
+ ## Usage
27
+
28
+ require 'cssminify2'
29
+
30
+ CSSminify2.compress('/* a comment */ .test { display: block; }')
31
+ # => minified CSS
32
+
33
+ CSSminify2.compress(File.read('path/to/styles.css'))
34
+ # => minified CSS
35
+
36
+ CSSminify2.compress(File.open('path/to/styles.css'))
37
+ # => minified CSS
38
+
39
+ # Alternatively use instance method...
40
+ compressor = CSSminify2.new
41
+ compressor.compress(File.read("path/to/styles.css"))
42
+ # => minified CSS
43
+
44
+ Files or strings are acceptable as input.
45
+
46
+ You can pass in a second argument to control the maximum output line length (default 5000 characters):
47
+
48
+ CSSminify2.compress(File.read("path/to/styles.css"), 200)
49
+
50
+ Note: in most cases line length will only be approximated.
51
+
52
+ ## Rails asset pipeline
53
+ 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:
54
+
55
+ config.assets.css_compressor = CSSminify2.new
56
+
57
+ ## Compatibility
58
+ Tested with Ruby 1.9.2, 1.9.3, jruby-19mode, rbx-19mode
59
+
60
+ ## Changelog
61
+ See [CHANGES](https://github.com/digitalsparky/cssminify/blob/master/CHANGES.md).
62
+
63
+ ## Copyright
64
+
65
+ ### CSSminify gem and documentation
66
+ Copyright (c) 2012 Matthias Siegel (matthias.siegel@gmail.com)
67
+ Copyright (c) 2016 Matt Spurrier (matthew@spurrier.com.au)
68
+ See [LICENSE](https://github.com/cssminify/cssminify/blob/master/LICENSE.md) for details.
69
+
70
+ ### YUI compressor
71
+ See [file](https://github.com/digitalsparky/cssminify/blob/master/lib/cssminify/cssmin.rb).
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "cssminify2/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "cssminify2"
7
+ s.version = CSSminify2::VERSION
8
+ s.author = "Matt Spurrier"
9
+ s.email = "matthew@spurrier.com.au"
10
+ s.homepage = "https://github.com/digitalsparky/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.
14
+ This package is a fork of the original (now unmaintained) version.
15
+ EOF
16
+
17
+ s.extra_rdoc_files = [
18
+ "CHANGES.md",
19
+ "LICENSE.md",
20
+ "README.md"
21
+ ]
22
+
23
+ s.license = "MIT"
24
+ s.rubyforge_project = "cssminify2"
25
+
26
+ s.files = `git ls-files`.split("\n")
27
+ s.test_files = `git ls-files -- spec/*`.split("\n")
28
+ s.require_paths = ["lib"]
29
+
30
+ s.add_development_dependency "rspec", "~> 2.7"
31
+ end
data/lib/cssminify2.rb ADDED
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+
3
+ require "CSSminify2/cssmin"
4
+ require "CSSminify2/version"
5
+
6
+ class CSSminify2
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,394 @@
1
+ #
2
+ # cssmin.rb - 1.0.1
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)(\{|,)/i) { ":first-#{$1.to_s.downcase} #{$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
+ oldCss = '';
184
+ while oldCss != css do
185
+ oldCss = css
186
+ css = css.gsub(/(?i)(^|: ?)((?:[0-9a-z\-\.]+ )*?)?(?:0?\.)?0(?:px|em|%|in|cm|mm|pc|pt|ex|deg|g?rad|m?s|k?hz)/i) { "#{$1.to_s}#{$2.to_s}0" }
187
+ end
188
+
189
+ oldCss = '';
190
+ while oldCss != css do
191
+ oldCss = css
192
+ css = css.gsub(/(?i)\( ?((?:[0-9a-z\-\.]+[ ,])*)?(?:0?\.)?0(?:px|em|%|in|cm|mm|pc|pt|ex|deg|g?rad|m?s|k?hz)/i) { "(#{$1.to_s}0" }
193
+ end
194
+
195
+ css = css.gsub(/([0-9])\.0(px|em|%|in|cm|mm|pc|pt|ex|deg|g?rad|m?s|k?hz| |;)/i) {"#{$1.to_s}#{$2.to_s}"}
196
+
197
+ # Replace 0 0 0 0; with 0
198
+ #
199
+ css = css.gsub(/:0 0 0 0(;|\})/) { ':0' + $1.to_s }
200
+ css = css.gsub(/:0 0 0(;|\})/) { ':0' + $1.to_s }
201
+ css = css.gsub(/:0 0(;|\})/) { ':0' + $1.to_s }
202
+
203
+ #
204
+ # Replace background-position:0; with background-position:0 0;
205
+ # Same for transform-origin
206
+ #
207
+ 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}" }
208
+
209
+ #
210
+ # Replace 0.6 to .6, but only when preceded by : or a white-space
211
+ #
212
+ css = css.gsub(/(:|\s)0+\.(\d+)/) { "#{$1.to_s}.#{$2.to_s}" }
213
+
214
+ #
215
+ # Shorten colors from rgb(51,102,153) to #336699
216
+ # This makes it more likely that it'll get further compressed in the next step.
217
+ #
218
+ css = css.gsub(/rgb\s*\(\s*([0-9,\s]+)\s*\)(\d+%)?/i) {
219
+ rgbcolors = $1.to_s.split(',')
220
+ i = 0
221
+
222
+ while i < rgbcolors.length
223
+ rgbcolors[i] = rgbcolors[i].to_i.to_s(16)
224
+
225
+ if rgbcolors[i].length === 1
226
+ rgbcolors[i] = '0' + rgbcolors[i]
227
+ end
228
+
229
+ i += 1
230
+ end
231
+
232
+ unless $2.to_s.empty?
233
+ rgbcolors << (' ' + $2.to_s)
234
+ end
235
+
236
+ '#' + rgbcolors.join('')
237
+ }
238
+
239
+ #
240
+ # Shorten colors from #AABBCC to #ABC. Note that we want to make sure
241
+ # the color is not preceded by either ", " or =. Indeed, the property
242
+ # filter: chroma(color="#FFFFFF");
243
+ # would become
244
+ # filter: chroma(color="#FFF");
245
+ # which makes the filter break in IE.
246
+ # We also want to make sure we're only compressing #AABBCC patterns inside { }, not id selectors ( #FAABAC {} )
247
+ # We also want to avoid compressing invalid values (e.g. #AABBCCD to #ABCD)
248
+ #
249
+ rest = css
250
+ new_css = ''
251
+
252
+ while m = rest.match(/(\=\s*?["']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(:?\}|[^0-9a-f{][^{]*?\})/i) do
253
+ #
254
+ # Normal color value
255
+ #
256
+ if m[1].nil?
257
+ #
258
+ # Color value that can be shortened
259
+ #
260
+ 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
261
+ color = '#' + m[2].to_s.downcase + m[4].to_s.downcase + m[6].to_s.downcase
262
+ shift = 7
263
+
264
+ #
265
+ # Color value that can't be shortened
266
+ #
267
+ else
268
+ 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
269
+ shift = 7
270
+ end
271
+
272
+ #
273
+ # Filter property that needs to stay the way it is
274
+ #
275
+ else
276
+ 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
277
+ shift = color.length
278
+ end
279
+
280
+ new_css += m.pre_match + color
281
+ rest.slice!(rest.index(m.pre_match || 0), m.pre_match.length + shift)
282
+
283
+ end
284
+ css = new_css + rest
285
+
286
+ #
287
+ # border: none -> border:0
288
+ #
289
+ css = css.gsub(/(border|border-top|border-right|border-bottom|border-left|outline|background):none(;|\})/i) { "#{$1.to_s.downcase}:0#{$2.to_s}" }
290
+
291
+ #
292
+ # Shorter opacity IE filter
293
+ #
294
+ css = css.gsub(/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/i, 'alpha(opacity=')
295
+
296
+ #
297
+ # Remove empty rules.
298
+ #
299
+ css = css.gsub(/[^\};\{\/]+\{\}/, '')
300
+
301
+ #
302
+ # Some source control tools don't like it when files containing lines longer
303
+ # than, say 8000 characters, are checked in. The linebreak option is used in
304
+ # that case to split long lines after a specific column.
305
+ #
306
+ if linebreakpos
307
+ startIndex = 0
308
+ i = 0
309
+ while i < css.length
310
+ i = i + 1
311
+ if css[i - 1] === '}' && i - startIndex > linebreakpos
312
+ css = css.slice(0, i) + "\n" + css.slice(i, css.length - i)
313
+ startIndex = i
314
+ end
315
+ end
316
+ end
317
+
318
+ #
319
+ # Replace multiple semi-colons in a row by a single one
320
+ #
321
+ css = css.gsub(/;;+/, ';')
322
+
323
+ #
324
+ # Restore preserved comments and strings
325
+ #
326
+ for i in 0...@@preservedTokens.length do
327
+ css = css.gsub(/___YUICSSMIN_PRESERVED_TOKEN_#{i}___/) { @@preservedTokens[i] }
328
+ end
329
+
330
+ #
331
+ # Remove leading and trailing whitespace
332
+ #
333
+ css = css.chomp.strip
334
+ end
335
+
336
+
337
+
338
+
339
+ private
340
+
341
+
342
+ #
343
+ # Utility method to replace all data urls with tokens before we start
344
+ # compressing, to avoid performance issues running some of the subsequent
345
+ # regexes against large strings chunks.
346
+ #
347
+ # @param [String] css The input css
348
+ # @returns [String] The processed css
349
+ #
350
+ def self.extractDataUrls(css)
351
+ new_css = ''
352
+
353
+ while m = css.match(/url\(\s*(["']?)data\:/i) do
354
+ startIndex = m.begin(0) + 4 # "url(".length()
355
+ terminator = m[1] # ', " or empty (not quoted)
356
+ terminator = ')' if terminator.empty?
357
+ foundTerminator = false
358
+ endIndex = m.end(0) - 1
359
+
360
+ while !foundTerminator && endIndex + 1 <= css.length
361
+ endIndex = css.index(terminator, endIndex + 1)
362
+
363
+ #
364
+ # endIndex == 0 doesn't really apply here
365
+ #
366
+ if endIndex > 0 && css[endIndex - 1] != '\\'
367
+ foundTerminator = true
368
+ endIndex = css.index(')', endIndex) if terminator != ')'
369
+ end
370
+ end
371
+
372
+ new_css += css[0...m.begin(0)]
373
+
374
+ if foundTerminator
375
+ token = css[startIndex...endIndex]
376
+ token = token.gsub(/\s+/, '')
377
+ @@preservedTokens << token
378
+
379
+ new_css += "url(___YUICSSMIN_PRESERVED_TOKEN_" + (@@preservedTokens.length - 1).to_s + "___)"
380
+
381
+ #
382
+ # No end terminator found, re-add the whole match
383
+ #
384
+ else
385
+ new_css += css[m.begin(0)...m.end(0)]
386
+ end
387
+
388
+ css = css[(endIndex + 1)..-1]
389
+ end
390
+
391
+ css = new_css + css
392
+ end
393
+
394
+ end