cssminify2 2.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 (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