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

时间:2013-03-04 06:24:23

标签: php css regex pcre minify

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

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

背景说明:花了好几个小时写下deleted (half crap) question的答案后,我想我会发布一部分基本问题并自己回答。希望没关系。

4 个答案:

答案 0 :(得分:45)

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

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

要求

这个答案假定要求是:

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

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

解决方案

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

应该可以在一次传递中完成,但是你必须用一个匹配空格和注释的表达式替换所有;(在其他一些修改中)。

删除评论的第一个表达式:

\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之前删除空格(负向前瞻)可以减慢此速度。 解析器已经知道它们是否在选择器中,并且不必进行额外的搜索来检查它。

PHP中的示例实现

:

快速测试

可以找到at ideone.com

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);
}

输出:

$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);

相比

cssminifier.com

cssminifier.com与上述测试输入相同的结果:

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}"

长度为263字节。比上面的正则表达式缩写器的输出长12个字节。

与此正则表达式缩小器相比,

cssminifier.com有一些缺点:

  • 它留下了部分评论。 (这可能是有原因的。也许有些CSS黑客攻击。)
  • 在某些表达式
  • 中,它不会删除运算符周围的空格

CSSTidy

在预设的最高压缩级别输出CSSTidy 1.3(通过codebeautifier.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}

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

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

并排比较

与上例中相同输入的不同缩小器的缩小输出。 (剩余的换行符用空格替换。)

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压缩器)应该更好,并且比这个正则表达式缩小器的结果更短。

答案 1 :(得分:4)

以下是@Qtax's answer的略微修改版本,通过@matthiasmullie's Minify library的替代正则表达式解决了calc()的问题。

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 );
}

答案 2 :(得分:0)

这个问题是关于PHP的,但由于这篇帖子在我用Google搜索“缩小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模式。我删除的修饰符可以提高性能,并可能加强恶意输入的正则表达式,所以it's probably not a good idea to use this on untrusted input

答案 3 :(得分:-1)

这是我如何做的紧凑来源。  随着压缩。如果您在源中更改了某些内容,则无需关心。

事实上,在CSS中不允许使用'// comments'。

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-很短的时间我忘了逃避$ fom数组中的反斜杠。 PSS。只有新版本的PHP不符合'U'参数,这使得正则表达式不合适。