在PHP你可以压缩/缩小CSS与regex (PCRE)?
(作为regex中的理论。我相信有一些库在这方面做得很好。
背景说明:在花了几个小时写了一个已删除(半垃圾)问题的答案后,我想我应该发布一部分潜在问题并自己回答它。
简单正则表达式CSS压缩器
(好吧,这可能不是太简单,但很直接。)
需求这个答案假设需求是:
- 删除评论
- 将大于1个空格的空格组合替换为单个空格
- 删除元字符周围的所有空白:
{
,}
,;
,,
,>
,~
,+
,-
- 删除
!important
周围的空格 - 删除
:
周围的空格,除了在选择器(你必须在它之前保持一个空格) - 删除
$=
等操作符周围的空格 - 删除
(
/[
右侧和)
/]
左侧的所有空格 - 删除字符串 开头和结尾的所有空格
- 删除块中的最后一个
;
- 不要修改字符串
- 不需要处理无效的CSS
注意,这里的要求不包括将CSS属性转换为更短的版本(比如使用速记属性而不是几个完整长度的属性,删除不需要的引号)。这是一般情况下regex无法解决的问题。
解决方案通过两步解决这个问题更容易:首先删除注释,然后删除其他所有内容。
应该可以在一次传递中完成,但随后您必须将所有s
替换为匹配空格和注释的表达式(以及其他一些修改)。
删除注释的第一个传递表达式:
(?xs)
# quotes
(
"(?:[^"\]++|\.)*+"
| '(?:[^'\]++|\.)*+'
)
|
# comments
/* (?> .*? */ )
用$1
代替
要删除其他所有内容,可以使用:
(?six)
# quotes
(
"(?:[^"\]++|\.)*+"
| '(?:[^'\]++|\.)*+'
)
|
# ; before } (and the spaces after it while we're here)
s*+ ; s*+ ( } ) s*+
|
# all spaces around meta chars/operators
s*+ ( [*$~^|]?+= | [{};,>~+-] | !importantb ) s*+
|
# spaces right of ( [ :
( [[(:] ) s++
|
# spaces left of ) ]
s++ ( [])] )
|
# spaces left (and right) of :
s++ ( : ) s*+
# but not in selectors: not followed by a {
(?!
(?>
[^{}"']++
| "(?:[^"\]++|\.)*+"
| '(?:[^'\]++|\.)*+'
)*+
{
)
|
# spaces at beginning/end of string
^ s++ | s++ z
|
# double spaces to single
(s)s+
替换为$1$2$3$4$5$6$7
.
与正确的解析器相比,选择器检查是否删除:
之前的空格(负向前看)可能会减慢此速度。解析器已经知道它们是否在选择器中,并且不需要做额外的搜索来检查。
PHP中的示例实现
function minify_css($str){
# remove comments first (simplifies the other regex)
$re1 = <<<'EOS'
(?sx)
# quotes
(
"(?:[^"\]++|\.)*+"
| '(?:[^'\]++|\.)*+'
)
|
# comments
/* (?> .*? */ )
EOS;
$re2 = <<<'EOS'
(?six)
# quotes
(
"(?:[^"\]++|\.)*+"
| '(?:[^'\]++|\.)*+'
)
|
# ; before } (and the spaces after it while we're here)
s*+ ; s*+ ( } ) s*+
|
# all spaces around meta chars/operators
s*+ ( [*$~^|]?+= | [{};,>~+-] | !importantb ) s*+
|
# spaces right of ( [ :
( [[(:] ) s++
|
# spaces left of ) ]
s++ ( [])] )
|
# spaces left (and right) of :
s++ ( : ) s*+
# but not in selectors: not followed by a {
(?!
(?>
[^{}"']++
| "(?:[^"\]++|\.)*+"
| '(?:[^'\]++|\.)*+'
)*+
{
)
|
# spaces at beginning/end of string
^ s++ | s++ z
|
# double spaces to single
(s)s+
EOS;
$str = preg_replace("%$re1%", '$1', $str);
return preg_replace("%$re2%", '$1$2$3$4$5$6$7', $str);
}
快速测试
可以在ideone.com找到:
$in = <<<'EOS'
p * i , html
/* remove spaces */
/* " comments have no escapes */
body/* keep */ /* space */p,
p [ remove ~= " spaces " ] :nth-child( 3 + 2n ) > b span i , div::after
{
/* comment */
background : url( " /* string */ " ) blue !important ;
content : " escapes " allowed \" ;
width: calc( 100% - 3em + 5px ) ;
margin-top : 0;
margin-bottom : 0;
margin-left : 10px;
margin-right : 10px;
}
EOS;
$out = minify_css($in);
echo "input:n";
var_dump($in);
echo "nn";
echo "output:n";
var_dump($out);
输出:input:
string(435) "
p * i , html
/* remove spaces */
/* " comments have no escapes */
body/* keep */ /* space */p,
p [ remove ~= " spaces " ] :nth-child( 3 + 2n ) > b span i , div::after
{
/* comment */
background : url( " /* string */ " ) blue !important ;
content : " escapes " allowed \" ;
width: calc( 100% - 3em + 5px ) ;
margin-top : 0;
margin-bottom : 0;
margin-left : 10px;
margin-right : 10px;
}
"
output:
string(251) "p * i,html body p,p [remove~=" spaces "] :nth-child(3+2n)>b span i,div::after{background:url(" /* string */ ") blue!important;content:" escapes " allowed \";width:calc(100%-3em+5px);margin-top:0;margin-bottom:0;margin-left:10px;margin-right:10px}"
与
cssminifier.com
对于相同输入的cssminifier.com的结果:
p * i,html /**/body/**/p,p [remove ~= " spaces "] :nth-child(3+2n)>b span i,div::after{background:url(" /* string */ ") blue;content:" escapes " allowed \";width:calc(100% - 3em+5px);margin-top:0;margin-bottom:0;margin-left:10px;margin-right:10px}
长度263字节。比上面的正则表达式的输出长12个字节。
cssminifier.com与这个正则表达式迷你器相比有一些缺点:
- 它留下了部分评论。(这可能是有原因的。也许需要一些CSS技巧)
- 不删除某些表达式中操作符周围的空格
CSSTidy
在预设的最高压缩级别下输出CSSTidy 1.3(通过codebeautifier.com):
p * i,html /* remove spaces */
/* " comments have no escapes */
body/* keep */ /* space */p,p [ remove ~= " spaces " ] :nth-child( 3 + 2n ) > b span i,div::after{background:url(" /* string */ ") blue!important;content:" escapes " allowed \";width:calc(100%-3em+5px);margin:0 10px;}
长度286字节。比正则表达式的输出长35字节。
CSSTidy不删除某些选择器中的注释或空格。但它确实简化为速记属性。后者可能会帮助压缩普通CSS更多。
并排比较
对于相同的输入,从不同的缩小器缩小输出,如上面的例子。(剩余的换行符用空格代替)
this answern (251): p * i,html body p,p [remove~=" spaces "] :nth-child(3+2n)>b span i,div::after{background:url(" /* string */ ") blue!important;content:" escapes " allowed \";width:calc(100%-3em+5px);margin-top:0;margin-bottom:0;margin-left:10px;margin-right:10px}
cssminifier.com (263): p * i,html /**/body/**/p,p [remove ~= " spaces "] :nth-child(3+2n)>b span i,div::after{background:url(" /* string */ ") blue!important;content:" escapes " allowed \";width:calc(100% - 3em+5px);margin-top:0;margin-bottom:0;margin-left:10px;margin-right:10px}
CSSTidy 1.3 (286): p * i,html /* remove spaces */ /* " comments have no escapes */ body/* keep */ /* space */p,p [ remove ~= " spaces " ] :nth-child( 3 + 2n ) > b span i,div::after{background:url(" /* string */ ") blue!important;content:" escapes " allowed \";width:calc(100%-3em+5px);margin:0 10px;}
对于普通的CSS, CSSTidy可能是最好的,因为它可以转换为简短的属性。
我认为有其他的缩小器(如YUI压缩器)应该在这方面做得更好,并且给出比这个regex缩小器更短的结果。
这是@Qtax的答案的一个稍微修改的版本,它解决了calc()
的问题,这要归功于@matthiasmullie的miniify库中的一个替代正则表达式。
function minify_css( $string = '' ) {
$comments = <<<'EOS'
(?sx)
# don't change anything inside of quotes
( "(?:[^"\]++|\.)*+" | '(?:[^'\]++|\.)*+' )
|
# comments
/* (?> .*? */ )
EOS;
$everything_else = <<<'EOS'
(?six)
# don't change anything inside of quotes
( "(?:[^"\]++|\.)*+" | '(?:[^'\]++|\.)*+' )
|
# spaces before and after ; and }
s*+ ; s*+ ( } ) s*+
|
# all spaces around meta chars/operators (excluding + and -)
s*+ ( [*$~^|]?+= | [{};,>~] | !importantb ) s*+
|
# all spaces around + and - (in selectors only!)
s*([+-])s*(?=[^}]*{)
|
# spaces right of ( [ :
( [[(:] ) s++
|
# spaces left of ) ]
s++ ( [])] )
|
# spaces left (and right) of : (but not in selectors)!
s+(:)(?![^}]*{)
|
# spaces at beginning/end of string
^ s++ | s++ z
|
# double spaces to single
(s)s+
EOS;
$search_patterns = array( "%{$comments}%", "%{$everything_else}%" );
$replace_patterns = array( '$1', '$1$2$3$4$5$6$7$8' );
return preg_replace( $search_patterns, $replace_patterns, $string );
}
这个问题是专门关于PHP的,但由于这篇文章在我搜索"最小化css regex"时位于结果的顶部,所以我在这里发布了一个Python改编版:
#!/usr/bin/env python
# These regexes were adapted from PCRE patterns by Dustin "lots0logs" Falgout,
# Matthias Mullie (https://stackoverflow.com/a/15195752/299196), and Andreas
# "Qtax" Zetterlund (https://stackoverflow.com/a/44350195/299196).
import re
CSS_COMMENT_STRIPPING_REGEX = re.compile(r"""
# Quoted strings
( "(?:[^"\]+|\.)*" | '(?:[^'\]+|\.)*' )
|
# Comments
/* ( .*? */ )
""",
re.DOTALL | re.VERBOSE
)
CSS_MINIFICATION_REGEX = re.compile(r"""
# Quoted strings
( "(?:[^"\]+|\.)*" | '(?:[^'\]+|\.)*' )
|
# Spaces before and after ";" and "}"
s* ; s* ( } ) s*
|
# Spaces around meta characters and operators excluding "+" and "-"
s* ( [*$~^|]?= | [{};,>~] | !importantb ) s*
|
# Spaces around "+" and "-" in selectors only
s*([+-])s*(?=[^}]*{)
|
# Spaces to the right of "(", "[" and ":"
( [[(:] ) s+
|
# Spaces to the left of ")" and "]"
s+ ( [])] )
|
# Spaces around ":" outside of selectors
s+(:)(?![^}]*{)
|
# Spaces at the beginning and end of the string
^ s+ | s+ z
|
# Collapse concurrent spaces
(s)s+
""",
re.DOTALL | re.IGNORECASE | re.VERBOSE
)
def minify_css(css):
return CSS_MINIFICATION_REGEX.sub(r"12345678",
CSS_COMMENT_STRIPPING_REGEX.sub(r"1", css))
可能与PHP+PCRE版本不完全相同。由于Python的正则表达式库不支持PCRE所支持的许多结构,因此我不得不修改PCRE模式。我删除的修饰符提高了性能,但可能会增强regex抵御恶意输入的能力,因此在不可信的输入上使用它可能不是一个好主意。
这里是我如何做的一个简洁的源代码。与压缩。如果你在源代码中修改了一些东西,你也不必在意。
实际上'//comments'在css中是不允许的。
ob_start('ob_handler');
if(!file_exists('style/style-min.css)
or filemtime('style/style.css') > filemtime('style/style-min.css')){
$css=file_get_contents('style/style.css');
//you need to escape some more charactes if pattern is an external string.
$from=array('@\s*/\*.*\*/\s*@sU', '/\s{2,}/');
$to= array('' , ' ');
$css=preg_replace($from,$to,$css);
$css=preg_replace('@s*([:;,."'{}()])s*@',"$1",$css);
$css=preg_replace('@;}@','}',$css);
header('Content-type: text/css');
echo $css;
file_put_contents('style/style-min.css',$css);
//etag- modified- cache-control- header
}
else{
//exit if not modified?
//etag- modified- cache-control- header
header('Content-type: text/css');
readfile('style/style-min.css');
}
ob_end_flush();
PS谁在我准备打字之前给了我减号?QTax—有一小段时间我忘记转义$ form数组中的反斜杠。PSS。只有新版本的PHP才能理解'U'参数,它使正则表达式不贪婪。