16

Exactly what title asks. I'll provide some examples while explaining my question.

Test string:

var test = "#foo# #foo# bar #foo#";

Say, I want to extract all text between # (all foos but not bar).

var matches = test.match(/#(.*?)#/g);

Using .match as above, it'll store all matches but it'll simply throw away the capturing groups it seems.

var matches2 = /#(.*?)#/g.exec(test);

The .exec method apparently returns only the first result's matched string in the position 0 of the array and my only capturing group of that match in the position 1.

I've exhausted SO, Google and MDN looking for an answer to no avail.

So, my question is, is there any better way to store only the matched capturing groups than looping through it with .exec and calling array.push to store the captured groups?

My expected array for the test above should be:

 [0] => (string) foo
 [1] => (string) foo
 [2] => (string) foo

Pure JS and jQuery answers are accepted, extra cookies if you post JSFiddle with console.log. =]

Yahkob
  • 104
  • 7
Fabrício Matté
  • 69,329
  • 26
  • 129
  • 166

5 Answers5

19

You can use .exec too like following to build an array

var arr = [],
    s = "#foo# #bar# #test#",
    re = /#(.*?)#/g,
    item;

while (item = re.exec(s))
    arr.push(item[1]);

alert(arr.join(' '));​

Working Fiddle

Found from Here

Well, it still has a loop, if you dont want a loop then I think you have to go with .replace(). In which case the code will be like

var arr = [];
var str = "#foo# #bar# #test#"
str.replace(/#(.*?)#/g, function(s, match) {
                           arr.push(match);
                        });

Check these lines from MDN DOC which explains your query about howexec updates lastIndex property I think,

If your regular expression uses the "g" flag, you can use the exec method multiple times to find successive matches in the same string.

When you do so, the search starts at the substring of str specified by the regular expression's lastIndex property (test will also advance the lastIndex property).

Community
  • 1
  • 1
Prasenjit Kumar Nag
  • 13,391
  • 3
  • 45
  • 57
  • Similar to the `.exec` loop which I found but much more simplified and elegant, looks like a suitable answer as I can access all matches and capturing groups from the `item` array. – Fabrício Matté Jun 04 '12 at 04:56
  • If you want to see more js magics you can check the answers of [am-not-i-am](http://stackoverflow.com/users/1106925/am-not-i-am), it's from one of his answer. :) – Prasenjit Kumar Nag Jun 04 '12 at 04:58
  • Favorited his profiled to read the answers later =] Now, even after reading through the [MDN Documentation](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/RegExp/exec) I couldn't find how `.exec` knows from where to start matching in the string (the "offset"?) every time it's invoked, well, important is that it works. I think it automatically stores the `lastIndex` property? If anyone can contribute with a comment, please do. =] – Fabrício Matté Jun 04 '12 at 05:01
  • Oh damn man, I've read the first phrase of that paragraph, but I somehow missed the `the search starts at the substring of str specified by the regular expression's lastIndex property (test will also advance the lastIndex property).` (it must be the SO fomatting x]), thanks again! G'night. – Fabrício Matté Jun 04 '12 at 05:12
6

I'm not sure if this is the answer you are looking for but you may try the following code:

var matches = [];

var test = "#foo# #foo# bar #foo#";

test.replace(/#(.*?)#/g, function (string, match) {
    matches.push(match);
});

alert(JSON.stringify(matches));

Hope it helps.

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • Using `.replace` to take advantage of the global modifier, a really sweet trick (better than looping it with `.exec` which was the question). I'll +1 it for now and accept before I go to sleep if there isn't a better option (keeping this question open for a little while as it might be useful for future reference). =] – Fabrício Matté Jun 04 '12 at 04:46
2

data.replace(/.*?#(.*?#)/g, '$1').split(/#/)
No loops, no functions.

Leonid
  • 3,121
  • 24
  • 31
1

In case somebody arrives with a similar need to mine, I needed a matching function for a Django-style URL config handler that could pass path "arguments" to a controller. I came up with this. Naturally it wouldn't work very well if matching '$' but it wouldn't break on '$1.00'. It's a little bit more explicit than necessary. You could just return matchedGroups from the else statement and not bother with the for loop test but ;; in the middle of a loop declaration freaks people out sometimes.

var url = 'http://www.somesite.com/calendar/2014/june/6/';
var calendarMatch = /^http\:\/\/[^\/]*\/calendar\/(\d*)\/(\w*)\/(\d{1,2})\/$/;

function getMatches(str, matcher){
    var matchedGroups = [];
    for(var i=1,groupFail=false;groupFail===false;i++){
        var group = str.replace(matcher,'$'+i);

        groupFailTester = new RegExp('^\\$'+i+'$');

        if(!groupFailTester.test(group) ){
            matchedGroups.push(group);
        }
        else {
            groupFail = true;
        }
    }
    return matchedGroups;
}

console.log( getMatches(url, calendarMatch) );
Erik Reppen
  • 4,605
  • 1
  • 22
  • 26
0

Another thought, though exec is as efficient.

var s= "#foo# #foo# bar #foo#";
s= s.match(/#([^#])*#/g).join('#').replace(/^#+|#+$/g, '').split(/#+/);
kennebec
  • 102,654
  • 32
  • 106
  • 127