正则表达式排除包裹在特定 bbcode 标签中的匹配项

Regex excluding matches wrapped in specific bbcode tags

我正在尝试用弯引号替换双引号,除非文本包含在某些标签中,例如 [quote] 和 [code]。

示例输入

[quote="Name"][b]Alice[/b] said, "Hello world!"[/quote]
<p>"Why no goodbye?" replied [b]Bob[/b]. "It's always Hello!"</p>

预期输出

[quote="Name"][b]Alice[/b] said, "Hello world!"[/quote]
<p>“Why no goodbye?” replied [b]Bob[/b]. “It's always Hello!”</p>

我想出了如何通过使用 (*SKIP)(*F) 在 PHP 中优雅地实现我想要的东西,但是我的代码将是 运行 在 javascript 中,而 javascript 解决方案不太理想。

现在我在这些标签处拆分字符串,运行替换,然后将字符串放在一起:

var o = 3;
a = a
    .split(/(\[(?<first>(?:icode|quote|code))[^\]]*?\](?:[\s]*?.)*?[\s]*?\[\/(?:\k<first>)\])/i)
    .map(function(x,i) {
        if (i == o-1 && x) {
            x = '';
        }
        else if (i == o && x)
        {
            x = x.replace(/(?![^<]*>|[^\[]*\])"([^"]*?)"/gi, '“”')
            o = o+3;
        }
        return x;
    }).join('');

Javascript 正则表达式分解

  1. 内部 split()
    • (\[(?<first>icode|quote|code)[^\]]*?\](?:.)*?\[\/(\k<first>)\]) - 捕获括号内的模式:
      • \[(?<first>quote|code|icode)[^\]]*?\] - [quote][code][icode] 开始标记,带有或不带参数,如 =html,例如 [code=html]
      • (?:[\s]*?.)*? - 任何字符 (.) 出现次数超过 0 次(尽可能少),前面有或没有空格,因此如果后面跟着开始标记,它不会中断通过换行符
      • [\s]*? - 0+ 个空格
      • \[\/(\k<first>)\] - [\quote][\code][\icode] 结束标记。匹配 (?<first>) 组中捕获的文本。例如:如果它是 quote 开始标签,它将是 quote 结束标签
  2. 里面replace()
    • (?![^<]*>|[^\[]*\])"([^"]*?)" - 捕获双引号内的文本:
      • (?![^<]*>|[^\[]*\]) - 否定前瞻,查找字符(不是 <[)后跟 >] 并丢弃它们,所以它不会匹配任何 inside bbcode 和 html 标签。例如:[spoiler="Name"]<span style="color: #24c4f9">。请注意,标签中 wrapped 的匹配项保持不变。
      • " - 文字左双引号字符。
      • ([^"]*?) - 任何 0+ 字符,双引号除外。
      • " - 文字结束双引号字符。

SPLIT() 正则表达式演示: https://regex101.com/r/Ugy3GG/1

太糟糕了,因为替换被执行了多次。


同时,使用单个 PHP 正则表达式可以实现相同的结果。我写的正则表达式是基于 .

(\[(?<first>quote|code|icode)[^\]]*?\](?:[\s]*?.)*?[\s]*?\[\/(\k<first>)\])(*SKIP)(*F)|(?![^<]*>|[^\[]*\])"([^"]*?)"

PHP 正则表达式分解

PHP 演示: https://regex101.com/r/fB0lyI/1

这个正则表达式的美妙之处在于它只需要 运行 一次。没有字符串的拆分和连接。有没有办法在javascript中实现它?

嵌套标记很难用 rx 解析,尤其是 JS 的 RegExp。复杂的正则表达式也难以阅读、维护和调试。如果你的需求很简单,一个标签内容替换排除了一些被禁止的标签,考虑一个简单的基于代码的替代 运行-on RegExps:

function curly(str) {
    var excludes = {
        quote: 1,
        code: 1,
        icode: 1
    },
    xpath = [];

    return str.split(/(\[[^\]]+\])/) // breakup by tag markup
        .map(x => { // for each tag and content:
            if (x[0] === "[") { // tag markup:
                if (x[1] === "/") { // close tag
                    xpath.pop(); // remove from current path
                } else { // open tag
                    xpath.push(x.slice(1).split(/\W/)[0]); // add to current path
                } //end if open/close tag
            } else { // tag content
                if (xpath.every(tag =>!excludes[tag])) x = x.replace(/"/g, function repr() {
                    return (repr.z = !repr.z) ? "“" : "”"; // flip flop return value (naive)
                });
            } //end if markup or content?
            return x;
        }) // end term map
        .join("");
} /* end curly() */

var input = `[quote="Name"][b]Alice[/b] said, "Hello world!"[/quote]
<p>"Why no goodbye?" replied [b]Bob[/b]. "It's always Hello!"</p>`;

var wants = `[quote="Name"][b]Alice[/b] said, "Hello world!"[/quote]
<p>“Why no goodbye?” replied [b]Bob[/b]. “It's always Hello!”</p>`;

curly(input) == wants; // true

在我看来,尽管代码有点长,但代码允许文档、缩进和显式命名,这使得这些半复杂的逻辑操作更容易理解。

如果您的需求更复杂,请为 JavaScript 和 map/filter/reduce 使用真正的 BBCode 解析器,它是需要的模型。

因为 JS 缺少回溯动词,你将需要使用那些括号中的块,但稍后按原样替换它们。通过从您自己的正则表达式中获取交替的第二面,最终的正则表达式将是:

\[(quote|i?code)[^\]]*\][\s\S]*?\[\/\]|(?![^<]*>|[^\[]*\])"([^"]*)"

但棘手的部分是使用带有 replace() 方法的回调函数:

str.replace(regex, function([=11=], , ) {
    return  ? [=11=] : '“'  +  + '”';
})

上面的三元运算符 returns [=13=](整个匹配)如果第一个捕获组存在,否则它将第二个捕获组值用大引号括起来,returns 它。

注意:这可能会在不同情况下失败。

live demo here