【问题标题】:Regex: accurately match bold (**) and italics (*) item(s) from the input正则表达式:准确匹配输入中的粗体 (**) 和斜体 (*) 项目
【发布时间】:2022-08-23 02:50:41
【问题描述】:

我正在尝试使用正则表达式解析降价内容。抢大胆的斜体来自输入的项目,我目前正在使用正则表达式:

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

正则表达式 101 链接:https://regex101.com/r/2zOMid/1

这个正则表达式的问题是:
  • 如果在粗体文本内容之间有一个*,则匹配被中断
  • 如果有像*******这样的长文本,则匹配中断

#####:尝试使用: 我尝试删除 bold 组中的 [^**] 部分,但是这使粗体匹配与找到最后一个 ** 出现并包括所有 `**`` 字符

我想拥有的:
  • 准确的粗体
  • * 允许在粗体中
  • 准确的斜体

语言:Javascript

假设:

粗体字包裹在** 内 斜体文字包裹在 *

  • 不要在此处使用单个正则表达式,因为匹配项是重叠的。首先使用粗体正则表达式,然后使用斜体。
  • 是的,我正在尝试做同样的事情。因为上述正则表达式中的粗体匹配应该允许在其中包含单个 * 字符。如果我这样做,粗体匹配就会搞砸。我可以用正则表达式正确地做到这一点吗?
  • 根据降价规则,不应该直接显示一个星号* 转义它吗? ***\\**** 的确切原因?
  • 对我来说,***** & **\\*** 表示普通文本。如果我们只想要一个粗体的星号,那么使用原始 HTML 可能会更好(markdown 支持)。但如果输入类似于**ab*cd**,那么ab*cd 应该是匹配的。

标签: javascript regex markdown bold italics


【解决方案1】:

[^**]不会回避连续两个*。它是一个与[^*] 没有区别的字符类。重复的星号无效。

斜体的图案最好出现在正常部分的前面,它应该捕捉到任何剩余的东西。这甚至可以是唯一的星号(例如)——普通文本的模式应该允许这样做。

使用split 并使用粗体/斜体模式来匹配拆分的“分隔符”会更容易,同时仍然捕获它。然后所有其余的将是“正常的”。 split 的缺点是您无法从命名捕获组中受益,但它们只会由返回数组中的单独条目表示。

我将忽略 markdown 可能具有的其他语法(就像您在正则表达式中使用 [~ 暗示的那样)。另一方面,处理好反斜杠很重要,因为它用于转义星号。

这是正则表达式(链接):

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

这是一个具有两个功能的 sn-p:

  • 首先将输入拆分为标记的函数,其中每个标记是一对,例如["normal", " this is normal text "]["i", "text in italics"]
  • 另一个使用这些标记生成 HTML 的函数

sn-p 是交互式的。只需键入输入,输出将使用上述顺序以 HTML 格式呈现。

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>

【讨论】:

  • THX @trincot 的评论和 sn-p。我试图通过正则表达式来获得这个。在您的回答中,您曾经提到过which should capture anything that remains.,我可以用正则表达式做到这一点吗?像 /oneGroup|anotherGroup|everyThingElse/`。
  • 在 JavaScript 中,你只能这样做其他一切是一个非常聪明的模式,它可以捕获任何直到前一组开始匹配的点。这是不可维护的,因为您将向它们添加更多组和/或更多复杂性。第二种选择是让还要别的吗抓住特点。这意味着您将有很多连续的单字符匹配,然后您需要再次加入这些匹配。
  • “我试图通过正则表达式得到这个”:是的,我刚刚添加了一个 sn-p 来说明如何使用正则表达式的结果。正则表达式本身将识别所有粗体和斜体部分,以及周围的标记 (**),它会跳过正常部分。这就是我所理解的问题的目标——抓住粗体和斜体部分。
【解决方案2】:

有一些discussion in the chat 正在进行。只是提到它,还没有关于如何处理像\* 这样的转义字符的要求,所以我没有注意它。

根据期望的结果,我会选择一个两步解决方案并保持模式简单:

str = str.replace(/\*\*(.+?)\*\*(?!\*)/g,'<b>$1</b>').replace(/\*([^*><]+)\*/g,'<i>$1</i>');

Here is the JS-demo at tio.run

我自己认为依靠相同字符的数量来区分各种替换不是一个好主意。它的工作方式最终得到了品味。

【讨论】:

    【解决方案3】:

    再看一些关于负前瞻的信息,我想出了这个正则表达式:

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

    Regex101

    根据我的输入场景,这对我来说非常有用。如果有人有更优化的正则表达式,请发表评论。

    【讨论】:

    • 您将如何使用降价来获得以星号结尾的粗体部分,例如嘿*?
    • 对于这种类型的要求,我会使用&lt;b&gt;hey*&lt;/b&gt;
    【解决方案4】:

    斜体:((?&lt;!\s)\*(?!\s)(?:(?:[^\*\*]*(?:(?:\*\*[^\*\*]*){2})+?)+?|[^\*\*]+?)(?&lt;!\s)\*)

    (?&lt;!\s)\*(?!\s) 表示匹配开始 * 周围没有空格,
    (?:(?:[^\*\*]*(?:(?:\*\*[^\*\*]*){2})+?)+? 表示匹配 ** 与外观均匀,从而否定 intalic 内无意义的 **。
    |[^\*\*]+? 表示如果一对或多对 ** 不匹配,则匹配除单个 ** 之外的任何内容。(此“或”顺序很重要)
    (?<!\s)*) 表示匹配结尾 * 前面没有空格
    而 ?: 是 js 中的非捕获组,不需要的可以删除

    大胆的: ((?&lt;!\s)\*\*(?!\s)(?:[^\*]+?|(?:[^\*]*(?:(?:\*[^\*]*){2})+?)+?)(?&lt;!\s)\*\*)

    类似于斜体,除了*对和其他字符的顺序。

    您可以一起获得:
    ((?&lt;!\s)\*(?!\s)(?:(?:[^\*\*]*(?:(?:\*\*[^\*\*]*){2})+?)+?|[^\*\*]+?)(?&lt;!\s)\*)|((?&lt;!\s)\*\*(?!\s)(?:[^\*]+?|(?:[^\*]*(?:(?:\*[^\*]*){2})+?)+?)(?&lt;!\s)\*\*)
    https://regexr.com/中测试, view result screenshot

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-02-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-02
      • 1970-01-01
      相关资源
      最近更新 更多