在 PHP 中,您可以使用正则表达式 (PCRE) 压缩/缩小 CSS 吗?
(作为正则表达式的理论。我确信那里有图书馆可以很好地做到这一点。)
背景说明:花了几个小时写了一个已删除(半废话)问题的答案后,我想我会发布一部分基础问题并自己回答。希望没事。
(好吧,它可能不是太简单,但非常简单。)
这个答案假设要求是:
{
, }
, ;
, ,
, >
, ~
, +
,-
!important
:
,除了选择器(你必须在它之前保留一个空格)$=
(
左侧[
的所有空格)
]
;
块中的最后一个请注意,此处的要求不包括将 CSS 属性转换为较短的版本(例如使用速记属性而不是几个全长属性,在不需要的地方删除引号)。这是正则表达式通常无法解决的问题。
分两遍解决这个问题更容易:首先删除评论,然后删除其他所有内容。
应该可以一次性完成,但是您必须将 all 替换\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*+ ( [*$~^|]?+= | [{};,>~+-] | !important\b ) \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
。
与正确的解析器相比,选择器检查之前删除空格:
(负前瞻)可以减慢速度。解析器已经知道它们是否在选择器中,并且不必进行额外的搜索来检查它。
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*+ ( [*$~^|]?+= | [{};,>~+-] | !important\b ) \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 "\n\n";
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的结果:
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有一些缺点:
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 压缩器)应该在这方面做得更好,并且比这个正则表达式压缩器给出更短的结果。
这是@Qtax's answer的略微修改版本,它通过@matthiasmullie's Minify librarycalc()
的替代正则表达式解决了这些问题。
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*+ ( [*$~^|]?+= | [{};,>~] | !important\b ) \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 正则表达式”时这篇文章是结果的顶部,所以我在这里发布了一个 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* ( [*$~^|]?= | [{};,>~] | !important\b ) \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"\1\2\3\4\5\6\7\8",
CSS_COMMENT_STRIPPING_REGEX.sub(r"\1", css))
可能与 PHP+PCRE 版本不完全相同。由于 Python 的正则表达式库不支持 PCRE 所做的许多构造,因此我不得不修改 PCRE 模式。我删除的修饰符提高了性能,并可能强化正则表达式以防止恶意输入,因此在 untrusted input 上使用它可能不是一个好主意。
Here is a compact source of how I do it. With compression. And you don't have to care, if you changed something in the source.
In fact '//comments' are not allowed in 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 Who gave me the minus before I'm ready with typing? QTax- For a short time I forgot to escape the backslashes in the $fom array. PSS. Only new version of PHP undestand the 'U' param which makes a regex ungreedy.