Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 592
Wesley Murch Avatar asked Mar 21 '12 03:03

Wesley Murch


3 Answers

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}";
like image 142
mathematical.coffee Avatar answered Oct 13 '22 08:10

mathematical.coffee


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

like image 4
Mangirdas Skripka Avatar answered Oct 13 '22 08:10

Mangirdas Skripka


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);
  • allows spaces around the brackets
  • supports the negative lookahead for http, https, data, "//"
  • inserts quotes around the replaced asset

Test available here: https://regex101.com/r/zT2gM9/1

like image 1
cornernote Avatar answered Oct 13 '22 08:10

cornernote