Find substring containing the escaped form of a delimiter (Regexp)

Viewed 258

Hi All!

I am playing with markdown, dealing with inline markers and escaped characters.

Problem:

I want to transform this: some text *some number \* other number* more text

Into this: some text <strong>some number * other number</strong> more text

My current pattern is: /((?!\\)\*)(.*?)((?!\\)\*)/g

But the (.*?) group seems to capture the \ character, so the third group finds the second * character and stops looking for the third one, which should be its target.

Possible solution:

I can solve this problem using negative lookbehind: /((?<!\\)\*)(.*?)((?<!\\)\*)/g, but I'd like to avoid it, if it is possible.

Can I modify my other pattern to make it work?

3 Answers

You may use

var str = "some text *some number \\* other number* more text";
console.log(
 str.replace(/((?:^|[^\\])(?:\\{2})*)\*([^\\*]*(?:\\[\s\S][^*\\]*)*)\*/g, 
   function($0, $1, $2) { return $1 + '<strong>' + $2.replace(/\\([\s\S])/g, '$1') + '</strong>'; }
 )
)

The first /((?:^|[^\\])(?:\\{2})*)\*([^\\*]*(?:\\[\s\S][^*\\]*)*)\*/g regex matches all the strings within unescaped *:

  • ((?:^|[^\\])(?:\\{2})*) - Group 1:
    • (?:^|[^\\]) - start of string or a non-backslash
    • (?:\\{2})* - any 0+ occurrences of double backslash (this avoids matching escaped *)
  • \* - a * char
  • ([^\\*]*(?:\\[\s\S][^*\\]*)*) - Group 2:
    • [^\\*]* - 0+ chars other than \ and *
    • (?:\\[\s\S][^*\\]*)* - 0+ sequences of
      • \\[\s\S] - a \ and any char
      • [^*\\]* - 0+ chars other than \ and *
  • \* - a * char.

The match is passed to the anonymous method as the second argument to the replace method and the contents of Group 2 are processed to "unescape" any escape sequence with .replace(/\\([\s\S])/g, '$1'): \\ matches a backslash and ([\s\S]) matches and captures any char into Group 1, and this is what remains after the replacement with the group placeholder $1.

You can use this

\*(.*)\*

This uses above regex to find * up to the last *. And than with \\(.) i am finding the escaped character and replacing it with captured group.

const regex = /\*(.*)\*/gm;
const str = `some text *some number \\* other number* more text`;
const subst = `<strong>$1</strong>`;

// The substituted value will be contained in the result variable
const result = str.replace(regex, subst);
const finalResult = result.replace(/\\(.)/,'$1')   //replacing escaped character here

console.log(finalResult);

UPDATE: For matching more than one substring

const regex = /\*(.*?[^\\])\*/gm;
const str = `some text *some number \\* other number* blah blah *some number \\* other number* more text`;
const subst = `<strong>$1</strong>`;

// The substituted value will be contained in the result variable
const result = str.replace(regex, subst);
const finalResult = result.replace(/\\(.)/g,'$1')   //replacing escaped character here

console.log(finalResult);

There could be a simpler way to accomplish the same task using the following regex:

\\.|\*((\\.|[^*])+)\*

The idea is matching a desired string should occur after all escaped characters are consumed. We try to match all escaped characters using first side of alternation then at the second attempt we want to match our desired pattern if exists.

JS code:

var str = `some text *some number \\* other number* more text`

console.log(str.replace(/\\.|\*((\\.|[^*])+)\*/g, function(match, $1) {
 return $1 ? '<strong>' + $1 + '</strong>' : match;
}));

Breakdown:

  • \\. Match an escaped character
  • | Or
  • \* Match a literal *
  • ( Start of first capturing group
    • ( Start of second capturing group
      • \\. Match an escaped character
      • | Or
      • [^*]+ Match anything except *
    • )+ End of second capturing group, repeat one or more time
    • ) End of first capturing group
  • \* Match a literal *
Related