Sayam Qazi
Sayam Qazi

Reputation: 193

Regex is not matching all occurrences

I am trying to match all occurrences <% anything %> inside a template literal. My regex setup looks like this.

process(template : string , data: Object) {
    let re = /<%([^%>]+)?%>/g, match;
    while (match = re.exec(template)) {
        template = template.replace(match[0], data[match[1]]);
    }
    return template;
}

if my template looks like this

`<div class="tile">
        <span><%ENTRY_NUMBER%></span> 
        <span><%NAME%> , <%CLASS%> , <%GENDER%> , <%AGE%></span>
</div>`

it will match the first occurrence just fine then keep picking the alternate occurrences. if there is one match inside a html tag it works fine but when i use multiples like in second span element it misses alternate occurrences. I think it has something to do with < and >.

When I separate those occurrences with <> instead of comma like below it works fine.

<span><%NAME%> <> <%CLASS%> <> <%GENDER%> <> <%AGE%></span>

The result it like this

<span> john <> biology <> male <> 17 </span>

I have examined the regex multiple times but to no avail.

Upvotes: 1

Views: 833

Answers (2)

bluehipy
bluehipy

Reputation: 2294

The problem is you don't need a global flag g becuase you reexecute the expression over and over until will not match but a multiline flag m

let s = `<div class="tile">
        <span><%ENTRY_NUMBER%></span> 
        <span><%NAME%> , <%CLASS%> , <%GENDER%> , <%AGE%></span>
</div>`;
const re = /<%([^\%]+)?%>/,
  data = {
    ENTRY_NUMBER: '1977',
    NAME: 'Blue',
    CLASS: 'Vertebrates',
    GENDER: 'Male',
    AGE: '40'
  };


let k = 0;
while (re.test(s) && k++ < 1000) {
  match = re.exec(s);
  if (match) s = s.replace(match[0], data[match[1]]);
}
console.log(s);

Upvotes: 0

Wiktor Stribiżew
Wiktor Stribiżew

Reputation: 627469

The problem is that re.lastIndex stores the value that is correct for the previous value of template, but since you change the template value the re.lastIndex is no longer correct for the modified string.

See a simplified example below:

let template = `<%ENTRY_NUMBER%><%NAME%><%CLASS%><%GENDER%><%AGE%>`;
let re = /<%(.+?)%>/g, match;
let data = { 'NAME':'John', 'CLASS':'A3', 'GENDER':'Male', 'AGE':'19' };
while (match = re.exec(template)) {
    console.log(re.lastIndex);
    template = template.replace(match[0], data[match[1]]);
    console.log(template);
}
console.log(template);

// Iteration 1: re.lastIndex = 16
// undefined<%NAME%><%CLASS%><%GENDER%><%AGE%>
// Index is now between <%NAME% and > -> this match will be skipped
// and so on...

Thus, it is better to modify the string on the fly inside a replace callback since all matches will get replaced once they are found, and you won't have to track the indices where to search from.

Use

process(template : string , data: Object) {
    return template.replace(/<%(.+?)%>/g, function($0, $1) { 
              return data[$1] ? data[$1] : $0; 
           });
}

Note that <%(.+?)%> matches <%, then captures any 1 or more chars other than line break chars, as few as possible into Group 1 and then matches %>. This way, you will also match values that contain % and > (it is important if data contains such keys).

Upvotes: 2

Related Questions