Минимизировать / сжать CSS с регулярным выражением?

В PHP вы можете сжимать / уменьшать CSS с помощью регулярного выражения (PCRE)?

(Как теоретический в regex. Я уверен, что есть библиотеки, которые делают это хорошо.)

Фоновая записка: потратив часы на ответ на удаленный (наполовину дерьмовый) вопрос , я подумал, что отправлю часть основного вопроса и отвечу на него сам. Надеюсь, все в порядке.

Простой редактор / компрессор CSS регулярных выражений

(Хорошо, это может быть не слишком просто, но довольно прямолинейно.)

Требования

Этот ответ предполагает, что требования:

  • Удалить комментарии
  • Замените комбинации пробелов длиннее 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

 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

Результаты 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 байт дольше, чем выход майнера regex выше.

cssminifier.com имеет некоторые недостатки по сравнению с этим средним переменным regex:

  • Он оставляет части комментариев. (Возможно, это может быть причина. Возможно, некоторые хаки 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 байт дольше, чем выходное средство регулятора regex.

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, которая решает проблемы с calc() благодаря альтернативному регулярному выражению из библиотеки Minify @ matthiasmullie .

 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' ); return preg_replace( $search_patterns, $replace_patterns, $string ); } 

Вот компактный источник того, как я это делаю. С компрессией. И вам не нужно заботиться, если вы что-то изменили в источнике.

На самом деле «// комментарии» не разрешены в 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. В течение короткого времени я забыл избежать обратных косых черт в массиве $ fom. PSS. Только новая версия PHP undestand параметр 'U', который делает регулярное выражение неровным.