views:

187

answers:

5

I'm rubbish at Regular Expressions, really!

What I'd like is to split a string containing a CSS property value into an array of [string,value,unit].

For example: if I supplied the .split() method with 1px it'd return ["1px",1,"px"]. If I were to supply, similarly, 10% it'd return ["10%",10,"%"].

Can this be done?

I appreciate all your help!

Update: I'd also like it to return ["1.5em",1.5,"em"] if 1.5em were supplied. But, if possible, still return null if supplied yellow. Unfortunately /^([0-9]*\.?[0-9]*)(.*)/ supplied with yellow would return y,,y!

Thanks so far guys!

+5  A: 

Try:

cssProperty.match(/^([0-9]+\.?[0-9]*)(.*)/);
Josh
As both answers are the same, who do I choose to have the correct answer? Help!
Jonathon David Oates
@Justin, as your answer needed to be edited, and Josh's worked right out of the box, I am so sorry, but he has the points! Please forgive me!
Jonathon David Oates
`cssProperty.match(/^([0-9]*\.?[0-9]*)(.*)/);` will also support `1.5em`
dev-null-dweller
@dev: Good point! Answer updated.
Josh
@Jay: Accept whichever one you want. In full disclosure, I edited my answer twice, once to change `split` to `match` (*you* threw me off and I realized 10 second after posting you wanted `match` :-) and again to incorporate dev-null-dweller's excellent advice.
Josh
@Jay: See the question [Who do you reward points to when more than one is right?](http://meta.stackoverflow.com/questions/50486/who-do-you-reward-points-to-when-more-than-one-is-right/50488#50488)
Josh
+6  A: 
"1px".match(/(\d*\.?\d*)(.*)/)

yields

["1px", "1", "px"]

I've updated the expression to match real numbers with leading and trailing decimals.

var expr  = /(\d*\.?\d*)(.*)/,
    cases = [
    "1px",
    "1.px",
    "11.px",
    "11.1px",
    "11.11px",
    "1.11px",
    ".11px",
    ".1px",
    ".px" // Invalid numeric value, but still preserves unit and can be handled as seen below
];

for ( var i=0,l=cases.length; i<l; ++i ) {
    var r = cases[i].match(expr );
    console.log(r, parseFloat(r[1], 10) || 0);
}

Results

["1px", "1", "px"] 1
["1.px", "1.", "px"] 1
["11.px", "11.", "px"] 11
["11.1px", "11.1", "px"] 11.1
["11.11px", "11.11", "px"] 11.11
["1.11px", "1.11", "px"] 1.11
[".11px", ".11", "px"] 0.11
[".1px", ".1", "px"] 0.1
[".px", ".", "px"] 0
Justin Johnson
Also, if you want the "1" to be a number, just use `var aaa[1] = parseInt(aaa[1],10)`
M28
`parseFloat` is actually more appropriate.
Justin Johnson
@Justin Johnson: cool, how about completing expr with plus/minus sign var expr = /^([\+\-]?\d*\.?\d*)(.*)$/
Marco Demajo
+5  A: 

Using capturing groups:

var matches = "100%".match(/^(\d+(?:\.\d+)?)(.*)$/);

You're in luck. match returns an array with the full match on the first position, followed by each group.
split is probably wrong here, since you want to keep the full match. If you wanted to get ['100', '%'], for example, you could have done .split(/\b/).

Updated to enable fractions. Also, the use of both anchors will not match when the format isn't [number][unit], so null is returned.

Kobi
This is very nearly there, only it returns <code>["1.5em",1.5,.5,"em"]</code> and <code>["1em",1,,"em"]</code>.
Jonathon David Oates
@​​​​​​​​​​​​​​Jay - My bad, should have been a non-capturing group.
Kobi
@Kobi: split(/\b/) is WRONG, it does not split "100%" into ['100', '%] unless there is a space between the two like: "100 %"
Marco Demajo
@Marco - Hello! Seems to work well for me: http://jsfiddle.net/kobi/QftyC/ . `\b` matches the boundary between an alphanumeric (`0`) to a non-alphanumeric (`%`), so it works perfectly in this case, and should work in all flavors. It will not work well for `1.1` or `1em` though, only for `100%`.
Kobi
@Kobi: I didn't think you wanted to split only '100%', I thought the solution should work also for something like "300px", "1em" etc.
Marco Demajo
A: 
function mySplit(str)
{
    var regex = /(\d*)([^\d]*)/;
    var t = "$1$2,$1,$2";
    return str.replace(regex,t).split(",");
} 
John Isaacks
This returns a string, the OP needs an array in this case. Also, `\D` is a handy replacement for `[^\d]` (there's also \W, etc)
Kobi
@Kobi It would return a string except for the .split(',') I assumed would split it into an array on the ','. I didn't test it thought. oh and thanks for the \D tip! :)
John Isaacks
+1  A: 

More complicated but will return parsed number and null for some garbage strings

String.prototype.unitSplit = function(parse){
    var retArr = this.toString().match(/^([\d]*\.?[\d]+)([^\d]*)$/);
    if(retArr && parse != undefined)
    {
        retArr[1] = (retArr[1].indexOf('.') >= 0)?parseFloat(retArr[1]):parseInt(retArr[1]);
    }
    return retArr;
}
/* Test cases */
"10px".unitSplit(); //["10px", "10", "px"]
"20%".unitSplit(true); //["20%", 20, "%"]
".8em".unitSplit(true); //[".8em", 0.8, "em"]
"127.0.0.1localhost".unitSplit(true); //null
dev-null-dweller
Ok you win! :-)
Josh
All numbers in JavaScript are floats, so you might as well just do `retArr[1] = parseFloat(retArr[1], 10);` (you also need to explicitly include the radix). Also, a little white space in a ternary expression never killed anyone ;) As for your expression, the range operators in your expression are not necessary when you are not negating: it could just be `(\d*\.?\d+)([^\d]*)`.
Justin Johnson