使用正则表达式缩小/压缩CSS?

Qta*_*tax 24 css php regex pcre minify

在PHP中,您可以使用正则表达式(PCRE)压缩/缩小CSS吗?

(作为正则表达式中的理论.我确信那里有很好的库可以做到这一点.)

背景说明:花了好几个小时写了一个删除(半废话)问题的答案后,我想我会发布一部分基本问题并自己回答.希望没关系.

Qta*_*tax 45

简单的正则表达式CSS minifier /压缩器

(好吧,它可能不是太简单,但非常直接.)

要求

这个答案假设要求是:

  • 删除评论
  • 用一个空格替换长于1个空格的空格组合
  • 移除所有的空格周围的元字符:{,},;,,,>,~,+,-
  • 删除周围的空格 !important
  • 删除周围的空格:,除了选择器(你必须在它之前保留一个空格)
  • 删除运营商周围的空间 $=
  • 删除所有空格右侧(/ [和左)/]
  • 删除字符串开头和结尾的所有空格
  • 删除;块中的最后一个
  • 不要改变字符串中的任何内容
  • 不必处理无效的CSS

请注意,此处的要求不包括将CSS属性转换为较短版本(例如使用速记属性而不是几个全长属性,删除不需要的引号).这是正则表达式无法解决的问题.

通过两次传递更容易解决这个问题:首先删除注释,然后删除其他所有注释.

应该可以在单个传递中执行,但是您必须\s使用与空格和注释匹配的表达式(在某些其他修改中)替换all .

删除注释的第一个表达式:

(?xs)
  # quotes
  (
    "(?:[^"\\]++|\\.)*+"
  | '(?:[^'\\]++|\\.)*+'
  )
|
  # comments
  /\* (?> .*? \*/ )
Run Code Online (Sandbox Code Playgroud)

替换为$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+
Run Code Online (Sandbox Code Playgroud)

替换为$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*+ ( [*$~^|]?+= | [{};,>~+-] | !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);
}
Run Code Online (Sandbox Code Playgroud)

快速测试

可以在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);
Run Code Online (Sandbox Code Playgroud)

输出:

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}"
Run Code Online (Sandbox Code Playgroud)

相比

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}
Run Code Online (Sandbox Code Playgroud)

长度263字节.比上面的正则表达式minifier的输出长12个字节.

与这个正则表达式minifier相比,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;}
Run Code Online (Sandbox Code Playgroud)

长度286字节.比正则表达式minifier的输出长35个字节.

CSSTidy不会删除某些选择器中的注释或空格.但它确实缩小了速记属性.后者应该可以帮助压缩正常的CSS更多.

并排比较

对于与上例中相同的输入,来自不同minifiers的缩小输出.(剩余的换行符用空格替换.)

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;}
Run Code Online (Sandbox Code Playgroud)

对于普通的CSS CSSTidy可能是最好的,因为它转换为速记属性.

我假设还有其他的缩放器(比如YUI压缩器)应该更好,并且比这个正则表达式缩小器提供更短的结果.

  • "发布为wiki"=== rep羡慕. (6认同)
  • @MihaiIorga,为什么维基?如果有人想要纠正或添加一些东西,他们可以继续.:-) (4认同)
  • @ kuldeep.kamboj,[Jeff Atwood不同意](http://blog.stackoverflow.com/2011/07/its-ok-to-ask-and-answer-your-own-questions/). (4认同)
  • 很好的答案.正如@Redzarf已经提到过的那样,在+和 - 周围剥离空间有点过于贪婪.在calc()中,他们必须在那里.或负边距(例如保证金:0 -5px;)也可能混淆它; 他们也不应该被剥夺. (3认同)

lot*_*ogs 6

这是@Qtax 答案的略微修改版本,它解决了问题,calc()这要归功于@matthiasmullie的 Minify 库中的替代正则表达式。

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 );
}
Run Code Online (Sandbox Code Playgroud)