mirror of
https://github.com/mrclay/minify.git
synced 2025-03-14 17:39: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:
parent
d2746cb43c
commit
08196a7ead
lib/Minify
web/test/_test_files/css
@ -97,6 +97,8 @@ class Minify_CSS {
|
|||||||
*/
|
*/
|
||||||
protected static function _minify($css, $options)
|
protected static function _minify($css, $options)
|
||||||
{
|
{
|
||||||
|
$css = str_replace("\r\n", "\n", $css);
|
||||||
|
|
||||||
// preserve empty comment after '>'
|
// preserve empty comment after '>'
|
||||||
// http://www.webdevout.net/css-hacks#in_css-selectors
|
// http://www.webdevout.net/css-hacks#in_css-selectors
|
||||||
$css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css);
|
$css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css);
|
||||||
@ -117,21 +119,46 @@ class Minify_CSS {
|
|||||||
// leave needed comments
|
// leave needed comments
|
||||||
$css = str_replace('/*keep*/', '/**/', $css);
|
$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);
|
||||||
$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);
|
$css = preg_replace('/\\s*;\\s*/', ';', $css);
|
||||||
|
|
||||||
// remove ws around urls
|
// 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
|
// 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
|
// 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);
|
,array('Minify_CSS', '_selectorsCB'), $css);
|
||||||
|
|
||||||
// minimize hex colors
|
// minimize hex colors
|
||||||
@ -151,7 +178,7 @@ class Minify_CSS {
|
|||||||
$rewrite = true;
|
$rewrite = true;
|
||||||
}
|
}
|
||||||
if ($rewrite) {
|
if ($rewrite) {
|
||||||
$css = preg_replace_callback('/@import ([\'"])(.*?)[\'"]\\s*;/'
|
$css = preg_replace_callback('/@import ([\'"])(.*?)[\'"]/'
|
||||||
,array('Minify_CSS', '_urlCB'), $css);
|
,array('Minify_CSS', '_urlCB'), $css);
|
||||||
$css = preg_replace_callback('/url\\(([^\\)]+)\\)/'
|
$css = preg_replace_callback('/url\\(([^\\)]+)\\)/'
|
||||||
,array('Minify_CSS', '_urlCB'), $css);
|
,array('Minify_CSS', '_urlCB'), $css);
|
||||||
@ -178,7 +205,7 @@ class Minify_CSS {
|
|||||||
protected static $_tempCurrentPath = '';
|
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
|
* @param array $m regex matches
|
||||||
*
|
*
|
||||||
@ -187,31 +214,41 @@ class Minify_CSS {
|
|||||||
protected static function _commentCB($m)
|
protected static function _commentCB($m)
|
||||||
{
|
{
|
||||||
$m = $m[1];
|
$m = $m[1];
|
||||||
// $m is everything after the opening tokens and before the closing
|
// $m is the comment content w/o the surrounding tokens,
|
||||||
// tokens but return will replace the entire comment.
|
// but the return value will replace the entire comment.
|
||||||
if ($m === 'keep') {
|
if ($m === 'keep') {
|
||||||
return '/*keep*/';
|
return '/*keep*/';
|
||||||
}
|
}
|
||||||
if (self::$_inHack) {
|
if (self::$_inHack) {
|
||||||
// inversion: feeding only to one browser
|
// 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;
|
self::$_inHack = false;
|
||||||
return "/*/{$n[1]}/*keep*/";
|
return "/*/{$n[1]}/*keep*/";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (substr($m, -1) === '\\') {
|
if (substr($m, -1) === '\\') { // comment ends like \*/
|
||||||
|
// begin hack mode and preserve hack
|
||||||
self::$_inHack = true;
|
self::$_inHack = true;
|
||||||
return '/*\\*/';
|
return '/*\\*/';
|
||||||
}
|
}
|
||||||
if ($m !== '' && $m[0] === '/') {
|
if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */
|
||||||
|
// begin hack mode and preserve hack
|
||||||
self::$_inHack = true;
|
self::$_inHack = true;
|
||||||
return '/*/*/';
|
return '/*/*/';
|
||||||
}
|
}
|
||||||
if (self::$_inHack) {
|
if (self::$_inHack) {
|
||||||
|
// a regular comment ends hack mode but should be preserved
|
||||||
self::$_inHack = false;
|
self::$_inHack = false;
|
||||||
return '/*keep*/';
|
return '/*keep*/';
|
||||||
}
|
}
|
||||||
return '';
|
return ''; // remove all other comments
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -223,6 +260,7 @@ class Minify_CSS {
|
|||||||
*/
|
*/
|
||||||
protected static function _selectorsCB($m)
|
protected static function _selectorsCB($m)
|
||||||
{
|
{
|
||||||
|
// remove ws around the combinators
|
||||||
return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]);
|
return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,7 +271,8 @@ class Minify_CSS {
|
|||||||
$quote = $m[1];
|
$quote = $m[1];
|
||||||
$url = $m[2];
|
$url = $m[2];
|
||||||
} else {
|
} 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] === '"')
|
$quote = ($m[1][0] === "'" || $m[1][0] === '"')
|
||||||
? $m[1][0]
|
? $m[1][0]
|
||||||
: '';
|
: '';
|
||||||
@ -262,7 +301,7 @@ class Minify_CSS {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($isImport) {
|
if ($isImport) {
|
||||||
return "@import {$quote}{$url}{$quote};";
|
return "@import {$quote}{$url}{$quote}";
|
||||||
} else {
|
} else {
|
||||||
return "url({$quote}{$url}{$quote})";
|
return "url({$quote}{$url}{$quote})";
|
||||||
}
|
}
|
||||||
@ -277,7 +316,15 @@ class Minify_CSS {
|
|||||||
*/
|
*/
|
||||||
protected static function _fontFamilyCB($m)
|
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];
|
return 'font-family:' . $m[1] . $m[2];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,3 +50,8 @@ div {
|
|||||||
}
|
}
|
||||||
/**/ /* end hidden from IE 5 Mac */
|
/**/ /* end hidden from IE 5 Mac */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foo { /* filters for IE */
|
||||||
|
_height : 20px;
|
||||||
|
*height : 15px;
|
||||||
|
}
|
||||||
|
2
web/test/_test_files/css/hacks.min.css
vendored
2
web/test/_test_files/css/hacks.min.css
vendored
@ -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}
|
@ -1,8 +1,8 @@
|
|||||||
@import "foo.css";
|
@import "foo.css";
|
||||||
@import 'bar/foo.css';
|
@import 'bar/foo.css' print;
|
||||||
@import '/css/foo.css'; /* abs, should not alter */
|
@import '/css/foo.css'; /* abs, should not alter */
|
||||||
@import 'http://foo.com/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("/css/foo.css"); /* abs, should not alter */
|
||||||
@import url(/css2/foo.css); /* abs, should not alter */
|
@import url(/css2/foo.css); /* abs, should not alter */
|
||||||
foo {background:url('bar/foo.png')}
|
foo {background:url('bar/foo.png')}
|
||||||
|
2
web/test/_test_files/css/paths.min.css
vendored
2
web/test/_test_files/css/paths.min.css
vendored
@ -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")}
|
10
web/test/_test_files/css/unusual_strings.css
Normal file
10
web/test/_test_files/css/unusual_strings.css
Normal 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\"";
|
||||||
|
}
|
1
web/test/_test_files/css/unusual_strings.min.css
vendored
Normal file
1
web/test/_test_files/css/unusual_strings.min.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
foo[attr="multiple spaces"]{content:"Hello World!"}foo[attr="Hello"]{content:" \"World\""}
|
Loading…
x
Reference in New Issue
Block a user