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?
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.
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}";
This pregmatch is as good as selected answer, but also supports raw data
'#url\((?!\s*([\'"]?(((?:https?:)?//)|(?:data\:?:))))\s*([\'"])?#'
Here are the building blocks of that long regular expression
$absoluteUrl = '((?:https?:)?//)';
$rawData = '(?:data\:?:)';
$relativeUrl = '\s*([\'"]?((' . $absoluteUrl . ')|(' . $rawData . ')))';
$search = '#url\((?!' . $relativeUrl . ')\s*([\'"])?#';
$replace = "url($6{$path}";
echo preg_replace($search, $replace, $css);
Full example
http://codepad.org/NDoya4NC
I got this working with the following pattern (based on other answers here):
url\s*\(\s*[\'"]?(?!(((?:https?:)?\/\/)|(?:data\:?:)))([^\'"\)]+)[\'"]?\s*\)
The PHP to get it to run is:
$path = '/themes/default/css/';
$search = '%url\s*\(\s*[\\\'"]?(?!(((?:https?:)?\/\/)|(?:data:?:)))([^\\\'")]+)[\\\'"]?\s*\)%';
$replace = 'url("' . $path . '/$3")';
$css = preg_replace($search, $replace, $css);
Test available here: https://regex101.com/r/zT2gM9/1
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With