正则表达式不包括包装在特定bbcode标签中的匹配项

时间:2019-03-02 18:00:36

标签: javascript php regex bbcode

我正在尝试将双引号替换为大括号,除非将文本包裹在某些标签中,例如[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, '“$1”')
            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+(尽可能少)出现,以空格开头或不以空格开头,因此如果开头标记后接a,则不会中断换行符
      • [\s]*?-超过0个空格
      • \[\/(\k<first>)\]-[\quote][\code][\icode]结束标记。匹配(?<first>)组中捕获的文本。例如:如果是 quote 开头标签,则将是 quote 结束标签
  2. replace()内部:
    • (?![^<]*>|[^\[]*\])"([^"]*?)"-捕获双引号内的文本:
      • (?![^<]*>|[^\[]*\])-前瞻性否定,查找字符(不是<[)后跟>]并丢弃它们,因此它不会与内部 bbcode和html标签匹配。例如:[spoiler="Name"]<span style="color: #24c4f9">。请注意,标记中的包裹匹配项保持不变。
      • "-文字双引号字符。
      • ([^"]*?)-任意0+字符,双引号除外。
      • "-文字双引号字符。

SPLIT()REGEX DEMO: https://regex101.com/r/Ugy3GG/1

那太可怕了,因为替换执行了多次。


同时,一个PHP正则表达式可以实现相同的结果。我写的正则表达式基于Match regex pattern that isn't within a bbcode tag

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

PHP正则表达式故障

  • (\[(?<first>quote|code|icode)[^\]]*?\](?:[\s]*?.)*?[\s]*?\[\/(\k<first>)\])(*SKIP)(*F)-像上面的JavaScript split()一样匹配捕获括号内的模式,然后(*SKIP)(*F)使正则表达式引擎忽略匹配的文本。
  • |-或
  • (?![^<]*>|[^\[]*\])"([^"]*?)"-以与JavaScript replace()相同的方式捕获双引号内的文本

PHP DEMO: https://regex101.com/r/fB0lyI/1

此正则表达式的优点在于它只需要运行一次。请勿拆分和连接字符串。有没有办法在javascript中实现它?

2 个答案:

答案 0 :(得分:2)

由于JS缺少回溯动词,因此您需要使用这些放在方括号中的块,但以后请按原样替换它们。通过从您自己的正则表达式获取替代的第二面,最终的正则表达式将为:

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

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

str.replace(regex, function($0, $1, $2) {
    return $1 ? $0 : '“'  + $2 + '”';
})

如果第一个捕获组存在,则上述三元运算符将返回$0(完全匹配),否则将第二个捕获组值括在引号中并返回。

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

请参见live demo here

答案 1 :(得分:1)

嵌套标记很难用rx解析,尤其是JS的RegExp。复杂的正则表达式也很难读取,维护和调试。如果您的需求很简单,可以用标签内容替换掉一些禁止使用的标签,那么可以考虑使用一种简单的基于代码的替代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使用真正的BBCode解析器,并根据需要映射/过滤/减少其模型。