7

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?

4 Answers 4

11

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}";
Sign up to request clarification or add additional context in comments.

8 Comments

Hmm, I can't quite get it working with the preg_replace. I do need to place the $path inside the first quote (if present). Any chance you can port this to the codepad demo or give an example in PHP with the preg_replace()?
Here. I had to modify the regex a bit, just added a \s*(['"])? to the end in order to capture the quote mark (if any) to put $path inside it.
Sorry, that link seems out of date. I like this codepad thing but I seem to suck at usin it! I'll put the code in my answer instead.
Ok, have updated the answer with the code and (hopefully) a new working codepad link (that's a cool site!)
Updated your link (it wasn't saved), thanks a ton, this is working! I still don't get what the ? does in ?!, or what the first ?: is for in the (?:https?:).
|
4

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

2 Comments

could you possibly extend this one to also fix the path for this ../images/image.jpg or ../../images/image.jpg
It is working with ../.. as expected. I've just updated an example codepad.org/NDoya4NC
1

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

Comments

0

Got it to work:

$sBaseUrl = 'http://example.com/';
$sCss = preg_replace_callback(
    '|url\s*\(\s*[\'"]?([^\'"\)]+)[\'"]\s*\)|',
    function($aMatches) use ($sBaseUrl) {
        return 'url("'. $sBaseUrl . trim($aMatches[1]). '")';
    },
    $sCss
);
print $sCss;

1 Comment

it worked with a ? after the closing quote - url\s*\(\s*[\'"]?([^\'"\)]+)[\'"]?\s*\)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.