Escaping markup inside code blocks using regex

Viewed 611

I'm currently trying to implement my own take on Discord's flavor of markdown into my web application. The way I've done so is simply by chaining replace methods, each checking and replacing the syntax with proper HTML tags (I do sanitize, don't worry).

let description = description.replace(/\`{3}([\S\s]*?)\`{3}/g, '<code>$1</code>')
      .replace(/\`(.*)\`/g, '<code class="inline">$1</code>')
      .replace(/~~([\S\s]*?)~~/g, '<s>$1</s>')

The problem I'm facing is that the regex also matches inside of entire code blocks and also inside inline code. This behavior is not wanted.

**bold and 
*italic and 
__underline and 
~~strikethrough~~__***

`~~Not strikethrough~~`
~~`Strikethrough`~~

Normal text

```
~~Not strikethrough~~
```

~~```
Strikethrough
```~~

**bold and 
*italic and 
__underline and 
~~strikethrough~~__***

`~~Not strikethrough~~`
~~`Strikethrough`~~

Normal text

I've tried something like this: /(?<!`[\S\s])\*([\S\s]*?)\*(?!`)/g but I can't get it to work like expected.

I'm still learning regex and continue to find it hard to wrap my head around, so any and all help is much appreciated.

Jan. 4. 2021 Sorry I didn't clarify earlier but the stylings should be "nestable", or in other words able to be combined e.g. ***strong and italic*** should become strong and italic

I've updated the input text (see above) to better encapsulate all probable use cases.

1 Answers

You can use

let text = "**bold and \n*italic and \n__underline and \n~~strikethrough~~__***\n\n`~~Not strikethrough~~`\n~~`Strikethrough`~~\n\nNormal text\n\n```\n~~Not strikethrough~~\n```\n\n~~```\nStrikethrough\n```~~\n\n**bold and \n*italic and \n__underline and \n~~strikethrough~~__***\n\n`~~Not strikethrough~~`\n~~`Strikethrough`~~\n\nNormal text";
const re = /<code(?:\s[^>]*)?>[\s\S]*?<\/code>|`{3}([\S\s]*?)`{3}|`([^`]*)`|~~([\S\s]*?)~~|\*{2}([\s\S]*?)\*{2}(?!\*)|\*([^*]*)\*|__([\s\S]*?)__/g;
let tmp="";
do {
  tmp = text;
  text = text.replace(re, (match, a, b, c, d, e, f) => f ? `<u>${f}</u>` : e ?  `<i>${e}</i>` : d ? `<b>${d}</b>` : c ? `<s>${c}</s>` : b ? `<code class="inline">${b}</code>` : a ? `<code>${a}</code>` : match);
}
while (text != tmp);
console.log(text);

See the regex demo.

The point is to devise a single regex for a single pass and capture the string parts into separate groups to apply different replacement logic to.

There are three alternatives matching

  • `{3}([\S\s]*?)`{3} - any substring between triple asterisks capturing it into Group 1 (x)
  • `([^`]*)` - any substring between single asterisks capturing it into Group 2 (y)
  • ~~([\S\s]*?)~~ - any substring between ~~ capturing it into Group 3 (z)

See the regex demo.

Related