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"
]
Eli Grey
  • 35,104
  • 14
  • 75
  • 93
  • That's just an example. It could be 40 `url()`s and then some gradients. – Eli Grey Aug 07 '11 at 02:04
  • Does any browser support `image(...` or `linear-gradient(...` for `background-image`? Pretty sure FF and Chrome don't. – Brock Adams Aug 07 '11 at 04: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. – Eli Grey Aug 07 '11 at 04:53
  • Oh [really](https://developer.mozilla.org/en/CSS/-moz-linear-gradient)? Perhaps you have an example? [Here's one of mine](http://jsfiddle.net/6PL6S/1/). – Brock Adams Aug 07 '11 at 05:14
  • 1
    This is unproductive, and has no relation to my question at all. Of course they support it, it's just vendor prefixed. – Eli Grey Aug 07 '11 at 05:31
  • It's very related, as the question is an arbitrary exercise with no practical application, as stated. It will not work, nor apply to any current browser (as stated). Should it be tagged "homework"? – Brock Adams Aug 07 '11 at 05:35
  • @BrockAdams let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/2205/discussion-between-eli-grey-and-brock-adams) – Eli Grey Aug 07 '11 at 18:24

3 Answers3

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"]
Hafydd
  • 146
  • 1
  • 2
  • 1
    @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, '') } – Hafydd Aug 08 '11 at 12:19
  • 1
    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) – David O'Sullivan Feb 20 '15 at 01:39
  • 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. – Steven Lambert Jul 13 '15 at 06:37
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.

Community
  • 1
  • 1
William Niu
  • 15,798
  • 7
  • 53
  • 93
  • 1
    I'm not parsing the values themselves, just splitting them apart, so that URI section isn't really necessary. – Eli Grey Aug 07 '11 at 02:30
  • 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. – Eli Grey Aug 07 '11 at 03:54
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>