Minify / compress CSS avec regex?

En PHP, vous pouvez compresser/minify CSS avec la regex (PCRE)?

(en tant Que théorique dans la regex. Je suis sûr qu'il y a des bibliothèques qui le font bien.)

note de Contexte: Après avoir passé des heures à écrire une réponse à une supprimé (la moitié de la merde) à la question , je pensais que je poste une partie de la question sous-jacente et d'y répondre de mon auto. J'espère que c'est ok.

23
demandé sur Community 2013-03-04 10:24:22

4 réponses

minificateur/compresseur simple regex CSS

(Ok, il ne peut pas être trop simple, mais assez simple en avant.)

exigences

cette réponse suppose que les exigences sont:

  • supprimer les commentaires
  • remplacer les combinaisons d'espaces de plus d'un espace par un espace unique
  • supprimer tous les espaces autour de la meta les personnages: { , } , ; , , , > , ~ , + , -
  • supprimer les espaces autour de !important
  • supprimer les espaces autour de : , sauf dans les sélecteurs (où vous devez garder un espace avant elle)
  • supprimer les espaces autour des opérateurs comme $=
  • supprimer tous les espaces à droite de ( / [ et à gauche de ) / ]
  • supprimer tous les espaces au début et à la fin de la chaîne de caractères
  • supprimer le dernier ; dans un bloc
  • Ne pas changer quoi que ce soit dans les chaînes
  • N'a pas à travailler sur le CSS invalide

notez que les exigences ici ne comprennent pas la conversion des propriétés CSS en versions plus courtes (comme utiliser des propriétés sténographiques au lieu de plusieurs propriétés de longueur, enlever les guillemets où n'est pas nécessaire). C'est quelque chose que regex ne pourrait pas résoudre en général.

Solution

il est plus facile de résoudre cela en deux passages: d'abord supprimer les commentaires, puis tout le reste.

il devrait être possible de faire en un seul passage, mais alors vous devez remplacer tous \s par une expression qui correspond à la fois les espaces et les commentaires (entre autres modification.)

de La première passe de l'expression de supprimer les commentaires:

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

remplacer par .

et pour supprimer tout le reste que vous pouvez utiliser:

(?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+

remplacé par .

le contrôle du sélecteur pour enlever les espaces avant : (le lookahead négatif) peut ralentir cela par rapport aux bons analyseurs. Les analyseurs déjà savoir si ils sont dans un sélecteur ou non, et ne pas avoir à faire des recherches supplémentaires pour vérifier.

exemple d'implémentation en 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%", '', $str);
    return preg_replace("%$re2%", '', $str);
}

test Rapide

peut être trouvé à 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);

sortie:

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

comparé

cssminifier.com

résultats de cssminifier.com pour la même entrée que l'essai ci-dessus:

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}

Longueur 263 byte. 12 octets de plus que la sortie du minificateur regex ci-dessus.

cssminifier.com présente certains inconvénients par rapport à ce mini-minificateur regex:

  • il laisse des parties de commentaires. (Il y a peut être une raison pour cela. Peut-être des hacks de la CSS.)
  • il ne supprime pas les espaces autour des opérateurs dans quelques expressions

CSSTidy

sortie de CSSTidy 1.3 (via codebeautifier.com ) au niveau de compression le plus élevé prédéfini:

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

Longueur 286 octets. 35 octets de plus que la sortie du minificateur regex.

CSSTidy ne supprime pas les commentaires ou les espaces dans certains sélecteurs. Mais il ne rapetisser à la sténographie propriété. Ce dernier devrait probablement aider à compresser les CSS normaux beaucoup plus.

comparaison côte à côte

Minimisé la sortie de l'différents minifiers pour la même entrée comme dans l'exemple ci-dessus. (Les ruptures de ligne sont remplacées par des espaces.)

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

pour CSS csstidy normal est probablement le meilleur car il convertit en propriétés sténographiques.

je suppose qu'il y a d'autres minifiers (comme le YUI compresseur) qui devrait être meilleur à cela, et donner un résultat plus court que ce mini-mini-ex regex.

44
répondu Qtax 2013-03-04 07:54:17

Voici une version légèrement modifiée de la réponse de @Qtax qui résout les problèmes avec calc() grâce à un regex alternatif de Bibliothèque Minify de @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( '', '' );

    return preg_replace( $search_patterns, $replace_patterns, $string );
}
4
répondu lots0logs 2018-06-01 20:30:07

cette question est spécifiquement sur PHP, mais puisque ce post était au sommet des résultats quand J'ai googlé "minify CSS regex", je poste une adaptation Python ici:

#!/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"",
        CSS_COMMENT_STRIPPING_REGEX.sub(r"", css))

peut ne pas être exactement la même que les versions PHP+PCRE. Puisque la bibliothèque d'expressions régulières de Python ne supporte pas beaucoup de constructions que PCRE fait, j'ai dû modifier les patterns PCRE. Les modificateurs que j'ai supprimés améliorent les performances et durcissent potentiellement le regex contre les d'entrée, de sorte que ce n'est probablement pas une bonne idée de l'utiliser sur non fiables entrée .

0
répondu Eric Pruitt 2018-06-08 00:52:57

Voici une source compacte de comment je le fais. Avec la compression. Et vous n'avez pas de soins si vous avez changé quelque chose dans la source.

En fait, " //les commentaires ne sont pas autorisés dans le 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*@',"",$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 qui m'a donné le moins avant que je sois prêt à taper? QTax-pendant une courte période, j'ai oublié d'échapper aux slashs dans le tableau $fom. PSS. Seule la nouvelle version de PHP undestand le' U ' param qui rend un regex ungreedy.

-1
répondu B.F. 2014-04-09 09:09:13