6

How do I parse CSS background-image, which supports multiple values, which may be none and functions (e.g. url() and linear-gradient()) with multiple comma-separated arguments? I can't seem to do this correctly with regexps. A good test case is as follows:

  linear-gradient(top left, red, rgba(255,0,0,0))
, url(a)
, image(url(b.svg), 'b.png' 150dpi, 'b.gif', rgba(0,0,255,0.5))
, none

Which I'd want to convert to the following array:

[
      "linear-gradient(top left, red, rgba(255,0,0,0))"
    , "url(a)"
    , "image(url(b.svg), 'b.png' 150dpi, 'b.gif', rgba(0,0,255,0.5))"
    , "none"
]
7
  • That's just an example. It could be 40 url()s and then some gradients. Commented Aug 7, 2011 at 2:04
  • Does any browser support image(... or linear-gradient(... for background-image? Pretty sure FF and Chrome don't. Commented Aug 7, 2011 at 4:44
  • Yes, they support linear-gradient(). As for image(), they support parsing it so that they could skip it if there are multiple background-images. Commented Aug 7, 2011 at 4:53
  • Oh really? Perhaps you have an example? Here's one of mine. Commented Aug 7, 2011 at 5:14
  • 1
    This is unproductive, and has no relation to my question at all. Of course they support it, it's just vendor prefixed. Commented Aug 7, 2011 at 5:31

3 Answers 3

8
function split (string) {
    var token = /((?:[^"']|".*?"|'.*?')*?)([(,)]|$)/g;
    return (function recurse () {
        for (var array = [];;) {
            var result = token.exec(string);
            if (result[2] == '(') {
                array.push(result[1].trim() + '(' + recurse().join(',') + ')');
                result = token.exec(string);
            } else array.push(result[1].trim());
            if (result[2] != ',') return array
        }
    })()
}

split("linear-gradient(top left, red, rgba(255,0,0,0)), url(a), image(url" +
      "(b.svg), 'b.png' 150dpi, 'b.gif', rgba(0,0,255,0.5)), none").toSource()

["linear-gradient(top left,red,rgba(255,0,0,0))", "url(a)",
 "image(url(b.svg),'b.png' 150dpi,'b.gif',rgba(0,0,255,0.5))", "none"]
Sign up to request clarification or add additional context in comments.

3 Comments

@brock-adams That is true, it would require a line like this for such browsers: if (!String.prototype.trim) String.prototype.trim = function () { return this.replace(/^\s+|\s+$/g, '') }
I found that this also strips percentage values from gradient color stops, so this :- linear-gradient(67deg,rgb(30, 87, 153) 0%,rgb(41, 137, 216) 50%,rgb(32, 124, 202) 51%,rgb(125, 185, 232) 100%) becomes this linear-gradient(67deg,rgb(30,87,153),rgb(41,137,216),rgb(32,124,202),rgb(125,185,232)) Would really LOVE to know how to adjust the regex so that doesn't happen (regex totally baffles me no matter how hard I try)
I think I solved it @DavidO'Sullivan, add else if (result[2] == ',' && result[1][result[1].length - 1] == '%') array[array.length - 1] += result[1]; after if (result[2] != ',') return array and it should capture percentages.
4

Looking at the current W3C Candidate Recommendation for CSS3 (in particular, see background-image and uri), it is structured as follows:

<background-image> = <bg-image> [ , <bg-image> ]* 
<bg-image> = <image> | none
<image> = <url> | <image-list> | <element-reference> | <image-combination> | <gradient>

... (you can find the rest of syntax for images here)

EDIT:

You will need to parse for matching parenthese or none then, and the former is not possible with regex. This post has a pseudo code for the algorithm: Python parsing bracketed blocks.

2 Comments

I'm not parsing the values themselves, just splitting them apart, so that URI section isn't really necessary.
Thanks, though I'm going to look into integrating comma-separated list support into that algorithm, though I'm not exactly sure as to how I should do it.
1

I know, the question is pretty old, but if you stumble over it and need another solution, you can use this one:

let mydiv = document.getElementById('mydiv'),
  result = document.getElementById('result'),
  bgImageArray = getBackgroundImageArray(mydiv);

console.log(bgImageArray);

result.innerHTML = bgImageArray.join('<hr>');

function getBackgroundImageArray(el)
{
    // get backgroundImageStyle
    let bgimg_style = getComputedStyle(el).backgroundImage,
        // count for parenthesis
        parenthesis = -1;

    // split background by characters...
    return bgimg_style.split('').reduce((str_to_split, character) => {
        // if opening parenthesis
        if(character === '(') {
            // if first opening parenthesis set parenthesis count to zero
            if(parenthesis === -1) {
                parenthesis = 0;
            }
            // add 1 to parenthesis count
            parenthesis++;
        }
        // for closing parenthesis reduce parenthesis count by 1
        else if(character === ')') {
            parenthesis--;
        }
        else {
            // if current character is a comma and it is not inside a parenthesis, at a "split" character
            if(character === ',') {
                if(parenthesis === 0) {
                    str_to_split += '||';
                    return str_to_split;
                }
            }
        }
    
        // else keep the character
        str_to_split += character;
    
        return str_to_split;
    }, '')
    // split the resulting string by the split characters including whitespaces before and after to generate an array
    .split(/\s*\|\|\s*/);
}
#mydiv {
  height: 75px;
  background-image:
    /* first bg image */
    linear-gradient(90deg, rgba(28,221,218,1) 0%, rgba(45,109,210,1) 35%, rgba(0,212,255,1) 100%),
    
    /* second bg image */
    -webkit-image-set(url(nothing.jpg) 1x, url(everything.png) 2x),

    /* third bg image */
    url('there/is/an/image.svg');
    
}
<div id="mydiv"></div>

<p>See in console for actual result array, below is the array splitted by &lt;hr&gt;</p>

<div id="result"></div>

1 Comment

Wow, thanks for this very well-thought-out answer a decade later!

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.