1
0
mirror of https://github.com/mrclay/minify.git synced 2025-03-13 17:09:39 +01:00

Minify_CSS: URIs can be rewritten inside @imports with media types, trims around properties beginning with * or _ (targeting IE), more thorough internal docs (inc regexes), all Unix newlines

+ tests for the above changes
+ test for unusual but valid strings in style sheets (currently FAILs)
This commit is contained in:
Steve Clay 2008-08-14 04:24:41 +00:00
parent d2746cb43c
commit 08196a7ead
7 changed files with 84 additions and 21 deletions

View File

@ -97,6 +97,8 @@ class Minify_CSS {
*/
protected static function _minify($css, $options)
{
$css = str_replace("\r\n", "\n", $css);
// preserve empty comment after '>'
// http://www.webdevout.net/css-hacks#in_css-selectors
$css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css);
@ -117,21 +119,46 @@ class Minify_CSS {
// leave needed comments
$css = str_replace('/*keep*/', '/**/', $css);
// remove ws around { }
// remove ws around { } and last semicolon in declaration block
$css = preg_replace('/\\s*{\\s*/', '{', $css);
$css = preg_replace('/;?\\s*}\\s*/', '}', $css);
// remove ws between rules
// remove ws surrounding semicolons
$css = preg_replace('/\\s*;\\s*/', ';', $css);
// remove ws around urls
$css = preg_replace('/url\\([\\s]*([^\\)]+?)[\\s]*\\)/', 'url($1)', $css);
$css = preg_replace('/
url\\( # url(
\\s*
([^\\)]+?) # 1 = the URL (really just a bunch of non right parenthesis)
\\s*
\\) # )
/x', 'url($1)', $css);
// remove ws between rules and colons
$css = preg_replace('/\\s*([{;])\\s*([\\w\\-]+)\\s*:\\s*(\\b|[#\'"])/', '$1$2:$3', $css);
$css = preg_replace('/
\\s*
([{;]) # 1 = beginning of block or rule separator
\\s*
([\\*_]?[\\w\\-]+) # 2 = property (and maybe IE filter)
\\s*
:
\\s*
(\\b|[#\'"]) # 3 = first character of a value
/x', '$1$2:$3', $css);
// remove ws in selectors
$css = preg_replace_callback('/(?:\\s*[^~>+,\\s]+\\s*[,>+~])+\\s*[^~>+,\\s]+{/'
$css = preg_replace_callback('/
(?: # non-capture
\\s*
[^~>+,\\s]+ # selector part
\\s*
[,>+~] # combinators
)+
\\s*
[^~>+,\\s]+ # selector part
{ # open declaration block
/x'
,array('Minify_CSS', '_selectorsCB'), $css);
// minimize hex colors
@ -151,7 +178,7 @@ class Minify_CSS {
$rewrite = true;
}
if ($rewrite) {
$css = preg_replace_callback('/@import ([\'"])(.*?)[\'"]\\s*;/'
$css = preg_replace_callback('/@import ([\'"])(.*?)[\'"]/'
,array('Minify_CSS', '_urlCB'), $css);
$css = preg_replace_callback('/url\\(([^\\)]+)\\)/'
,array('Minify_CSS', '_urlCB'), $css);
@ -178,7 +205,7 @@ class Minify_CSS {
protected static $_tempCurrentPath = '';
/**
* Process what looks like a comment and return a replacement
* Process a comment and return a replacement
*
* @param array $m regex matches
*
@ -187,31 +214,41 @@ class Minify_CSS {
protected static function _commentCB($m)
{
$m = $m[1];
// $m is everything after the opening tokens and before the closing
// tokens but return will replace the entire comment.
// $m is the comment content w/o the surrounding tokens,
// but the return value will replace the entire comment.
if ($m === 'keep') {
return '/*keep*/';
}
if (self::$_inHack) {
// inversion: feeding only to one browser
if (preg_match('@^/\\s*(\\S[\\s\\S]+?)\\s*/\\*@', $m, $n)) {
if (preg_match('@
^/ # comment started like /*/
\\s*
(\\S[\\s\\S]+?) # has at least some non-ws content
\\s*
/\\* # ends like /*/ or /**/
@x', $m, $n)) {
// end hack mode after this comment, but preserve the hack and comment content
self::$_inHack = false;
return "/*/{$n[1]}/*keep*/";
}
}
if (substr($m, -1) === '\\') {
if (substr($m, -1) === '\\') { // comment ends like \*/
// begin hack mode and preserve hack
self::$_inHack = true;
return '/*\\*/';
}
if ($m !== '' && $m[0] === '/') {
if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */
// begin hack mode and preserve hack
self::$_inHack = true;
return '/*/*/';
}
if (self::$_inHack) {
// a regular comment ends hack mode but should be preserved
self::$_inHack = false;
return '/*keep*/';
}
return '';
return ''; // remove all other comments
}
/**
@ -223,6 +260,7 @@ class Minify_CSS {
*/
protected static function _selectorsCB($m)
{
// remove ws around the combinators
return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]);
}
@ -233,7 +271,8 @@ class Minify_CSS {
$quote = $m[1];
$url = $m[2];
} else {
// $m[1] is surrounded by quotes or not
// is url()
// $m[1] is either quoted or not
$quote = ($m[1][0] === "'" || $m[1][0] === '"')
? $m[1][0]
: '';
@ -262,7 +301,7 @@ class Minify_CSS {
}
}
if ($isImport) {
return "@import {$quote}{$url}{$quote};";
return "@import {$quote}{$url}{$quote}";
} else {
return "url({$quote}{$url}{$quote})";
}
@ -277,7 +316,15 @@ class Minify_CSS {
*/
protected static function _fontFamilyCB($m)
{
$m[1] = preg_replace('/\\s*("[^"]+"|\'[^\']+\'|[\\w\\-]+)\\s*/', '$1', $m[1]);
$m[1] = preg_replace('/
\\s*
(
"[^"]+" # 1 = family in double qutoes
|\'[^\']+\' # or 1 = family in single quotes
|[\\w\\-]+ # or 1 = unquoted family
)
\\s*
/x', '$1', $m[1]);
return 'font-family:' . $m[1] . $m[2];
}
}

View File

@ -49,4 +49,9 @@ div {
height: 1%;
}
/**/ /* end hidden from IE 5 Mac */
}
}
foo { /* filters for IE */
_height : 20px;
*height : 15px;
}

View File

@ -1 +1 @@
/*\*/a{}.foo{color:red}/**//*\*//*/@import "ie5mac.css";/**//*/*/.foo{display:block}/**//*/*//*/.foo{display:crazy}/**/div{width:140px;width/**/:/**/100px;width:/**/100px}html>/**/body{}div{width:400px;voice-family:"\"}\"";voice-family:inherit;width:300px}div{filter:chroma(color=#aabbcc);filter:mask(color=#000000) shadow(color=#9BAD71, direction=135) chroma(color=#000000)}@media screen{/*\*/* html div#page{height:1%}/**/}
/*\*/a{}.foo{color:red}/**//*\*//*/@import "ie5mac.css";/**//*/*/.foo{display:block}/**//*/*//*/.foo{display:crazy}/**/div{width:140px;width/**/:/**/100px;width:/**/100px}html>/**/body{}div{width:400px;voice-family:"\"}\"";voice-family:inherit;width:300px}div{filter:chroma(color=#aabbcc);filter:mask(color=#000000) shadow(color=#9BAD71, direction=135) chroma(color=#000000)}@media screen{/*\*/* html div#page{height:1%}/**/}foo{_height:20px;*height:15px}

View File

@ -1,8 +1,8 @@
@import "foo.css";
@import 'bar/foo.css';
@import 'bar/foo.css' print;
@import '/css/foo.css'; /* abs, should not alter */
@import 'http://foo.com/css/foo.css'; /* abs, should not alter */
@import url(../foo.css);
@import url(../foo.css) tv, projection;
@import url("/css/foo.css"); /* abs, should not alter */
@import url(/css2/foo.css); /* abs, should not alter */
foo {background:url('bar/foo.png')}

View File

@ -1 +1 @@
@import "../foo.css";@import '../bar/foo.css';@import '/css/foo.css';@import 'http://foo.com/css/foo.css';@import url(../../foo.css);@import url("/css/foo.css");@import url(/css2/foo.css);foo{background:url('../bar/foo.png')}foo{background:url('http://foo.com/css/foo.css')}foo{background:url("//foo.com/css/foo.css")}
@import "../foo.css";@import '../bar/foo.css' print;@import '/css/foo.css';@import 'http://foo.com/css/foo.css';@import url(../../foo.css) tv, projection;@import url("/css/foo.css");@import url(/css2/foo.css);foo{background:url('../bar/foo.png')}foo{background:url('http://foo.com/css/foo.css')}foo{background:url("//foo.com/css/foo.css")}

View File

@ -0,0 +1,10 @@
/* test unusual, but valid strings in CSS */
foo[attr="multiple spaces"] {
content: "Hello World!";
}
foo[attr="Hel\
lo"] {
content: " \"World\"";
}

View File

@ -0,0 +1 @@
foo[attr="multiple spaces"]{content:"Hello World!"}foo[attr="Hello"]{content:" \"World\""}