PluginProbe
Autoptimize / 1.3
Autoptimize v1.3
2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 All 107 releases
autoptimize / classes / minify-css-compressor.php

minify-css-compressor.php in Autoptimize 1.3, at classes/minify-css-compressor.php

251 lines 7.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class Minify_CSS_Compressor
4 * @package Minify
5 */
6
7 /**
8 * Compress CSS
9 *
10 * This is a heavy regex-based removal of whitespace, unnecessary
11 * comments and tokens, and some CSS value minimization, where practical.
12 * Many steps have been taken to avoid breaking comment-based hacks,
13 * including the ie5/mac filter (and its inversion), but expect tricky
14 * hacks involving comment tokens in 'content' value strings to break
15 * minimization badly. A test suite is available.
16 *
17 * @package Minify
18 * @author Stephen Clay <steve@mrclay.org>
19 * @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
20 */
21 class Minify_CSS_Compressor {
22
23 /**
24 * Minify a CSS string
25 *
26 * @param string $css
27 *
28 * @param array $options (currently ignored)
29 *
30 * @return string
31 */
32 public static function process($css, $options = array())
33 {
34 $obj = new Minify_CSS_Compressor($options);
35 return $obj->_process($css);
36 }
37
38 /**
39 * @var array options
40 */
41 protected $_options = null;
42
43 /**
44 * @var bool Are we "in" a hack?
45 *
46 * I.e. are some browsers targetted until the next comment?
47 */
48 protected $_inHack = false;
49
50
51 /**
52 * Constructor
53 *
54 * @param array $options (currently ignored)
55 *
56 * @return null
57 */
58 private function __construct($options) {
59 $this->_options = $options;
60 }
61
62 /**
63 * Minify a CSS string
64 *
65 * @param string $css
66 *
67 * @return string
68 */
69 protected function _process($css)
70 {
71 $css = str_replace("\r\n", "\n", $css);
72
73 // preserve empty comment after '>'
74 // http://www.webdevout.net/css-hacks#in_css-selectors
75 $css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css);
76
77 // preserve empty comment between property and value
78 // http://css-discuss.incutio.com/?page=BoxModelHack
79 $css = preg_replace('@/\\*\\s*\\*/\\s*:@', '/*keep*/:', $css);
80 $css = preg_replace('@:\\s*/\\*\\s*\\*/@', ':/*keep*/', $css);
81
82 // apply callback to all valid comments (and strip out surrounding ws
83 $css = preg_replace_callback('@\\s*/\\*([\\s\\S]*?)\\*/\\s*@'
84 ,array($this, '_commentCB'), $css);
85
86 // remove ws around { } and last semicolon in declaration block
87 $css = preg_replace('/\\s*{\\s*/', '{', $css);
88 $css = preg_replace('/;?\\s*}\\s*/', '}', $css);
89
90 // remove ws surrounding semicolons
91 $css = preg_replace('/\\s*;\\s*/', ';', $css);
92
93 // remove ws around urls
94 $css = preg_replace('/
95 url\\( # url(
96 \\s*
97 ([^\\)]+?) # 1 = the URL (really just a bunch of non right parenthesis)
98 \\s*
99 \\) # )
100 /x', 'url($1)', $css);
101
102 // remove ws between rules and colons
103 $css = preg_replace('/
104 \\s*
105 ([{;]) # 1 = beginning of block or rule separator
106 \\s*
107 ([\\*_]?[\\w\\-]+) # 2 = property (and maybe IE filter)
108 \\s*
109 :
110 \\s*
111 (\\b|[#\'"]) # 3 = first character of a value
112 /x', '$1$2:$3', $css);
113
114 // remove ws in selectors
115 $css = preg_replace_callback('/
116 (?: # non-capture
117 \\s*
118 [^~>+,\\s]+ # selector part
119 \\s*
120 [,>+~] # combinators
121 )+
122 \\s*
123 [^~>+,\\s]+ # selector part
124 { # open declaration block
125 /x'
126 ,array($this, '_selectorsCB'), $css);
127
128 // minimize hex colors
129 $css = preg_replace('/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i'
130 , '$1#$2$3$4$5', $css);
131
132 // remove spaces between font families
133 $css = preg_replace_callback('/font-family:([^;}]+)([;}])/'
134 ,array($this, '_fontFamilyCB'), $css);
135
136 $css = preg_replace('/@import\\s+url/', '@import url', $css);
137
138 // replace any ws involving newlines with a single newline
139 $css = preg_replace('/[ \\t]*\\n+\\s*/', "\n", $css);
140
141 // separate common descendent selectors w/ newlines (to limit line lengths)
142 $css = preg_replace('/([\\w#\\.\\*]+)\\s+([\\w#\\.\\*]+){/', "$1\n$2{", $css);
143
144 // Use newline after 1st numeric value (to limit line lengths).
145 $css = preg_replace('/
146 ((?:padding|margin|border|outline):\\d+(?:px|em)?) # 1 = prop : 1st numeric value
147 \\s+
148 /x'
149 ,"$1\n", $css);
150
151 // prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/
152 $css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css);
153
154 return trim($css);
155 }
156
157 /**
158 * Replace what looks like a set of selectors
159 *
160 * @param array $m regex matches
161 *
162 * @return string
163 */
164 protected function _selectorsCB($m)
165 {
166 // remove ws around the combinators
167 return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]);
168 }
169
170 /**
171 * Process a comment and return a replacement
172 *
173 * @param array $m regex matches
174 *
175 * @return string
176 */
177 protected function _commentCB($m)
178 {
179 $hasSurroundingWs = (trim($m[0]) !== $m[1]);
180 $m = $m[1];
181 // $m is the comment content w/o the surrounding tokens,
182 // but the return value will replace the entire comment.
183 if ($m === 'keep') {
184 return '/**/';
185 }
186 if ($m === '" "') {
187 // component of http://tantek.com/CSS/Examples/midpass.html
188 return '/*" "*/';
189 }
190 if (preg_match('@";\\}\\s*\\}/\\*\\s+@', $m)) {
191 // component of http://tantek.com/CSS/Examples/midpass.html
192 return '/*";}}/* */';
193 }
194 if ($this->_inHack) {
195 // inversion: feeding only to one browser
196 if (preg_match('@
197 ^/ # comment started like /*/
198 \\s*
199 (\\S[\\s\\S]+?) # has at least some non-ws content
200 \\s*
201 /\\* # ends like /*/ or /**/
202 @x', $m, $n)) {
203 // end hack mode after this comment, but preserve the hack and comment content
204 $this->_inHack = false;
205 return "/*/{$n[1]}/**/";
206 }
207 }
208 if (substr($m, -1) === '\\') { // comment ends like \*/
209 // begin hack mode and preserve hack
210 $this->_inHack = true;
211 return '/*\\*/';
212 }
213 if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */
214 // begin hack mode and preserve hack
215 $this->_inHack = true;
216 return '/*/*/';
217 }
218 if ($this->_inHack) {
219 // a regular comment ends hack mode but should be preserved
220 $this->_inHack = false;
221 return '/**/';
222 }
223 // Issue 107: if there's any surrounding whitespace, it may be important, so
224 // replace the comment with a single space
225 return $hasSurroundingWs // remove all other comments
226 ? ' '
227 : '';
228 }
229
230 /**
231 * Process a font-family listing and return a replacement
232 *
233 * @param array $m regex matches
234 *
235 * @return string
236 */
237 protected function _fontFamilyCB($m)
238 {
239 $m[1] = preg_replace('/
240 \\s*
241 (
242 "[^"]+" # 1 = family in double qutoes
243 |\'[^\']+\' # or 1 = family in single quotes
244 |[\\w\\-]+ # or 1 = unquoted family
245 )
246 \\s*
247 /x', '$1', $m[1]);
248 return 'font-family:' . $m[1] . $m[2];
249 }
250 }
251