![如何正确解析嵌套的 BBCode [code] 标签(PHP 教程)](https://img.php.cn/upload/article/001/246/273/176680893049687.jpg)
本文详解如何用正则表达式与循环替换策略,精准处理多层嵌套的 `[code=xxx]...[/code]` bbcode 标签,避免截断或错配,确保深层嵌套内容被完整、安全地提取或转换。
在 PHP 中实现 BBCode 解析时,最常见也最容易出错的问题之一就是同名标签的嵌套处理——例如 [code=php]...[code=js]...[/code]...[/code]。原始正则 #\[(code)=(.*?)\](.*?)\[/\1\]#si 采用贪婪匹配,仅能捕获最外层到第一个 [/code] 的内容,导致内层标签被当作普通文本残留,最终解析失败。
✅ 正确思路:非贪婪 + 负向先行断言 + 迭代替换
为支持任意深度嵌套(如 [code]A[code]B[/code]C[/code]),需满足两个核心要求:
- 跳过内部同名标签的起始/结束标记(即不把 [code=...] 或 [/code] 当作当前层级的边界);
- 逐层由内向外剥离,避免一次性匹配引发的截断。
推荐使用以下正则(带注释):
$pattern = '~\[code=([^]]*)]([^[]*(?:\[(?!/code]|code=)[^[]*)*)\[/code]~';
关键设计解析:
立即学习“PHP免费学习笔记(深入)”;
- ([^]]*):捕获语言标识(如 php),限定在 ] 前,防止越界;
- ([^[]*(?:\[(?!/code]|code=)[^[]*)*):主体内容组,核心逻辑:
- [^[]*:匹配不含 [ 的任意字符;
- (?:\[(?!/code]|code=)[^[]*)*:匹配「以 [ 开头但不是 /code] 或 code=」的子串(即允许 [img]、[b] 等其他标签,但跳过嵌套 [code]);
- 整体不依赖 s 修饰符,兼容换行且更可控。
⚠️ 注意:该正则仅匹配一层(最内层),因此必须循环执行直到无替换发生,才能完全展开所有嵌套:
function parseNestedCode($text) {
$count = 0;
do {
$text = preg_replace(
'~\[code=([^]]*)]([^[]*(?:\[(?!/code]|code=)[^[]*)*)\[/code]~i',
'$2',
$text,
-1,
$count
);
} while ($count > 0);
return $text;
}
// 测试用例
$content = '[code=php]test message [code=js]console.log("hello");[/code] and more[/code]';
echo parseNestedCode($content);
// 输出:test message console.log("hello"); and more? 替代方案:PCRE 递归模式(需 PCRE ≥ 8.32)
若环境支持,可启用 (?R) 递归子模式,单次匹配即可处理任意深度:
$pattern = '~\[code=([^]]*)]((?:(?!\[/?code\b).|(?R))*)\[/code]~si';
$result = preg_replace($pattern, '$2', $content);✅ 优势:简洁、一次到位;
⚠️ 风险:(?R) 在复杂嵌套下可能栈溢出,且部分旧版 PHP(生产环境建议优先使用迭代法。
? 最佳实践建议
- 永远先转义输出:替换后的 HTML 内容若含用户输入,务必对 $2 中的内容做 htmlspecialchars() 处理,防止 XSS;
- 限制最大嵌套深度:在循环中加入计数器(如 if ($iterations++ > 10) break;),防恶意超深嵌套导致 DoS;
- 扩展性设计:将 $pattern 和替换逻辑封装为通用方法,便于后续支持 [quote]、[list] 等其他可嵌套标签。
通过上述方法,你不仅能彻底解决 [code] 嵌套解析问题,更能构建一个健壮、可维护的 BBCode 解析器基础框架。











