7

I'm writing a quick preg_replace to strip comments from CSS. CSS comments usually have this syntax:

/* Development Classes*/
/* Un-comment me for easy testing
  (will make it simpler to see errors) */

So I'm trying to kill everything between /* and */, like so:

$pattern = "#/\*[^(\*/)]*\*/#";
$replace = "";
$v = preg_replace($pattern, $replace, $v);

No dice! It seems to be choking on the forward slashes, because I can get it to remove the text of comments if I take the /s out of the pattern. I tried some simpler patterns to see if I could just lose the slashes, but they return the original string unchanged:

$pattern = "#/#";
$pattern = "/\//";

Any ideas on why I can't seem to match those slashes? Thanks!

Doug Avery
  • 1,057
  • 1
  • 8
  • 14
  • 1
    I won't supply it as an answer since it's not really applicable directly to the question, but there are some good tools out there that remove/minify CSS content already: http://www.minifycss.com/ Just for those who might be on here trying to reinvent the wheel :) – AvatarKava Oct 17 '09 at 15:29
  • You should better use a parser. Otherwise you will remove something like `content: "/* … */"`. – Gumbo Oct 17 '09 at 15:33
  • AvatarKara - I'm actually using this script: http://code.google.com/p/cssmin/ - but it wasn't removing comments for some reason. I assumed I would have to write that bit in myself, but since other scripts AND answers aren't working, it seems like something stranger is going on....This is for use in an EE plugin, so I wonder if the string being passed in or the environment have some quirks I'm not aware of. Thanks for the link! – Doug Avery Oct 17 '09 at 18:16

5 Answers5

12

Here's a solution:

$regex = array(
"`^([\t\s]+)`ism"=>'',
"`^\/\*(.+?)\*\/`ism"=>"",
"`([\n\A;]+)\/\*(.+?)\*\/`ism"=>"$1",
"`([\n\A;\s]+)//(.+?)[\n\r]`ism"=>"$1\n",
"`(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+`ism"=>"\n"
);
$buffer = preg_replace(array_keys($regex),$regex,$buffer);

Taken from the Script/Stylesheet Pre-Processor in Samstyle PHP Framework

See: http://code.google.com/p/samstyle-php-framework/source/browse/trunk/sp.php

csstest.php:

<?php

$buffer = file_get_contents('test.css');

$regex = array(
"`^([\t\s]+)`ism"=>'',
"`^\/\*(.+?)\*\/`ism"=>"",
"`([\n\A;]+)\/\*(.+?)\*\/`ism"=>"$1",
"`([\n\A;\s]+)//(.+?)[\n\r]`ism"=>"$1\n",
"`(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+`ism"=>"\n"
);
$buffer = preg_replace(array_keys($regex),$regex,$buffer);
echo $buffer;

?>

test.css:

/* testing to remove this */
.test{}

Output of csstest.php:

.test{}
mauris
  • 42,982
  • 15
  • 99
  • 131
  • That took out some spaces and linebreaks, but seemed to leave all comments intact. It definitely looks like the third line SHOULD be removing comments, though. Baffling. – Doug Avery Oct 17 '09 at 01:07
  • hi there, it works fine for me - totally. comments were all removed. – mauris Oct 17 '09 at 01:53
  • Interesting! Thanks for the updated test. I'm running this inside an ExpressionEngine plugin, so I wonder if some environment difference is causing my issues. – Doug Avery Oct 17 '09 at 17:33
  • 3
    Lines 3 and 4 of this REGEX now throw an error in PHP 7.3 Warning: preg_replace(): Compilation failed: escape sequence is invalid in character class offset 4 - question here: https://stackoverflow.com/questions/57829977/php-7-3-error-when-removing-css-comments-via-regex code was working great... for years... lol – Christian Žagarskas Sep 07 '19 at 01:18
  • 2
    This sample fails with `Warning: preg_replace(): Compilation failed: escape sequence is invalid in character class at offset 4 in ....the preg_replace line`. See https://onlinephp.io/c/799cd – user1432181 Nov 25 '22 at 15:05
7

I don't believe you can use grouping within a negated character class like you have there. What you're going to want to use is called Assertions, of which there are two types. "look-ahead" and "look-behind".

The pattern you're looking for in English is basically, "forward slash, literal wildcard, anything that isn't followed by a forward slash or anything other than a literal wildcard that is followed by a forward slash or a forward slash that isn't preceded by a literal wildcard zero or more times, literal wild card, forward slash"

<?php

$str = '/* one */ onemore
/*
* a
* b
**/
stuff // single line
/**/';

preg_match_all('#/\*(?:.(?!/)|[^\*](?=/)|(?<!\*)/)*\*/#s', $str, $matches);
print_r($matches);

?>
joebert
  • 2,653
  • 2
  • 18
  • 22
  • Thanks for the in-depth explanation. I can't get this to work in the file I'm using it in, but it works perfectly when isolated, so I suspect I have more problems to figure out... – Doug Avery Oct 17 '09 at 18:10
5

I had the same issue. To solve it, I first simplified the code by replacing "/ASTERIX" and "ASTERIX/" with different identifiers and then used those as the start and end markers.

$code = str_replace("/*","_COMSTART",$code);
$code = str_replace("*/","COMEND_",$code);
$code = preg_replace("/_COMSTART.*?COMEND_/s","",$code);

The /s flag tells the search to go onto new lines

Watts Epherson
  • 692
  • 5
  • 9
2

There's a number of suggestions out there, but this one seems to work for me:

$v=preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $v); 

so

"/* abc */.test { color:white; } /* XYZ */.test2 { padding:1px; /* DEF */} /* QWERTY */"

gives

.test { color:white; } .test2 { padding:1px; } 

see https://onlinephp.io/c/2ae1c for working test

user1432181
  • 918
  • 1
  • 9
  • 24
1

Just for fun(and small project of course) I made a non-regexp version of a such code (I hope it's faster):

function removeCommentFromCss( $textContent )
{
    $clearText = "";
    $charsInCss = strlen( $textContent );
    $searchForStart = true;
    for( $index = 0; $index < $charsInCss; $index++ )
    {
        if ( $searchForStart )
        {
            if ( $textContent[ $index ] == "/" && (( $index + 1 ) < $charsInCss ) && $textContent[ $index + 1 ] == "*" )
            {
                $searchForStart = false;
                continue;
            }
            else
            {
                $clearText .= $textContent[ $index ];
            }
        }
        else
        {
            if ( $textContent[ $index ] == "*" && (( $index + 1 ) < $charsInCss ) && $textContent[ $index + 1 ] == "/" )
            {
                $searchForStart = true;
                $index++;
                continue;
            }
        }
    }
    return $clearText;
}
crea7or
  • 4,421
  • 2
  • 26
  • 37