-1

I have a simple script to match series episode codes, like S01E02 or s09e11. The idea is to find all of the episode codes in the provided text and create an array of objects, containing all episodes found.

I first use match() to get the array of all matched codes, then I loop through the codes to extract the season and episode number.

Problem is, when I use the same regex patter with /gi modifiers both for finding all matches, and extracting the episode details, I get an error: Uncaught TypeError: Cannot read property '1' of null (see the Console output).

Case 1 (failing) -- fiddle 1

var episodePatternGI = /s(\d{1,2})e(\d{1,2})/gi;
var matches = 'S3E1 hehehe bla s09e12'.match(episodePatternGI);
var episodes = [];

matches.forEach(function(val) {
    var ep = episodePatternGI.exec(val);
    episodes.push({
    s: ep[1],
    e: ep[2]
  });
});

console.log(episodes);

Case 2 (working) -- fiddle 2

var episodePatternGI = /s(\d{1,2})e(\d{1,2})/gi;
var matches = 'S3E1 hehehe bla s09e12'.match(episodePatternGI);
var episodes = [];

var episodePatternI = /s(\d{1,2})e(\d{1,2})/i; // g modifier removed

matches.forEach(function(val) {
    var ep = episodePatternI.exec(val); // New pattern applied
    episodes.push({
    s: ep[1],
    e: ep[2]
  });
});

console.log(episodes);

As you can see, in the second case I use the same pattern, but the g modifier is removed.

Why doesn't the first case work?

lesssugar
  • 15,486
  • 18
  • 65
  • 115

3 Answers3

3

The problem is that lastIndex is not resetted automatically, so when you call exec for the second match, it won't start looking at the beginning of the string, and thus it won't match.

You can reset lastIndex manually by setting it to 0:

var episodePatternGI = /s(\d{1,2})e(\d{1,2})/gi;
var matches = 'S3E1 hehehe bla s09e12'.match(episodePatternGI);
var episodes = matches.map(function(val) {
  episodePatternGI.lastIndex = 0;
  var ep = episodePatternGI.exec(val);
  return {
    s: ep[1],
    e: ep[2]
  };
});
console.log(episodes);

Calling exec repeatedly until reaching the end of the string (a 2nd call should be enough), as shown in anubhava's answer, will reset lastIndex too.

Community
  • 1
  • 1
Oriol
  • 274,082
  • 63
  • 437
  • 513
  • Exactly the answer I was looking for. – lesssugar Mar 06 '16 at 17:57
  • 1
    Aww you beat me to the answer. I was just about to submit – 4castle Mar 06 '16 at 17:59
  • 2
    An even better answer though, would be to actually *use* the `lastIndex` to your advantage and just loop through `exec()` until it returns null. Because currently the regex is being used twice as much as it needs to be. Skip the matches array portion. – 4castle Mar 06 '16 at 18:45
  • There is no need to call both match and exec and it can be error prone as well – anubhava Mar 07 '16 at 03:26
  • True. I just wanted to show what was the problem with OP's approach, and the minimal way to fix it. – Oriol Mar 07 '16 at 04:14
2

You need to use regexp.exec in a loop to grab the captured group while using g flag:

var episodePatternGI = /s(\d{1,2})e(\d{1,2})/gi;
var str = 'S3E1 hehehe bla s09e12';
var episodes = [];

var m;
while ((m = episodePatternGI.exec(str)) !== null) {
    episodes.push({
        s: m[1],
        e: m[2]
      });
}

console.log(episodes);

Updated Fiddle

RegEx Demo (check code generator in this link)

anubhava
  • 761,203
  • 64
  • 569
  • 643
  • OK, I see. Basically I need to manually keep track of `exec()` index during each iteration. Kinda annoying ;) Thanks! – lesssugar Mar 06 '16 at 17:47
  • The thing I don't get, though, is that in my code I am looping through an array of codes, and I'm extracting season and episode from each code **separately**. Yet, the `exec()` index is somehow global? The solution with `while` is a bit different, more concise. The `forEach` one - not working - is bothering me. – lesssugar Mar 06 '16 at 17:50
  • Actually that's not really needed in this case, see updated answer now. – anubhava Mar 06 '16 at 17:50
  • 1
    This is the best answer. MUCH cleaner and faster than the accepted answer. – 4castle Mar 06 '16 at 20:44
0

If you want to pass in multiple flags to a RegExp in JS, you can also do something like this:

var episodePatternGI = /s(\d{1,2})e(\d{1,2})/;
var other = new RegExp(episodePatternGI.source, "gi");

here is a relevant thread: Changing the RegExp flags

Community
  • 1
  • 1
omarjmh
  • 13,632
  • 6
  • 34
  • 42