0

Given these examples:

{{foo}}
{foo{bar}}
{bar}
baz

{quz
{foobar}}

the expected output will be:

foo
foo{bar}
bar
baz

{quz
foobar}

That is to say, search in arbitrary text witch may contains up to one match, and replace the balanced delimeter ({}) if there are any by using regular expression with the string replace method.

someStr.replace(/regexp goes here/, match => return match);

I know pattern for brackets pair ({}) /\{(.*?)\}/, ({{}}) /\{\{(.*?)\}\}/, but don't know how to combine the 0, 1 and 2 occurrences for the brackets into one regular expression.

Also, in order to only match the content without the delimeter, we should use the lookaround assertions https://stackoverflow.com/a/2973495/1553656

mplungjan
  • 169,008
  • 28
  • 173
  • 236
牛さん
  • 2,884
  • 3
  • 29
  • 43
  • 2
    Why not `foobar}` ? – mplungjan Apr 08 '18 at 14:22
  • Have you tried any regular expression so far? – Jordi Nebot Apr 08 '18 at 14:23
  • @mplungjan guess the meaning is, if no equal amount of delimiters is found on either side, leave value as-is. – CBroe Apr 08 '18 at 14:30
  • 1
    Is it possible to have other characters between braces i.e. `{a{b}}`? – revo Apr 08 '18 at 14:32
  • @CBroe that's it! – 牛さん Apr 08 '18 at 14:33
  • @revo examples updated – 牛さん Apr 08 '18 at 14:36
  • 1
    Last question, are they each a single input string or all within a large text? – revo Apr 08 '18 at 14:40
  • Now these are all single examples, to be viewed as one line independent from each other - or do you want to get from that shown input to the output "in one go", one string in, one string out? – CBroe Apr 08 '18 at 14:43
  • @revo a single input – 牛さん Apr 08 '18 at 14:49
  • @CBroe just for one input witch may have up to one match – 牛さん Apr 08 '18 at 14:54
  • It would be hardly possible even with more featured regex engines to do this. However there should be a solution if no whitespace character is there within braces `{f o o{ba r}}` – revo Apr 08 '18 at 14:57
  • I see you edited your comment from *a large text* to *a single input*. If you mean it I'll go for writing an answer. – revo Apr 08 '18 at 15:01
  • @revo, yep I updated my comment. I think it's ok for a single input and up to only one match for the input. – 牛さん Apr 08 '18 at 15:02
  • 2
    Write a parser, not a regex. – zzzzBov Apr 08 '18 at 15:10
  • 2
    Your input/output examples are still not consistent (or the problem specification is still to vague) - if `{foo{bar}}` results in `foo{bar}`, then `{foobar}}` should be `foobar}`, no? Or is it supposed to check the overall string for matching amount of delimiters, even if nested over multiple "levels", as well ...? Getting way too complex for a simple regex replace here IMHO, too. – CBroe Apr 08 '18 at 15:14
  • @CBroe yes {foobar}} results in foobar} – 牛さん Apr 08 '18 at 15:22
  • lookbehind support in JavaScript is very limited (Chrome only?) https://stackoverflow.com/questions/30118815/why-are-lookbehind-assertions-not-supported-in-javascript – Slai Apr 08 '18 at 15:29
  • 1
    @Slai node environment. – 牛さん Apr 08 '18 at 15:31
  • seems pretty easy without RegEx, and pretty complicated with it. Are only RegEx answers acceptable? – Slai Apr 08 '18 at 15:39
  • @Slai maybe I make things complicate by accident with regular expression. Of course without regexp are welcome. For the brackets matching reason I think about regexp. – 牛さん Apr 08 '18 at 15:44

4 Answers4

2

If all of those occurrences are within a large text it would be hardly possible to find a working answer even with more featured regex flavors but if they are separated input strings you may have a solution. Count number of braces at beginning of input string and remove closing braces in that number at the end:

var inputStrings = [
    '{{foo}}',
    '{bar}',
    'baz',
    '{quz',
    '{foobar}}',
    '{foo{bar}}'
];

inputStrings.forEach(function(value) {
    var numberOfBracesAtStart = (value.match(/{/gy) || []).length;;
    console.log(value.replace(new RegExp("^\{{" + numberOfBracesAtStart + "}(.*)\}{" + numberOfBracesAtStart + "}$"), '$1'));
})

RegEx breakdown:

"^\{{" + X + "}(.*)\}{" + X + "}$"
  • "^\{{" + X + "}" Match { at beginning X times
  • (.*) Match any thing between
  • "\}{" + X + "}$" Match } at the end X times

Such a regex will be cooked for {{foo}}:

/^\{{2}(.*)\}{2}$/
revo
  • 47,783
  • 14
  • 74
  • 117
2

Recursion makes things much easier and shorter:

var trim = s => /^{.*}$/.test(s) ? trim(s.slice(1, -1)) : s;

var a = '{{foo}} {foo{bar}} {bar} baz  {quz {foobar}}'.split(' ');

console.log( a.map(trim).join('\n') );
Slai
  • 22,144
  • 5
  • 45
  • 53
1

If the goal really simply is to remove the same amount of opening and closing delimiter characters from each of those lines, then you could also use a simple loop to "count" for how long the first and last characters match { and } respectively, and then simply return the substring of the appropriate length:

var inputStrings = '{{foo}},{foo{bar}},{bar},baz,{quz,{foobar}}'.split(',');

inputStrings.forEach(function(value) {
    var c = 0, l = value.length-1;
    while(value[c]==='{' && value[l-c]==='}') { c++; }
    console.log(value, "->", value.substring(c, l-c+1));
})

Might even go with a for loop here, that would be even more "literal" use of a tool by it's most basic purpose:

var inputStrings = '{{foo}},{foo{bar}},{bar},baz,{quz,{foobar}}'.split(',');

inputStrings.forEach(function(value) {
  for(var c = 0, l = value.length-1; value[c]==='{' && value[l-c]==='}'; c++) ; // <-!!!
  /* ; at the end is an implicit "NOOP" to make this loop do nothing */
  console.log(value, "->", value.substring(c, l-c+1));
})
CBroe
  • 91,630
  • 14
  • 92
  • 150
0

Try This :

var arr = ["{{foo}}", "{foo{bar}}", "{bar}", "baz", "{quz", "{foobar}}"];
var regx = /^{{([^{}]+)}}|{{?(.*)}?}$/g;
var res = [];
for (var i = 0; i < arr.length; i++) {
  var match = arr[i].replace(regx, "$1$2");
  res.push(match);
}
console.log(res);
Mohammed Elhag
  • 4,272
  • 1
  • 10
  • 18