admin管理员组

文章数量:1316388

I am trying to parse a markdown content with the use of regex. To grab bold and italic items from the input, I'm currently using a regex:

/(\*\*)(?<bold>[^**]+)(\*\*)|(?<normal>[^`*[~]+)|\*(?<italic>[^*]+)\*/g

Regex101 Link:

The problem with this regex are:
  • if there is a single * in between a bold text content, the match is breaked
  • if there are long texts like ******* anywhere in between the match is broken

#####: tried with: I tried removing the [^**] part in the bold group but that messed up the bold match with finding the last ** occurrence and including all `**`` chars within

What I want to have:
  • accurate bold
  • * allowed inside bold
  • accurate italics

Language: Javascript

Assumptions:

Bold text wrapped inside ** Italic text wrapped inside *

I am trying to parse a markdown content with the use of regex. To grab bold and italic items from the input, I'm currently using a regex:

/(\*\*)(?<bold>[^**]+)(\*\*)|(?<normal>[^`*[~]+)|\*(?<italic>[^*]+)\*/g

Regex101 Link: https://regex101./r/2zOMid/1

The problem with this regex are:
  • if there is a single * in between a bold text content, the match is breaked
  • if there are long texts like ******* anywhere in between the match is broken

#####: tried with: I tried removing the [^**] part in the bold group but that messed up the bold match with finding the last ** occurrence and including all `**`` chars within

What I want to have:
  • accurate bold
  • * allowed inside bold
  • accurate italics

Language: Javascript

Assumptions:

Bold text wrapped inside ** Italic text wrapped inside *

Share Improve this question edited Oct 2, 2022 at 7:17 Tyzoid 1,07813 silver badges31 bronze badges asked Jul 16, 2022 at 8:48 Kiran ParajuliKiran Parajuli 1,0558 silver badges17 bronze badges 4
  • 1 Do not use a single regex here since matches are overlapping. Use bold regex first, then italics. – Wiktor Stribiżew Commented Jul 16, 2022 at 9:09
  • yes, i'm trying to do the same. for that the bold match in the above regex should allow to contain single * char within. if I do that the bold match is messed up. can i do that properly with regex? – Kiran Parajuli Commented Jul 16, 2022 at 11:13
  • Shouldn't one, by markdown rules, in need to literally show an asterisk * escape it? ***\**** for the exact reason? – Roko C. Buljan Commented Jul 16, 2022 at 11:19
  • for me, ***** & **\*** means a normal text. If we want just an asterisk as bold maybe using raw HTML is better (markdown supports that). but if the input is like **ab*cd** then ab*cd should be a match. – Kiran Parajuli Commented Jul 16, 2022 at 11:40
Add a ment  | 

5 Answers 5

Reset to default 4

There was some discussion in the chat going on. Just to have it mentioned, there is no requirement yet on how to deal with escaped characters like \* so I didn't take attention of it.

Depending on the desired oute I'd pick a two step solution and keep the patterns simple:

str = str.replace(/\*\*(.+?)\*\*(?!\*)/g,'<b>$1</b>').replace(/\*([^*><]+)\*/g,'<i>$1</i>');
  • Step 1: Replace bold parts
    Replace \*\*(.+?)\*\*(?!\*) with <b>$1</b> -> Regex101 demo
    It captures (.+?) one or more characters between ** lazily to $1
    and uses a lookahead for matching the outher most * at the end.

  • Step 2: Now as the amount of remaining * is reduced, italic parts
    Replace remaining \*([^*><]+)\* to <i>$1</i> -> Regex101 demo
    [^*><]+ matches one or more characters that are not *, > or <.

Here is the JS-demo at tio.run

Myself I don't think it's a good idea to rely on the amount of the same character for distinguishing between kinds of replacement. The way how it works finally gets a matter of taste.

[^**] will not avoid two consecutive *. It is a character class that is no different from [^*]. The repeated asterisk has no effect.

The pattern for italic should better e in front of the normal part, which should capture anything that remains. This could even be a sole asterisk (for example) -- the pattern for normal text should allow this.

It will be easier to use split and use the bold/italic pattern for matching the "delimiter" of the split, while still capturing it. All the rest will then be "normal". The downside of split is that you cannot benefit from named capture groups, but they will just be represented by separate entries in the returned array.

I will ignore the other syntax that markdown can have (like you seem to hint at with [ and ~ in your regex). On the other hand, it is important to deal well with backslash, as it is used to escape an asterisk.

Here is the regular expression (link):

(\*\*?)(?![\s\*])((?:[\s*]*(?:\\[\\*]|[^\\\s*]))+?)\1

Here is a snippet with two functions:

  • a function that first splits the input into tokens, where each token is a pair, like ["normal", " this is normal text "] and ["i", "text in italics"]
  • another function that uses these tokens to generate HTML

The snippet is interactive. Just type the input, and the output will be rendered in HTML using the above sequence.

function tokeniseMarkdown(s) {
    const regex = /(\*\*?)(?![\s\*])((?:[\s*]*(?:\\[\\*]|[^\\\s*]))+?)\1/gs;
    const styles = ["i", "b"];
    // Matches follow this cyclic order: 
    //   normal text, mark (= "*" or "**"), formatted text, normal text, ...
    const types = ["normal", "mark", ""];
    return s.split(regex).map((match, i, matches) =>
        types[i%3] !== "mark" && match &&
            [types[i%3] || styles[matches[i-1].length-1], 
             match.replace(/\\([\\*])/g, "$1")]
    ).filter(Boolean); // Exclude empty matches and marks
}

function tokensToHtml(tokens) {
    const container = document.createElement("span");
    for (const [style, text] of tokens) {
        let node = style === "normal" ? document.createTextNode(text) 
                                      : document.createElement(style);
        node.textContent = text;
        container.appendChild(node);
    }
    return container.innerHTML;
}


// I/O management
document.addEventListener("input", refresh);

function refresh() {
    const s = document.querySelector("textarea").value;
    const tokens = tokeniseMarkdown(s);
    document.querySelector("div").innerHTML = tokensToHtml(tokens);
}
refresh();
textarea { width: 100%; height: 6em }
div { font: 22px "Times New Roman" }
<textarea>**fi*rst b** some normal text here **second b**  *first i* normal *second i* normal again</textarea><br>

<div></div>

Looking some more about the negative lookaheads, I came up with this regex:

/\*\*(?<bold>(?:(?!\*\*).)+)\*\*|`(?<code>[^`]+)`|~~(?<strike>(?:(?!~~).)+)~~|\[(?<linkTitle>[^]]+)]\((?<linkHref>.*)\)|(?<normal>[^`[*~]+)|\*(?<italic>[^*]+)\*|(?<tara>[*~]{3,})|(?<sitara>[`[]+)/g

Regex101

this pretty much works for me as per my input scenarios. If someone has a more optimized regex, please ment.

You can choose the tags depending on the number of asterisks. (1 → italic, 2 → bold, 3 → bold+italic)

function simpleMarkdownTransform(markdown) {
  return markdown
    .replace(/</g, '&lt') // disallow tags
    .replace(/>/g, '&gt')
    .replace(
      /(\*{1,3})(.+?)\1(?!\*)/g,
      (match, { length: length }, text) => {
        if (length !== 2) text = text.italics()
        return length === 1 ? text : text.bold()
      }
    )
    .replace(/\n/g, '<br>') // new line
}

Example:

simpleMarkdownTransform('abcd **bold** efgh *italic* ijkl ***bold-italic*** mnop') 
// "abcd <b>bold</b> efgh <i>italic</i> ijkl <b><i>bold-italic</i></b> mnop"

italic: ((?<!\s)\*(?!\s)(?:(?:[^\*\*]*(?:(?:\*\*[^\*\*]*){2})+?)+?|[^\*\*]+?)(?<!\s)\*)

(?<!\s)\*(?!\s) means matching the start * with no space around,
(?:(?:[^\*\*]*(?:(?:\*\*[^\*\*]*){2})+?)+? means match ** with even appearance, by which negalates meaningless ** inside intalic.
|[^\*\*]+? means if there's no match for one or more ** pair, match anything except a single **.(this "or" order is important)
(?<!\s)*) means matching the end * with no space ahead
And ?: is non-capturing group in js, you can delete it if not needing

bold: ((?<!\s)\*\*(?!\s)(?:[^\*]+?|(?:[^\*]*(?:(?:\*[^\*]*){2})+?)+?)(?<!\s)\*\*)

Similar to italic, except the order of * pair and other character.

Together you can get:
((?<!\s)\*(?!\s)(?:(?:[^\*\*]*(?:(?:\*\*[^\*\*]*){2})+?)+?|[^\*\*]+?)(?<!\s)\*)|((?<!\s)\*\*(?!\s)(?:[^\*]+?|(?:[^\*]*(?:(?:\*[^\*]*){2})+?)+?)(?<!\s)\*\*)

See the result here: https://regex101./r/9gTBpj/1

本文标签: javascriptRegex accurately match bold (**) and italics (*) item(s) from the inputStack Overflow