Wesley Murch Wesley Murch - 7 months ago 168
PHP Question

preg_replace() regex to match relative url() paths in CSS files

I'm combining some CSS files and writing them to a file in a separate directory. I'm trying to replace the relative

url()
values to work with the new file location, ignoring any absolute URLs. Here's some sample CSS:

#TEST {
background:url(test.jpg);
background:url( 'test.jpg' );
background:url("test.jpg" );
background:url(http://example.com/test.jpg);
background:url('https://example.com/test.jpg');
background:url("http://example.com/test.jpg");
background:url( '//example.com/test.jpg' );
background:url( "//example.com/test.jpg" );
background:url(//example.com/test.jpg);
}


Anything that doesn't start with
http://
,
https://
or
//
should get
$path
injected before it (only the first 3 should match).

Desired output:

#TEST {
background:url(/themes/default/css/test.jpg);
background:url( '/themes/default/css/test.jpg' );
background:url("/themes/default/css/test.jpg" );
background:url(http://example.com/test.jpg);
background:url('https://example.com/test.jpg');
background:url("http://example.com/test.jpg");
background:url( '//example.com/test.jpg' );
background:url( "//example.com/test.jpg" );
background:url(//example.com/test.jpg);
}


However, this code is matching the opposite:

$path = '/themes/default/css/';
$search = '#url\(\s*([\'"]?)((http(s)?:)?//)#';
$replace = "url($1{$path}$2";
$css = preg_replace($search, $replace, $css);


I know you can use something like
!^(http)
to not match strings that start with
http
, but everything I've tried has failed (me === bad at regex). I've been using an online regex tester to figure this out but am truly stuck.

This might not be what I use to solve the real problem (making sure paths work in compiled CSS) but can anyone help me fix this regex problem, or have a better solution?

Answer

You're almost there - what you're after for the "not matching strings starting with" is called a "negative lookahead" and looks like (?!http).

So for url\( not followed by \s*['"]?(https?:)?//, you do:

url\((?!\s*['"]?(http(s)?:)?//)

(I kept that (s) capturing group in there incase you wanted to capture it, but you don't need those brackets around the (s); s? is the same as (s)? if you don't care about capturing).

See it in action here.

Edit:

Since you want to put the $path after the quote mark (if any), I modified the regex to:

url\((?!\s*['"]?(?:https?:)?//)\s*(['"])?

i.e. removed all capturing brackets I don't care about, and added a \s*(['"])? to capture what kind of quote it is.

I think it is in codepad here, but just in case, here is the code:

$path = '/themes/default/css/';
$search = '#url\((?!\s*[\'"]?(?:https?:)?//)\s*([\'"])?#';
$replace = "url($1{$path}";