views:

41

answers:

2

As an exercise in learning RegEx in JavaScript, I've been trying to select an attribute's value within a CSS stylesheet. Given the following CSS:

foo { 
  color: rgb(0, 0, 0)     !important;
  background-color: rgb(0, 0, 0) !important;
  border-left: 1px solid #111; 
}

and a function getStyle(css, attribute) such that getStyle(css, 'background-color') returns "rgb(0, 0, 0)", I've come up with the following code:

function getStyle(css, attribute) {
  var pattern = new RegExp(attribute + ':\\s+([^!;]+)');
  return pattern.exec(css);
}

This gets pretty close: it matches until it hits a ! or a ;, whichever comes first. But in the case of getStyle(css, 'color'), it also returns the whitespace after rgb(0, 0, 0) and before !important:

color = "rgb(0, 0, 0)      "
background-color = "rgb(0, 0, 0) "
border-left =  "1px solid #111"

I've tried things like attribute + ':\\s+([^\\s+!;]+)', but that winds up returning only rgb(0, because of the spaces in the value. I've also been able to get the expected output using different regex and look behinds, but JavaScript doesn't support them.

I've also thought that maybe I could indicate something like match while \\s+! or \\s+.+; isn't true: that is, stop matching once it finds whitespace before a specific character, but I'm not sure how to key that in regex.

I could just as easily strip the whitespace in a second step, but I'm wondering: is there a way to get the expected output in one RegEx?

+2  A: 

You need what's called a look-ahead. It will match the text you give it, but not include it in the output.

pattern = new RegExp(attribute + ':\\s+.+?(?=\\s*[!;])');

The (?=...) is a look-ahead, where I'm allowing leading whitespace and either of !;. I'm using .+? so that it matches as little as possible before the first matched thing in the look-ahead (otherwise, it'd capture the !important before the ;).

jtbandes
This is great: I only want the `rgb(...` part (your solution gives `color: rgb(...`, but with a parenthesis, I was able to get what I'm looking for: `attribute + ':\\s+(.+?(?=\\s*[!;]))'`. Thanks!
Mark Trapp
This works. I would make it `:\s+(.+?)(?=\s*[!;])` to capture just the attribute without the `: `. Also, JavaScript doesn't need the double escape `\\s`, `\s` works fine.
cnanney
@cnanney, I've tried using single escapes, but when I do that `\s` is completely ignored (as if it's trying match `s` I suppose).
Mark Trapp
@Mark, you're right - in literal strings backslashes must be escaped. My bad.
cnanney
+1  A: 

I would use

'\\s*:\\s*([^\\s+!;]+(?:\\s+[^\\s+!;]+)*)'

If there's some whitespace that's followed by more non-whitespace/bang/semicolon characters, treat it as a continuation of the attribute value.

I also changed the first part, because the whitespace before and after the colon is optional.

You might want to add something like '[\\s;]' + to the beginning, too. The way you have it, the color regex will match both the color and background-color attributes.

Alan Moore
Perfect, I didn't think about the `color` vs. `background-color` issue, or that there could be whitespace before the colon. Thanks!
Mark Trapp