正则表达式不包括包装在特定 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+(尽可能少)出现,前面有空格或不带空格,因此如果开始标记后跟换行符,它不会中断
      • [s]*?- 0+ 空格
      • [/(k<first>)]-[quote][code][icode]结束标记。匹配在(?<first>)组中捕获的文本。例如:如果是报价开始标签,它将报价结束标签
  2. 内部replace()
    • (?![^<]*>|[^[]*])"([^"]*?)"- 捕获双引号内的文本:
      • (?![^<]*>|[^[]*])- 负面展望,查找字符(未<[),后跟>]并丢弃它们,因此它不会匹配 bbcode 和 html 标签中的任何内容。例如:[spoiler="Name"]<span style="color: #24c4f9">。请注意,包装在标签中的匹配项保持不变。
      • "- 文字开始双引号字符。
      • ([^"]*?)- 任何 0+ 字符,双引号除外。
      • "- 文字右双引号字符。

分裂() 正则表达式演示:

https://regex101.com/r/Ugy3GG/1这很糟糕,因为替换被执行了多次。


同时,使用单个 PHP 正则表达式可以实现相同的结果。我编写的正则表达式基于不在 bbcode 标签内的匹配正则表达式模式。

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

PHP 正则表达式细分

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

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

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

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

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

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

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

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

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

在此处观看现场演示

嵌套标记很难用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

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

如果你的需求更复杂,请使用真正的BBCode解析器来做JavaScript,并根据需要映射/过滤/减少它的模型。

最新更新