我正在尝试用大引号替换双引号,除非文本被包装在某些标签中,例如 [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 正则表达式分解
- 内部
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>)
组中捕获的文本。例如:如果是报价开始标签,它将是报价结束标签
- 内部
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,并根据需要映射/过滤/减少它的模型。