Ignoring nested parts of regular expressions

Viewed 82

I was hoping to use regular expressions in JS to extract bounded substrings. Specifically, substrings that are bounded by %{ }. For the simple case it is easy enough with %{.*?}.

However, I was hoping to be able to ignore nested braces {} and strings "" or ''. I know how to get those independently similar to the above regular expression, but I don't know how to combine things meaningfully.

I'd like the hard_string to output [ "{{friends}} and foes", "cause problems with my '}'s" ]

let easy_string = "Hello %{world}, I am here to %{cause problems}"
let hard_string = "Hello %{{{friends}} and foes}, I am here to %{cause problems with my '}'s} and {{mustaches}}"

console.log('easy string = ',easy_string.match(/%{.*?}/g).map(s=>s.slice(2,-1)))
console.log('hard string = ',hard_string.match(/%{.*?}/g).map(s=>s.slice(2,-1)))

2 Answers

This is certainly a case where you need to use a parser.

Here is one that matches substrings between %{ and } with anything allowed between '...', "...", and {...}:

function splitIt(str) {
  var result = [],
    start = 0,
    level = 0,
    in_quotes = false,
    in_dblquotes = false,
    prcnt_found = false;
  for (var i = 0; i < str.length; ++i) {
    switch (str[i]) {
      case '%':
        if (i + 1 < str.length && str[i + 1] == '{') {
          prcnt_found = true;
          start = i + 2;
        }
        break;
      case '{':
        if (prcnt_found && !in_quotes && !in_dblquotes) {
          ++level
        };
        break;
      case '}':
        if (prcnt_found && level > 0 && !in_quotes && !in_dblquotes) --level;
        if (prcnt_found && level == 0) {
          result.push(str.substr(start, i - start));
          prcnt_found = false;
        }
        break;
      case '"':
        if (prcnt_found) {
          in_dblquotes = !in_dblquotes
        }
        break;
      case "'":
        if (prcnt_found) {
          in_quotes = !in_quotes
        }
        break;
    }
  }
  return result;
}

var hard_string = "Hello %{{{friends}} and foes}, I am here to %{cause problems with my '}'s} and {{mustaches}}"
console.log(splitIt(hard_string))

Instead of asking for all characters .* you can ask for "all characters but curly braces":

/%{([^{}]*)}/g

Nested curly braces will simply be ignored. If you need them filtered somehow, you would have to do that separately.

PS: I used https://regexfiddler.com/ to test for your examples and the above seems to work with them.

Related