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

Posted

技术标签:

【中文标题】使用正则表达式缩小/压缩 CSS?【英文标题】:Minify/compress CSS with regex? 【发布时间】:2013-02-18 04:45:33 【问题描述】:

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

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

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

【问题讨论】:

+1 回答问题 :) 为什么要关闭投票?另一个问题是关于 JS 的,这个问题是关于 CSS 的。 @SamuelLiew,这更多是关于正则表达式,而不是正确的做法。 (; @SamuelLiew,那个有点受限:“我只是想从文件中删除任何换行符/制表符和 cmets” @WesleyMurch,也许不是一个好问题,但作为一个技术正则表达式问题,我认为它是完全有效的。 【参考方案1】:

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

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

要求

此答案假设要求是:

删除 cmets 用单个空格替换超过 1 个空格的空格组合 删除元字符周围的所有空格:;,>~+- 删除!important 周围的空格 删除: 周围的空格,选择器除外(您必须在其前面保留一个空格) 删除运算符周围的空格,例如 $= 删除(/[右侧和)/]左侧的所有空格 删除字符串开头和结尾的所有空格 删除块中的最后一个; 不要更改字符串中的任何内容 不必处理无效的 CSS

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

解决方案

分两遍更容易解决这个问题:首先删除 cmets,然后是其他所有内容。

应该可以一次性完成,但是您必须将所有 \s 替换为同时匹配空格和 cmets 的表达式(以及其他一些修改)。

去除cmets的第一遍表达式:

(?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 中的示例实现

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

快速测试

可以找到at 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::afterbackground: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::afterbackground: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 与这个正则表达式缩小器相比有一些缺点:

它会留下部分 cmets。 (这可能是有原因的。也许是一些 CSS hack。) 它不会删除某些表达式中运算符周围的空格

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::afterbackground:url("  /* string */  ") blue!important;content:" escapes \" allowed \\";width:calc(100%-3em+5px);margin:0 10px;

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

CSSTidy 不会删除某些选择器中的 cmets 或空格。但它确实缩小为速记属性。后者可能有助于更多地压缩普通 CSS。

并排比较

对于与上述示例相同的输入,来自不同缩小器的缩小输出。 (剩余的换行符替换为空格。)

this answern    (251): p * i,html body p,p [remove~=" spaces  "] :nth-child(3+2n)>b span i,div::afterbackground: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::afterbackground: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::afterbackground:url("  /* string */  ") blue!important;content:" escapes \" allowed \\";width:calc(100%-3em+5px);margin:0 10px;

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

我认为还有其他的压缩器(比如 YUI 压缩器)应该在这方面做得更好,并且比这个正则表达式压缩器给出更短的结果。

【讨论】:

@MihaiIorga,为什么是维基?如果有人想更正或添加一些东西,他们可以继续。 :-) 我也认为这不是 Stack Overflow 真正目标的一部分,即问答社区。如果用户可以搜索 js-minified 解决方案,则对 wiki/博客更有帮助。 @kuldeep.kamboj, Jeff Atwood disagrees. “作为 wiki 发布”=== 表示羡慕。 很好的答案。正如@Redzarf 已经提到的那样,在 + 和 - 周围剥离空格有点过于贪婪。在 calc() 中,它们必须存在。或者负边距(例如边距:0 -5px;)也会混淆它;他们也不应该在那里被剥夺。【参考方案2】:

这是我如何做到这一点的简洁来源。 带压缩。如果您更改了源代码中的某些内容,您不必在意。

事实上'//cmets'在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', '/\\s2,/');
  $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' 参数。

【讨论】:

这个有几个问题。在我的示例 CSS 上试一试,看看你自己。它不会以多种方式正确删除 cmets。此代码破坏的示例 CSS:content: " foo /* bar */ baz";/* comment */ color: #f00; /* comment */【参考方案3】:

这里是@Qtax's answer 的略微修改版本,它解决了calc() 的问题,这要归功于@matthiasmullie's Minify library 的替代正则表达式。

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

【讨论】:

【参考方案4】:

这个问题是专门针对 PHP 的,但是由于当我在 Google 上搜索“缩小 css 正则表达式”时这篇文章位于结果的顶部,所以我在这里发布了一个 Python 改编版:

#!/usr/bin/env python
# These regexes were adapted from PCRE patterns by Dustin "lots0logs" Falgout,
# Matthias Mullie (https://***.com/a/15195752/299196), and Andreas
# "Qtax" Zetterlund (https://***.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。

【讨论】:

以上是关于使用正则表达式缩小/压缩 CSS?的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript 正则表达式规则正在破坏 ASP.NET 缩小

tar,grep与正则表达式

通过正则表达式解析 CSS

Linux基础,targrep与正则表达式!

linux基础正则表达式shell基础文件查找和压缩

linux基础正则表达式shell基础文件查找和压缩