Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Regex.prototype.exec returns null on second iteration of the search [duplicate]

Use Case

I'd like to search a string for several matches. Each match is eventually linked to an object property in an object array. When a match is found, that match is replaced by another property within an object. The problem is the code will always return null on the second match.

Test Case

This is the test case that I am using. To simplify the problem, I just replace all matches with the number 5, but note that the final code will be replacing the match with a variable value.

Test Code

Below is the code I'm using to test and debug the issue. The interesting thing is that if I change var str = '5 + QUESTION_2', QUESTION_2 is successfully replaced with 5. Essentially, the problem boils down to the second match always returning null even though it can be matched.

var re = /( |^)(QUESTION_1|QUESTION_2|QUESTION_3)( |$)/g;
var str = 'QUESTION_1 + QUESTION_2';
var rep = 5;

matches = re.exec(str);
var re2 = new RegExp("( |^)(" + matches[2] + ")( |$)", "g");
console.log(matches); // Returns a match on QUESTION_1
str = str.replace(re2, rep);
console.log(str); // Returns 5+ QUESTION_2

matches = re.exec(str);
console.log(matches); // Returns a match on NULL - doesn't find QUESTION_2
re2 = new RegExp("( |^)(" + matches[2] + ")( |$)", "g");
str = str.replace(re2, rep);
console.log(str); // Returns 5+ QUESTION_2

Question

  • Why is the second match always null?
  • The jsfiddle is available here.
like image 605
Pete Avatar asked Apr 25 '14 16:04

Pete


People also ask

What does regex exec return?

JavaScript RegExp exec() The exec() method tests for a match in a string. If it finds a match, it returns a result array, otherwise it returns null.

What does exec () do in JS?

exec() The exec() method executes a search for a match in a specified string and returns a result array, or null .

What is the difference between exec () and test () methods in JavaScript?

The difference in between exec() and test() method is that the exec() method will return the specified match if present or null if not present in the given string whereas the test() method returns a Boolean result i.e., true if the specified match is present or else returns false if it is not present.


2 Answers

Since you're using global flag in exec call regex engine remembers lastIndex which is is a read/write integer property of regular expressions that specifies the index at which to start the next match.

Place this to reset it:

re.lastIndex=0;

Just before next call to RegExp.exec

re.lastIndex = 0 allows you to reset the search index to perform a second search starting from the beginning. Without it, the search begins at the index of the last match, which would yield null in this case.

Read this Mozilla doc for more info on this. As per this manual:

This property is set only if the regular expression used the "g" flag to indicate a global search.

like image 137
anubhava Avatar answered Oct 17 '22 08:10

anubhava


When calling exec on a global regex, it remembers where was the last match and starts the next one from there.

var rx = /a/g, matches = rx.exec('aa');
rx.lastIndex; //1

When you modify your input string, between exec calls, the lastIndex becomes desychronised.

While there's a few way to fix this, I would rather perform the replace in a single statement.

'QUESTION1 + QUESTION2 + QUESTION3'.replace(/QUESTION\d+/g, function ($1) { 
    return 'VALUE_FOR_' + $1; 
});

//"VALUE_FOR_QUESTION1 + VALUE_FOR_QUESTION2 + VALUE_FOR_QUESTION3"
like image 36
plalx Avatar answered Oct 17 '22 10:10

plalx