You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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','&nbsp; &nbsp;', $code[$key]);
            $code[$key] = str_replace('  ', '&nbsp; ', $code[$key]);
            $code[$key] = str_replace('  ', ' &nbsp;', $code[$key]);
            $code[$key] = str_replace(
                array('{', '<', '>' , '\'', '"', "<!--", '$' ),
                array('&#123;', '&lt;', '&gt;', '&#039;', '&quot;', "&lt;!--", '&#036;' ),$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', '&nbsp; &nbsp;', $code);
        $code = preg_replace('/ {2}/', '&nbsp; ', $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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 14:38:10