1 | <?php |
---|
2 | /** |
---|
3 | * CssMin - A (simple) css minifier with benefits |
---|
4 | * |
---|
5 | * -- |
---|
6 | * |
---|
7 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING |
---|
8 | * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
---|
9 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
---|
10 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
---|
11 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
---|
12 | * -- |
---|
13 | * |
---|
14 | * @package CssMin |
---|
15 | * @author Joe Scylla <joe.scylla@gmail.com> |
---|
16 | * @copyright 2008 - 2010 Joe Scylla <joe.scylla@gmail.com> |
---|
17 | * @license http://opensource.org/licenses/mit-license.php MIT License |
---|
18 | * @version 2.0.1.0064 (2010-09-30) |
---|
19 | */ |
---|
20 | class CssMin |
---|
21 | { |
---|
22 | /** |
---|
23 | * State: Is in document |
---|
24 | * |
---|
25 | * @var integer |
---|
26 | */ |
---|
27 | const T_DOCUMENT = 1; |
---|
28 | /** |
---|
29 | * Token: Comment |
---|
30 | * |
---|
31 | * @var integer |
---|
32 | */ |
---|
33 | const T_COMMENT = 2; |
---|
34 | /** |
---|
35 | * Token: Generic at-rule |
---|
36 | * |
---|
37 | * @var integer |
---|
38 | */ |
---|
39 | const T_AT_RULE = 3; |
---|
40 | /** |
---|
41 | * Token: Start of @media block |
---|
42 | * |
---|
43 | * @var integer |
---|
44 | */ |
---|
45 | const T_AT_MEDIA_START = 4; |
---|
46 | /** |
---|
47 | * State: Is in @media block |
---|
48 | * |
---|
49 | * @var integer |
---|
50 | */ |
---|
51 | const T_AT_MEDIA = 5; |
---|
52 | /** |
---|
53 | * Token: End of @media block |
---|
54 | * |
---|
55 | * @var integer |
---|
56 | */ |
---|
57 | const T_AT_MEDIA_END = 6; |
---|
58 | /** |
---|
59 | * Token: Start of @font-face block |
---|
60 | * |
---|
61 | * @var integer |
---|
62 | */ |
---|
63 | const T_AT_FONT_FACE_START = 7; |
---|
64 | /** |
---|
65 | * State: Is in @font-face block |
---|
66 | * |
---|
67 | * @var integer |
---|
68 | */ |
---|
69 | const T_AT_FONT_FACE = 8; |
---|
70 | /** |
---|
71 | * Token: @font-face declaration |
---|
72 | * |
---|
73 | * @var integer |
---|
74 | */ |
---|
75 | const T_FONT_FACE_DECLARATION = 9; |
---|
76 | /** |
---|
77 | * Token: End of @font-face block |
---|
78 | * |
---|
79 | * @var integer |
---|
80 | */ |
---|
81 | const T_AT_FONT_FACE_END = 10; |
---|
82 | /** |
---|
83 | * Token: Start of @page block |
---|
84 | * |
---|
85 | * @var integer |
---|
86 | */ |
---|
87 | const T_AT_PAGE_START = 11; |
---|
88 | /** |
---|
89 | * State: Is in @page block |
---|
90 | * |
---|
91 | * @var integer |
---|
92 | */ |
---|
93 | const T_AT_PAGE = 12; |
---|
94 | /** |
---|
95 | * Token: @page declaration |
---|
96 | * |
---|
97 | * @var integer |
---|
98 | */ |
---|
99 | const T_PAGE_DECLARATION = 13; |
---|
100 | /** |
---|
101 | * Token: End of @page block |
---|
102 | * |
---|
103 | * @var integer |
---|
104 | */ |
---|
105 | const T_AT_PAGE_END = 14; |
---|
106 | /** |
---|
107 | * Token: Start of ruleset |
---|
108 | * |
---|
109 | * @var integer |
---|
110 | */ |
---|
111 | const T_RULESET_START = 15; |
---|
112 | /** |
---|
113 | * Token: Ruleset selectors |
---|
114 | * |
---|
115 | * @var integer |
---|
116 | */ |
---|
117 | const T_SELECTORS = 16; |
---|
118 | /** |
---|
119 | * Token: Start of declarations |
---|
120 | * |
---|
121 | * @var integer |
---|
122 | */ |
---|
123 | const T_DECLARATIONS_START = 17; |
---|
124 | /** |
---|
125 | * State: Is in declarations |
---|
126 | * |
---|
127 | * @var integer |
---|
128 | */ |
---|
129 | const T_DECLARATIONS = 18; |
---|
130 | /** |
---|
131 | * Token: Declaration |
---|
132 | * |
---|
133 | * @var integer |
---|
134 | */ |
---|
135 | const T_DECLARATION = 19; |
---|
136 | /** |
---|
137 | * Token: End of declarations |
---|
138 | * |
---|
139 | * @var integer |
---|
140 | */ |
---|
141 | const T_DECLARATIONS_END = 20; |
---|
142 | /** |
---|
143 | * Token: End of ruleset |
---|
144 | * |
---|
145 | * @var integer |
---|
146 | */ |
---|
147 | const T_RULESET_END = 21; |
---|
148 | /** |
---|
149 | * Token: Start of @variables block |
---|
150 | * |
---|
151 | * @var integer |
---|
152 | */ |
---|
153 | const T_AT_VARIABLES_START = 100; |
---|
154 | /** |
---|
155 | * State: Is in @variables block |
---|
156 | * |
---|
157 | * @var integer |
---|
158 | */ |
---|
159 | const T_AT_VARIABLES = 101; |
---|
160 | /** |
---|
161 | * Token: @variables declaration |
---|
162 | * |
---|
163 | * @var integer |
---|
164 | */ |
---|
165 | const T_VARIABLE_DECLARATION = 102; |
---|
166 | /** |
---|
167 | * Token: End of @variables block |
---|
168 | * |
---|
169 | * @var integer |
---|
170 | */ |
---|
171 | const T_AT_VARIABLES_END = 103; |
---|
172 | /** |
---|
173 | * State: Is in string |
---|
174 | * |
---|
175 | * @var integer |
---|
176 | */ |
---|
177 | const T_STRING = 254; |
---|
178 | /** |
---|
179 | * State: Is in url string property |
---|
180 | * |
---|
181 | * @var integer |
---|
182 | */ |
---|
183 | const T_STRING_URL = 255; |
---|
184 | /** |
---|
185 | * Css transformations table |
---|
186 | * |
---|
187 | * @var array |
---|
188 | */ |
---|
189 | private static $transformations = array |
---|
190 | ( |
---|
191 | "border-radius" => array("-moz-border-radius", "-webkit-border-radius", "-khtml-border-radius"), |
---|
192 | "border-top-left-radius" => array("-moz-border-radius-topleft", "-webkit-border-top-left-radius", "-khtml-top-left-radius"), |
---|
193 | "border-top-right-radius" => array("-moz-border-radius-topright", "-webkit-border-top-right-radius", "-khtml-top-right-radius"), |
---|
194 | "border-bottom-right-radius" => array("-moz-border-radius-bottomright", "-webkit-border-bottom-right-radius", "-khtml-border-bottom-right-radius"), |
---|
195 | "border-bottom-left-radius" => array("-moz-border-radius-bottomleft", "-webkit-border-bottom-left-radius", "-khtml-border-bottom-left-radius"), |
---|
196 | "box-shadow" => array("-moz-box-shadow", "-webkit-box-shadow", "-khtml-box-shadow"), |
---|
197 | "opacity" => array(array("CssMin", "_tOpacity")), |
---|
198 | "text-shadow" => array("-moz-text-shadow", "-webkit-text-shadow", "-khtml-text-shadow"), |
---|
199 | "white-space" => array(array("CssMin", "_tWhiteSpacePreWrap")) |
---|
200 | ); |
---|
201 | /** |
---|
202 | * Minifies the Css. |
---|
203 | * |
---|
204 | * @param string $css |
---|
205 | * @param array $config [optional] |
---|
206 | * @return string |
---|
207 | */ |
---|
208 | public static function minify($css, $config = array()) |
---|
209 | { |
---|
210 | $tokens = self::parse($css); |
---|
211 | $config = array_merge(array |
---|
212 | ( |
---|
213 | "remove-empty-blocks" => true, |
---|
214 | "remove-empty-rulesets" => true, |
---|
215 | "remove-last-semicolons" => true, |
---|
216 | "convert-css3-properties" => false, |
---|
217 | "convert-color-values" => false, |
---|
218 | "compress-color-values" => false, |
---|
219 | "compress-unit-values" => false, |
---|
220 | "emulate-css3-variables" => true, |
---|
221 | ), $config); |
---|
222 | // Minification options |
---|
223 | $sRemoveEmptyBlocks = $config["remove-empty-blocks"]; |
---|
224 | $sRemoveEmptyRulesets = $config["remove-empty-rulesets"]; |
---|
225 | $sRemoveLastSemicolon = $config["remove-last-semicolons"]; |
---|
226 | $sConvertCss3Properties = $config["convert-css3-properties"]; |
---|
227 | $sCompressUnitValues = $config["compress-unit-values"]; |
---|
228 | $sConvertColorValues = $config["convert-color-values"]; |
---|
229 | $sCompressColorValues = $config["compress-color-values"]; |
---|
230 | $sEmulateCcss3Variables = $config["emulate-css3-variables"]; |
---|
231 | $sRemoveTokens = array(self::T_COMMENT); |
---|
232 | // Remove tokens |
---|
233 | if (!$sEmulateCcss3Variables) |
---|
234 | { |
---|
235 | $sRemoveTokens = array_merge($sRemoveTokens, array(self::T_AT_VARIABLES_START, self::T_VARIABLE_DECLARATION, self::T_AT_VARIABLES_END)); |
---|
236 | } |
---|
237 | for($i = 0, $l = count($tokens); $i < $l; $i++) |
---|
238 | { |
---|
239 | if (in_array($tokens[$i][0], $sRemoveTokens)) |
---|
240 | { |
---|
241 | unset($tokens[$i]); |
---|
242 | } |
---|
243 | } |
---|
244 | $tokens = array_values($tokens); |
---|
245 | // Remove empty rulesets |
---|
246 | if ($sRemoveEmptyRulesets) |
---|
247 | { |
---|
248 | for($i = 0, $l = count($tokens); $i < $l; $i++) |
---|
249 | { |
---|
250 | // Remove empty rulesets |
---|
251 | if ($tokens[$i][0] == self::T_RULESET_START && $tokens[$i+4][0] == self::T_RULESET_END) |
---|
252 | { |
---|
253 | unset($tokens[$i]); // T_RULESET_START |
---|
254 | unset($tokens[++$i]); // T_SELECTORS |
---|
255 | unset($tokens[++$i]); // T_DECLARATIONS_START |
---|
256 | unset($tokens[++$i]); // T_DECLARATIONS_END |
---|
257 | unset($tokens[++$i]); // T_RULESET_END |
---|
258 | } |
---|
259 | } |
---|
260 | $tokens = array_values($tokens); |
---|
261 | } |
---|
262 | // Remove empty @media, @font-face or @page blocks |
---|
263 | if ($sRemoveEmptyBlocks) |
---|
264 | { |
---|
265 | for($i = 0, $l = count($tokens); $i < $l; $i++) |
---|
266 | { |
---|
267 | // Remove empty @media, @font-face or @page blocks |
---|
268 | if (($tokens[$i][0] == self::T_AT_MEDIA_START && $tokens[$i+1][0] == self::T_AT_MEDIA_END) |
---|
269 | || ($tokens[$i][0] == self::T_AT_FONT_FACE_START && $tokens[$i+1][0] == self::T_AT_FONT_FACE_END) |
---|
270 | || ($tokens[$i][0] == self::T_AT_PAGE_START && $tokens[$i+1][0] == self::T_AT_PAGE_END)) |
---|
271 | { |
---|
272 | unset($tokens[$i]); // T_AT_MEDIA_START, T_AT_FONT_FACE_START, T_AT_PAGE_START |
---|
273 | unset($tokens[++$i]); // T_AT_MEDIA_END, T_AT_FONT_FACE_END, T_AT_PAGE_END |
---|
274 | } |
---|
275 | } |
---|
276 | $tokens = array_values($tokens); |
---|
277 | } |
---|
278 | // CSS Level 3 variables: parse variables |
---|
279 | if ($sEmulateCcss3Variables) |
---|
280 | { |
---|
281 | // Parse variables |
---|
282 | $variables = array(); |
---|
283 | for($i = 0, $l = count($tokens); $i < $l; $i++) |
---|
284 | { |
---|
285 | if ($tokens[$i][0] == self::T_VARIABLE_DECLARATION) |
---|
286 | { |
---|
287 | for($i2 = 0, $l2 = count($tokens[$i][3]); $i2 < $l2; $i2++) |
---|
288 | { |
---|
289 | if (!isset($variables[$tokens[$i][3][$i2]])) |
---|
290 | { |
---|
291 | $variables[$tokens[$i][3][$i2]] = array(); |
---|
292 | } |
---|
293 | $variables[$tokens[$i][3][$i2]][$tokens[$i][1]] = $tokens[$i][2]; |
---|
294 | } |
---|
295 | } |
---|
296 | } |
---|
297 | } |
---|
298 | // Conversion and compression |
---|
299 | for($i = 0, $l = count($tokens); $i < $l; $i++) |
---|
300 | { |
---|
301 | if ($tokens[$i][0] == self::T_DECLARATION) |
---|
302 | { |
---|
303 | // CSS Level 3 variables |
---|
304 | if ($sEmulateCcss3Variables) |
---|
305 | { |
---|
306 | if (substr($tokens[$i][2], 0, 4) == "var(" && substr($tokens[$i][2], -1, 1) == ")") |
---|
307 | { |
---|
308 | $tokens[$i][3][] = "all"; |
---|
309 | $variable = trim(substr($tokens[$i][2], 4, -1)); |
---|
310 | for($i2 = 0, $l2 = count($tokens[$i][3]); $i2 < $l2; $i2++) |
---|
311 | { |
---|
312 | if (isset($variables[$tokens[$i][3][$i2]][$variable])) |
---|
313 | { |
---|
314 | $tokens[$i][2] = $variables[$tokens[$i][3][$i2]][$variable]; |
---|
315 | break; |
---|
316 | } |
---|
317 | } |
---|
318 | } |
---|
319 | } |
---|
320 | // Compress unit values |
---|
321 | if ($sCompressUnitValues) |
---|
322 | { |
---|
323 | // Compress "0.5px" to ".5px" |
---|
324 | $tokens[$i][2] = preg_replace("/(^| |-)0\.([0-9]+)(%|em|ex|px|in|cm|mm|pt|pc)/iS", "\${1}.\${2}\${3}", $tokens[$i][2]); |
---|
325 | // Compress "0px" to "0" |
---|
326 | $tokens[$i][2] = preg_replace("/(^| )-?(\.?)0(%|em|ex|px|in|cm|mm|pt|pc)/iS", "\${1}0", $tokens[$i][2]); |
---|
327 | // Compress "0 0 0 0" to "0" |
---|
328 | if ($tokens[$i][2] == "0 0 0 0") {$tokens[$i][2] = "0";} |
---|
329 | } |
---|
330 | // Convert RGB color values to hex ("rgb(200,60%,5)" => "#c89905") |
---|
331 | if ($sConvertColorValues && preg_match("/rgb\s*\(\s*([0-9%]+)\s*,\s*([0-9%]+)\s*,\s*([0-9%]+)\s*\)/iS", $tokens[$i][2], $m)) |
---|
332 | { |
---|
333 | for ($i2 = 1, $l2 = count($m); $i2 < $l2; $i2++) |
---|
334 | { |
---|
335 | if (strpos("%", $m[$i2]) !== false) |
---|
336 | { |
---|
337 | $m[$i2] = substr($m[$i2], 0, -1); |
---|
338 | $m[$i2] = (int) (256 * ($m[$i2] / 100)); |
---|
339 | } |
---|
340 | $m[$i2] = str_pad(dechex($m[$i2]), 2, "0", STR_PAD_LEFT); |
---|
341 | } |
---|
342 | $tokens[$i][2] = str_replace($m[0], "#" . $m[1] . $m[2] . $m[3], $tokens[$i][2]); |
---|
343 | } |
---|
344 | // Compress color values ("#aabbcc" to "#abc") |
---|
345 | if ($sCompressColorValues && preg_match("/\#([0-9a-f]{6})/iS", $tokens[$i][2], $m)) |
---|
346 | { |
---|
347 | $m[1] = strtolower($m[1]); |
---|
348 | if (substr($m[1], 0, 1) == substr($m[1], 1, 1) && substr($m[1], 2, 1) == substr($m[1], 3, 1) && substr($m[1], 4, 1) == substr($m[1], 5, 1)) |
---|
349 | { |
---|
350 | $tokens[$i][2] = str_replace($m[0], "#" . substr($m[1], 0, 1) . substr($m[1], 2, 1) . substr($m[1], 4, 1), $tokens[$i][2]); |
---|
351 | } |
---|
352 | } |
---|
353 | } |
---|
354 | } |
---|
355 | // Create minified css |
---|
356 | $r = ""; |
---|
357 | for($i = 0, $l = count($tokens); $i < $l; $i++) |
---|
358 | { |
---|
359 | // T_AT_RULE |
---|
360 | if ($tokens[$i][0] == self::T_AT_RULE) |
---|
361 | { |
---|
362 | $r .= "@" . $tokens[$i][1] . " " . $tokens[$i][2] . ";"; |
---|
363 | } |
---|
364 | // T_AT_MEDIA_START |
---|
365 | elseif ($tokens[$i][0] == self::T_AT_MEDIA_START) |
---|
366 | { |
---|
367 | if (count($tokens[$i][1]) == 1 && $tokens[$i][1][0] == "all") |
---|
368 | { |
---|
369 | $r .= "@media{"; |
---|
370 | } |
---|
371 | else |
---|
372 | { |
---|
373 | $r .= "@media " . implode(",", $tokens[$i][1]) . "{"; |
---|
374 | } |
---|
375 | } |
---|
376 | // T_AT_FONT_FACE_START |
---|
377 | elseif ($tokens[$i][0] == self::T_AT_FONT_FACE_START) |
---|
378 | { |
---|
379 | $r .= "@font-face{"; |
---|
380 | } |
---|
381 | // T_FONT_FACE_DECLARATION |
---|
382 | elseif ($tokens[$i][0] == self::T_FONT_FACE_DECLARATION) |
---|
383 | { |
---|
384 | $r .= $tokens[$i][1] . ":" . $tokens[$i][2] . ($sRemoveLastSemicolon && $tokens[$i+1][0] == self::T_AT_FONT_FACE_END ? "" : ";"); |
---|
385 | } |
---|
386 | // T_AT_PAGE_START |
---|
387 | elseif ($tokens[$i][0] == self::T_AT_PAGE_START) |
---|
388 | { |
---|
389 | $r .= "@page{"; |
---|
390 | } |
---|
391 | // T_PAGE_DECLARATION |
---|
392 | elseif ($tokens[$i][0] == self::T_PAGE_DECLARATION) |
---|
393 | { |
---|
394 | $r .= $tokens[$i][1] . ":" . $tokens[$i][2] . ($sRemoveLastSemicolon && $tokens[$i+1][0] == self::T_AT_PAGE_END ? "" : ";"); |
---|
395 | } |
---|
396 | // T_SELECTORS |
---|
397 | elseif ($tokens[$i][0] == self::T_SELECTORS) |
---|
398 | { |
---|
399 | $r .= implode(",", $tokens[$i][1]); |
---|
400 | } |
---|
401 | // Start of declarations |
---|
402 | elseif ($tokens[$i][0] == self::T_DECLARATIONS_START) |
---|
403 | { |
---|
404 | $r .= "{"; |
---|
405 | } |
---|
406 | // T_DECLARATION |
---|
407 | elseif ($tokens[$i][0] == self::T_DECLARATION) |
---|
408 | { |
---|
409 | if ($sConvertCss3Properties && isset(self::$transformations[$tokens[$i][1]])) |
---|
410 | { |
---|
411 | foreach (self::$transformations[$tokens[$i][1]] as $value) |
---|
412 | { |
---|
413 | if (!is_array($value)) |
---|
414 | { |
---|
415 | $r .= $value . ":" . $tokens[$i][2] . ";"; |
---|
416 | } |
---|
417 | elseif (is_array($value) && is_callable($value)) |
---|
418 | { |
---|
419 | $r.= call_user_func_array($value, array($tokens[$i][1], $tokens[$i][2])); |
---|
420 | |
---|
421 | } |
---|
422 | } |
---|
423 | } |
---|
424 | $r .= $tokens[$i][1] . ":" . $tokens[$i][2] . ($sRemoveLastSemicolon && $tokens[$i+1][0] == self::T_DECLARATIONS_END ? "" : ";"); |
---|
425 | } |
---|
426 | // T_DECLARATIONS_END, T_AT_MEDIA_END, T_AT_FONT_FACE_END, T_AT_PAGE_END |
---|
427 | elseif (in_array($tokens[$i][0], array(self::T_DECLARATIONS_END, self::T_AT_MEDIA_END, self::T_AT_FONT_FACE_END, self::T_AT_PAGE_END))) |
---|
428 | { |
---|
429 | $r .= "}"; |
---|
430 | } |
---|
431 | else |
---|
432 | { |
---|
433 | // Tokens with no output: |
---|
434 | // T_COMMENT |
---|
435 | // T_RULESET_START |
---|
436 | // T_RULESET_END |
---|
437 | // T_AT_VARIABLES_START |
---|
438 | // T_VARIABLE_DECLARATION |
---|
439 | // T_AT_VARIABLES_END |
---|
440 | } |
---|
441 | } |
---|
442 | return $r; |
---|
443 | } |
---|
444 | /** |
---|
445 | * Parses the Css and returns a array of tokens. |
---|
446 | * |
---|
447 | * @param string $css |
---|
448 | * @return array |
---|
449 | */ |
---|
450 | public static function parse($css) |
---|
451 | { |
---|
452 | // Settings |
---|
453 | $sDefaultScope = array("all"); // Default scope |
---|
454 | $sDefaultTrim = " \t\n\r\0\x0B"; // Default trim charlist |
---|
455 | $sTokenChars = "@{}();:\n\"'/*,"; // Tokens triggering parser processing |
---|
456 | // Basic variables |
---|
457 | $c = null; // Current char |
---|
458 | $p = null; // Previous char |
---|
459 | $buffer = ""; // Buffer |
---|
460 | $state = array(self::T_DOCUMENT); // State stack |
---|
461 | $currentState = self::T_DOCUMENT; // Current state |
---|
462 | $scope = $sDefaultScope; // Current scope |
---|
463 | $stringChar = null; // String delimiter char |
---|
464 | $isFilterWs = true; // Filter double whitespaces? |
---|
465 | $selectors = array(); // Array with collected selectors |
---|
466 | $r = array(); // Return value |
---|
467 | // Prepare css |
---|
468 | $css = str_replace("\r\n", "\n", $css); // Windows to Unix line endings |
---|
469 | $css = str_replace("\r", "\n", $css); // Mac to Unix line endings |
---|
470 | while (strpos($css, "\n\n") !== false) |
---|
471 | { |
---|
472 | $css = str_replace("\n\n", "\n", $css); // Remove double line endings |
---|
473 | } |
---|
474 | $css = str_replace("\t", " ", $css); // Convert tabs to spaces |
---|
475 | // Parse css |
---|
476 | for ($i = 0, $l = strlen($css); $i < $l; $i++) |
---|
477 | { |
---|
478 | $c = substr($css, $i, 1); |
---|
479 | // Filter out double spaces |
---|
480 | if ($isFilterWs && $c == " " && $c == $p) |
---|
481 | { |
---|
482 | continue; |
---|
483 | } |
---|
484 | $buffer .= $c; |
---|
485 | if (strpos($sTokenChars, $c) !== false) |
---|
486 | { |
---|
487 | // |
---|
488 | $currentState = $state[count($state) - 1]; |
---|
489 | /* |
---|
490 | * Start of comment |
---|
491 | */ |
---|
492 | if ($p == "/" && $c == "*" && $currentState != self::T_STRING && $currentState != self::T_COMMENT) |
---|
493 | { |
---|
494 | $saveBuffer = substr($buffer, 0, -2); // save the buffer (will get restored with comment ending) |
---|
495 | $buffer = $c; |
---|
496 | $isFilterWs = false; |
---|
497 | array_push($state, self::T_COMMENT); |
---|
498 | } |
---|
499 | /* |
---|
500 | * End of comment |
---|
501 | */ |
---|
502 | elseif ($p == "*" && $c == "/" && $currentState == self::T_COMMENT) |
---|
503 | { |
---|
504 | $r[] = array(self::T_COMMENT, trim($buffer)); |
---|
505 | $buffer = $saveBuffer; |
---|
506 | $isFilterWs = true; |
---|
507 | array_pop($state); |
---|
508 | } |
---|
509 | /* |
---|
510 | * Start of string |
---|
511 | */ |
---|
512 | elseif (($c == "\"" || $c == "'") && $currentState != self::T_STRING && $currentState != self::T_COMMENT && $currentState != self::T_STRING_URL) |
---|
513 | { |
---|
514 | $stringChar = $c; |
---|
515 | $isFilterWs = false; |
---|
516 | array_push($state, self::T_STRING); |
---|
517 | } |
---|
518 | /** |
---|
519 | * Escaped LF in string => remove escape backslash and LF |
---|
520 | */ |
---|
521 | elseif ($c == "\n" && $p == "\\" && $currentState == self::T_STRING) |
---|
522 | { |
---|
523 | $buffer = substr($buffer, 0, -2); |
---|
524 | } |
---|
525 | /* |
---|
526 | * End of string |
---|
527 | */ |
---|
528 | elseif ($c === $stringChar && $currentState == self::T_STRING) |
---|
529 | { |
---|
530 | if ($p == "\\") // Previous char is a escape char |
---|
531 | { |
---|
532 | $count = 1; |
---|
533 | $i2 = $i -2; |
---|
534 | while (substr($css, $i2, 1) == "\\") |
---|
535 | { |
---|
536 | $count++; |
---|
537 | $i2--; |
---|
538 | } |
---|
539 | // if count of escape chars is uneven => continue with string... |
---|
540 | if ($count % 2) |
---|
541 | { |
---|
542 | continue; |
---|
543 | } |
---|
544 | } |
---|
545 | // ...else end the string |
---|
546 | $isFilterWs = true; |
---|
547 | array_pop($state); |
---|
548 | $stringChar = null; |
---|
549 | } |
---|
550 | /** |
---|
551 | * Start of url string property |
---|
552 | */ |
---|
553 | elseif ($c == "(" && ($currentState != self::T_COMMENT && $currentState != self::T_STRING) && strtolower(substr($css, $i - 3, 3) == "url") |
---|
554 | && ($currentState == self::T_DECLARATION || $currentState == self::T_FONT_FACE_DECLARATION || $currentState == self::T_PAGE_DECLARATION || $currentState == self::T_VARIABLE_DECLARATION)) |
---|
555 | { |
---|
556 | array_push($state, self::T_STRING_URL); |
---|
557 | } |
---|
558 | /** |
---|
559 | * End of url string property |
---|
560 | */ |
---|
561 | elseif (($c == ")" || $c == "\n") && ($currentState != self::T_COMMENT && $currentState != self::T_STRING) && $currentState == self::T_STRING_URL) |
---|
562 | { |
---|
563 | if ($p == "\\") |
---|
564 | { |
---|
565 | continue; |
---|
566 | } |
---|
567 | array_pop($state); |
---|
568 | } |
---|
569 | /* |
---|
570 | * Start of at-rule @media block |
---|
571 | */ |
---|
572 | elseif ($c == "@" && $currentState == self::T_DOCUMENT && strtolower(substr($css, $i, 6)) == "@media") |
---|
573 | { |
---|
574 | $i = $i + 6; |
---|
575 | $buffer = ""; |
---|
576 | array_push($state, self::T_AT_MEDIA_START); |
---|
577 | } |
---|
578 | /* |
---|
579 | * At-rule @media block media types |
---|
580 | */ |
---|
581 | elseif ($c == "{" && $currentState == self::T_AT_MEDIA_START) |
---|
582 | { |
---|
583 | $buffer = strtolower(trim($buffer, $sDefaultTrim . "{")); |
---|
584 | $scope = $buffer != "" ? array_filter(array_map("trim", explode(",", $buffer))) : $sDefaultScope; |
---|
585 | $r[] = array(self::T_AT_MEDIA_START, $scope); |
---|
586 | $i = $i++; |
---|
587 | $buffer = ""; |
---|
588 | array_pop($state); |
---|
589 | array_push($state, self::T_AT_MEDIA); |
---|
590 | } |
---|
591 | /* |
---|
592 | * End of at-rule @media block |
---|
593 | */ |
---|
594 | elseif ($currentState == self::T_AT_MEDIA && $c == "}") |
---|
595 | { |
---|
596 | $r[] = array(self::T_AT_MEDIA_END); |
---|
597 | $scope = $sDefaultScope; |
---|
598 | $buffer = ""; |
---|
599 | array_pop($state); |
---|
600 | } |
---|
601 | /* |
---|
602 | * Start of at-rule @font-face block |
---|
603 | */ |
---|
604 | elseif ($c == "@" && $currentState == self::T_DOCUMENT && strtolower(substr($css, $i, 10)) == "@font-face") |
---|
605 | { |
---|
606 | $r[] = array(self::T_AT_FONT_FACE_START); |
---|
607 | $i = $i + 10; |
---|
608 | $buffer = ""; |
---|
609 | array_push($state, self::T_AT_FONT_FACE); |
---|
610 | } |
---|
611 | /* |
---|
612 | * @font-face declaration: Property |
---|
613 | */ |
---|
614 | elseif ($c == ":" && $currentState == self::T_AT_FONT_FACE) |
---|
615 | { |
---|
616 | $property = trim($buffer, $sDefaultTrim . ":{"); |
---|
617 | $buffer = ""; |
---|
618 | array_push($state, self::T_FONT_FACE_DECLARATION); |
---|
619 | } |
---|
620 | /* |
---|
621 | * @font-face declaration: Value |
---|
622 | */ |
---|
623 | elseif (($c == ";" || $c == "}" || $c == "\n") && $currentState == self::T_FONT_FACE_DECLARATION) |
---|
624 | { |
---|
625 | $value = trim($buffer, $sDefaultTrim . ";}"); |
---|
626 | $r[] = array(self::T_FONT_FACE_DECLARATION, $property, $value, $scope); |
---|
627 | $buffer = ""; |
---|
628 | array_pop($state); |
---|
629 | if ($c == "}") // @font-face declaration closed with a right curly brace => closes @font-face block |
---|
630 | { |
---|
631 | array_pop($state); |
---|
632 | $r[] = array(self::T_AT_FONT_FACE_END); |
---|
633 | } |
---|
634 | } |
---|
635 | /* |
---|
636 | * End of at-rule @font-face block |
---|
637 | */ |
---|
638 | elseif ($c == "}" && $currentState == self::T_AT_FONT_FACE) |
---|
639 | { |
---|
640 | $r[] = array(self::T_AT_FONT_FACE_END); |
---|
641 | $buffer = ""; |
---|
642 | array_pop($state); |
---|
643 | } |
---|
644 | /* |
---|
645 | * Start of at-rule @page block |
---|
646 | */ |
---|
647 | elseif ($c == "@" && $currentState == self::T_DOCUMENT && strtolower(substr($css, $i, 5)) == "@page") |
---|
648 | { |
---|
649 | $r[] = array(self::T_AT_PAGE_START); |
---|
650 | $i = $i + 5; |
---|
651 | $buffer = ""; |
---|
652 | array_push($state, self::T_AT_PAGE); |
---|
653 | } |
---|
654 | /* |
---|
655 | * @page declaration: Property |
---|
656 | */ |
---|
657 | elseif ($c == ":" && $currentState == self::T_AT_PAGE) |
---|
658 | { |
---|
659 | $property = trim($buffer, $sDefaultTrim . ":{"); |
---|
660 | $buffer = ""; |
---|
661 | array_push($state, self::T_PAGE_DECLARATION); |
---|
662 | } |
---|
663 | /* |
---|
664 | * @page declaration: Value |
---|
665 | */ |
---|
666 | elseif (($c == ";" || $c == "}" || $c == "\n") && $currentState == self::T_PAGE_DECLARATION) |
---|
667 | { |
---|
668 | $value = trim($buffer, $sDefaultTrim . ";}"); |
---|
669 | $r[] = array(self::T_PAGE_DECLARATION, $property, $value, $scope); |
---|
670 | $buffer = ""; |
---|
671 | array_pop($state); |
---|
672 | if ($c == "}") // @page declaration closed with a right curly brace => closes @font-face block |
---|
673 | { |
---|
674 | array_pop($state); |
---|
675 | $r[] = array(self::T_AT_PAGE_END); |
---|
676 | } |
---|
677 | } |
---|
678 | /* |
---|
679 | * End of at-rule @page block |
---|
680 | */ |
---|
681 | elseif ($c == "}" && $currentState == self::T_AT_PAGE) |
---|
682 | { |
---|
683 | $r[] = array(self::T_AT_PAGE_END); |
---|
684 | $buffer = ""; |
---|
685 | array_pop($state); |
---|
686 | } |
---|
687 | /* |
---|
688 | * Start of at-rule @variables block |
---|
689 | */ |
---|
690 | elseif ($c == "@" && $currentState == self::T_DOCUMENT && strtolower(substr($css, $i, 10)) == "@variables") |
---|
691 | { |
---|
692 | $i = $i + 10; |
---|
693 | $buffer = ""; |
---|
694 | array_push($state, self::T_AT_VARIABLES_START); |
---|
695 | } |
---|
696 | /* |
---|
697 | * @variables media types |
---|
698 | */ |
---|
699 | elseif ($c == "{" && $currentState == self::T_AT_VARIABLES_START) |
---|
700 | { |
---|
701 | $buffer = strtolower(trim($buffer, $sDefaultTrim . "{")); |
---|
702 | $r[] = array(self::T_AT_VARIABLES_START, $scope); |
---|
703 | $scope = $buffer != "" ? array_filter(array_map("trim", explode(",", $buffer))) : $sDefaultScope; |
---|
704 | $i = $i++; |
---|
705 | $buffer = ""; |
---|
706 | array_pop($state); |
---|
707 | array_push($state, self::T_AT_VARIABLES); |
---|
708 | } |
---|
709 | /* |
---|
710 | * @variables declaration: Property |
---|
711 | */ |
---|
712 | elseif ($c == ":" && $currentState == self::T_AT_VARIABLES) |
---|
713 | { |
---|
714 | $property = trim($buffer, $sDefaultTrim . ":"); |
---|
715 | $buffer = ""; |
---|
716 | array_push($state, self::T_VARIABLE_DECLARATION); |
---|
717 | } |
---|
718 | /* |
---|
719 | * @variables declaration: Value |
---|
720 | */ |
---|
721 | elseif (($c == ";" || $c == "}" || $c == "\n") && $currentState == self::T_VARIABLE_DECLARATION) |
---|
722 | { |
---|
723 | $value = trim($buffer, $sDefaultTrim . ";}"); |
---|
724 | $r[] = array(self::T_VARIABLE_DECLARATION, $property, $value, $scope); |
---|
725 | $buffer = ""; |
---|
726 | array_pop($state); |
---|
727 | if ($c == "}") // @variable declaration closed with a right curly brace => closes @variables block |
---|
728 | { |
---|
729 | array_pop($state); |
---|
730 | $r[] = array(self::T_AT_VARIABLES_END); |
---|
731 | $scope = $sDefaultScope; |
---|
732 | } |
---|
733 | } |
---|
734 | /* |
---|
735 | * End of at-rule @variables block |
---|
736 | */ |
---|
737 | elseif ($c == "}" && $currentState == self::T_AT_VARIABLES) |
---|
738 | { |
---|
739 | $r[] = array(self::T_AT_VARIABLES_END); |
---|
740 | $scope = $sDefaultScope; |
---|
741 | $buffer = ""; |
---|
742 | array_pop($state); |
---|
743 | } |
---|
744 | /* |
---|
745 | * Start of document level at-rule |
---|
746 | */ |
---|
747 | elseif ($c == "@" && $currentState == self::T_DOCUMENT) |
---|
748 | { |
---|
749 | $buffer = ""; |
---|
750 | array_push($state, self::T_AT_RULE); |
---|
751 | } |
---|
752 | /* |
---|
753 | * End of document level at-rule |
---|
754 | */ |
---|
755 | elseif ($c == ";" && $currentState == self::T_AT_RULE) |
---|
756 | { |
---|
757 | $pos = strpos($buffer, " "); |
---|
758 | $rule = substr($buffer, 0, $pos); |
---|
759 | $value = trim(substr($buffer, $pos), $sDefaultTrim . ";"); |
---|
760 | $r[] = array(self::T_AT_RULE, $rule, $value); |
---|
761 | $buffer = ""; |
---|
762 | array_pop($state); |
---|
763 | } |
---|
764 | /** |
---|
765 | * Selector |
---|
766 | */ |
---|
767 | elseif ($c == "," && ($currentState == self::T_AT_MEDIA || $currentState == self::T_DOCUMENT)) |
---|
768 | { |
---|
769 | $selectors[]= trim($buffer, $sDefaultTrim . ","); |
---|
770 | $buffer = ""; |
---|
771 | } |
---|
772 | /* |
---|
773 | * Start of ruleset |
---|
774 | */ |
---|
775 | elseif ($c == "{" && ($currentState == self::T_AT_MEDIA || $currentState == self::T_DOCUMENT)) |
---|
776 | { |
---|
777 | $selectors[]= trim($buffer, $sDefaultTrim . "{"); |
---|
778 | $selectors = array_filter(array_map("trim", $selectors)); |
---|
779 | $r[] = array(self::T_RULESET_START); |
---|
780 | $r[] = array(self::T_SELECTORS, $selectors); |
---|
781 | $r[] = array(self::T_DECLARATIONS_START); |
---|
782 | $buffer = ""; |
---|
783 | $selectors = array(); |
---|
784 | array_push($state, self::T_DECLARATIONS); |
---|
785 | } |
---|
786 | /* |
---|
787 | * Declaration: Property |
---|
788 | */ |
---|
789 | elseif ($c == ":" && $currentState == self::T_DECLARATIONS) |
---|
790 | { |
---|
791 | $property = trim($buffer, $sDefaultTrim . ":;"); |
---|
792 | $buffer = ""; |
---|
793 | array_push($state, self::T_DECLARATION); |
---|
794 | } |
---|
795 | /* |
---|
796 | * Declaration: Value |
---|
797 | */ |
---|
798 | elseif (($c == ";" || $c == "}" || $c == "\n") && $currentState == self::T_DECLARATION) |
---|
799 | { |
---|
800 | $value = trim($buffer, $sDefaultTrim . ";}"); |
---|
801 | $r[] = array(self::T_DECLARATION, $property, $value, $scope); |
---|
802 | $buffer = ""; |
---|
803 | array_pop($state); |
---|
804 | if ($c == "}") // declaration closed with a right curly brace => close ruleset |
---|
805 | { |
---|
806 | array_pop($state); |
---|
807 | $r[] = array(self::T_DECLARATIONS_END); |
---|
808 | $r[] = array(self::T_RULESET_END); |
---|
809 | } |
---|
810 | } |
---|
811 | /* |
---|
812 | * End of ruleset |
---|
813 | */ |
---|
814 | elseif ($c == "}" && $currentState == self::T_DECLARATIONS) |
---|
815 | { |
---|
816 | $r[] = array(self::T_DECLARATIONS_END); |
---|
817 | $r[] = array(self::T_RULESET_END); |
---|
818 | $buffer = ""; |
---|
819 | array_pop($state); |
---|
820 | } |
---|
821 | |
---|
822 | } |
---|
823 | |
---|
824 | $p = $c; |
---|
825 | } |
---|
826 | return $r; |
---|
827 | } |
---|
828 | /** |
---|
829 | * Transforms "opacity: {value}" into browser specific counterparts. |
---|
830 | * |
---|
831 | * @param string $property |
---|
832 | * @param string $value |
---|
833 | * @return string |
---|
834 | */ |
---|
835 | private static function _tOpacity($property, $value) |
---|
836 | { |
---|
837 | $ieValue = (int) ((float) $value * 100); |
---|
838 | $r = "-moz-opacity:" . $value . ";"; // Firefox < 3.5 |
---|
839 | $r .= "-ms-filter: \"alpha(opacity=" . $ieValue . ")\";"; // Internet Explorer 8 |
---|
840 | $r .= "filter: alpha(opacity=" . $ieValue . ");zoom: 1;"; // Internet Explorer 4 - 7 |
---|
841 | return $r; |
---|
842 | } |
---|
843 | /** |
---|
844 | * Transforms "white-space: pre-wrap" into browser specific counterparts. |
---|
845 | * |
---|
846 | * @param string $property |
---|
847 | * @param string $value |
---|
848 | * @return string |
---|
849 | */ |
---|
850 | private static function _tWhiteSpacePreWrap($property, $value) |
---|
851 | { |
---|
852 | if (strtolower($value) == "pre-wrap") |
---|
853 | { |
---|
854 | $r = "white-space:-moz-pre-wrap;"; // Mozilla |
---|
855 | $r .= "white-space:-webkit-pre-wrap;"; // Webkit |
---|
856 | $r .= "white-space:-khtml-pre-wrap;"; // khtml |
---|
857 | $r .= "white-space:-pre-wrap;"; // Opera 4 - 6 |
---|
858 | $r .= "white-space:-o-pre-wrap;"; // Opera 7+ |
---|
859 | $r .= "word-wrap:break-word;"; // Internet Explorer 5.5+ |
---|
860 | return $r; |
---|
861 | } |
---|
862 | else |
---|
863 | { |
---|
864 | return ""; |
---|
865 | } |
---|
866 | } |
---|
867 | } |
---|
868 | ?> |
---|