使用preg_replace转换BBCode到HTML遇问题,求代码优化方案
BBCode转HTML的正则处理改进建议
问题背景
升级网站引擎后,新版本不支持BBCode,但现有所有论坛帖子和文章均使用该格式创作。目前用str_replace处理[b]这类简单标签没有问题,但preg_replace处理带参数或跨行/复杂内容的BBCode时遇到障碍,以下是现有代码及改进方案:
现有代码
核心转换函数
function bbcodeconvert($text) { $text = " ".$text; $bbcodes = array( '[b]' => '<strong>', '[/b]' => '</strong>' ); foreach($bbcodes as $bbcode => $bbcodehtml) { $text = str_replace($bbcode,$bbcodehtml,$text); } $bbcodes2 = array( '\\[img\\]([^\\\'\;\?([]*)\.(jpg|jpeg|gif|png)\\[/img\\]' => '<img src="\\1.\\2" alt="">', '\\[img=([^\\\'\;\?([]*)\.(jpg|jpeg|gif|png)\\]([^\\[]*)\.(jpg|jpeg|gif|png)\\[/img\\]' => '<a href="\\1.\\2" data-lightbox="spic"><img src="\\3.\\4" alt=""></a>', '\\[youtube\\]([^\\([]*)\\[/youtube\\]' => '<iframe width="540" height="320" src="https://www.youtube.com/embed/\\1?wmode=opaque" frameborder="0" allowfullscreen></iframe>', '\\[colleft\\]([^\\[]*)\\[/colleft\\]' => '<div class="colleft">\\1</div>', '\\[colright\\]([^\\[]*)\\[/colright\\]' => '<div class="colright">\\1</div>' ); foreach($bbcodes2 as $bbcode => $bbcodehtml) { $text = preg_replace($bbcode,$bbcodehtml,$text); } return(mb_substr($text,1)); }
旧版解析函数
function oldparse($text) { global $sys, $L; $text = ' '.$text; $code = array(); $unique_seed = $sys['unique']; $ii = 5000; $p1 = 1; $p2 = 1; while ($p1 > 0 && $p2 > 0 && $ii < 5031) { $ii++; $p1 = mb_strpos($text, '[code]'); $p2 = mb_strpos($text, '[/code]'); if ($p2 > $p1 && $p1 > 0) { $key = '**'.$ii.$unique_seed.'**'; $code[$key]= mb_substr ($text, $p1 + 6, ($p2 - $p1) - 6); $code_len = mb_strlen($code[$key]) + 13; $code[$key] = str_replace('\t',' ', $code[$key]); $code[$key] = str_replace(' ', ' ', $code[$key]); $code[$key] = str_replace(' ', ' ', $code[$key]); $code[$key] = str_replace( array('{', '<', '>' , '\'', '"', "<!--", '$' ), array('{', '<', '>', ''', '"', "<!--", '$' ),$code[$key]); $code[$key] = "<div class=\"codetitle\">".$L['bbcodes_code']."</div><div class=\"code\">".trim($code[$key])."</div>"; $text = substr_replace($text, $key, $p1, $code_len); } } $text = sed_bbcode($text); return(mb_substr($text, 1)); }
执行示例
$sql = sql_query("SELECT some_text FROM forum_posts WHERE topicid='$s' LIMIT 1"); while ($row = sql_fetchassoc($sql)) { $post_text = oldparse($row['some_text']); } $output = "Converted text:$post_text";
具体改进方案
1. 修复正则表达式的匹配范围问题
原正则中使用[^\\\'\;\?([]*这类严格的字符排除规则,会导致URL中包含?、(等常见字符时无法匹配,应改为更通用的匹配逻辑:
- 对于标签内容,用
[^]]*匹配到]为止(适用于无嵌套的标签) - 对于标签内的URL,用
https?://[^\\s]+匹配合法的HTTP/HTTPS链接,避免扩展名限制过于死板 - 添加
i修饰符忽略大小写,支持JPG、GIF等扩展名;添加s修饰符让.匹配换行,支持跨行内容
修改后的正则示例:
$bbcodes2 = array( // 带链接的img优先处理,避免被普通img先匹配 '/\[img=(https?:\/\/[^\]]+)\](https?:\/\/[^\]]+)\[\/img\]/i' => '<a href="$1" data-lightbox="spic"><img src="$2" alt=""></a>', // 普通img标签 '/\[img\](https?:\/\/[^\]]+)\[\/img\]/i' => '<img src="$1" alt="">', // YouTube标签,支持完整链接和仅ID的情况 '/\[youtube\]((?:https?:\/\/www\.youtube\.com\/watch\?v=)?([a-zA-Z0-9_-]+))\[\/youtube\]/i' => '<iframe width="540" height="320" src="https://www.youtube.com/embed/$2?wmode=opaque" frameborder="0" allowfullscreen></iframe>', // 列布局标签,支持跨行内容 '/\[colleft\](.*?)\[\/colleft\]/is' => '<div class="colleft">$1</div>', '/\[colright\](.*?)\[\/colright\]/is' => '<div class="colright">$1</div>' );
2. 调整正则处理顺序
- 带参数的标签(如
[img=...])必须放在同类型无参数标签(如[img])之前处理,否则无参数的正则会先匹配内容,导致带参数的标签无法被识别。 - 所有正则使用
/作为分隔符,替代直接写转义的\\[,提升代码可读性。
3. 优化代码块处理逻辑
原oldparse中的循环替换[code]标签效率低且有次数限制,改用preg_replace_callback一次性处理所有代码块:
function oldparse($text) { global $sys, $L; // 处理代码块 $text = preg_replace_callback('/\[code\](.*?)\[\/code\]/is', function($matches) use ($L) { $code = $matches[1]; // 替换制表符和连续空格 $code = str_replace('\t', ' ', $code); $code = preg_replace('/ {2}/', ' ', $code); // 转义特殊字符,避免XSS $code = htmlspecialchars($code, ENT_QUOTES); return "<div class=\"codetitle\">".$L['bbcodes_code']."</div><div class=\"code\">".trim($code)."</div>"; }, $text); $text = sed_bbcode($text); return $text; }
4. 增加XSS防护
所有从BBCode提取的属性值(如图片URL、链接地址)必须用htmlspecialchars转义,防止注入攻击。需改用preg_replace_callback实现动态转义:
// 示例:带链接的img标签转义 '/\[img=(https?:\/\/[^\]]+)\](https?:\/\/[^\]]+)\[\/img\]/i' => function($matches) { $href = htmlspecialchars($matches[1], ENT_QUOTES); $src = htmlspecialchars($matches[2], ENT_QUOTES); return "<a href=\"$href\" data-lightbox=\"spic\"><img src=\"$src\" alt=\"\"></a>"; },
5. 统一字符编码处理
确保所有字符串操作使用多字节函数(如mb_*系列),避免中文等多字节字符出现截断问题,原代码中substr_replace应改为mb_substr_replace。
内容的提问来源于stack exchange,提问作者Leks
相关产品推荐
相关产品推荐

