核心实现思路

实现 UBB 标签主要有两种方法:

网页如何实现 php ubb标签
(图片来源网络,侵删)
  1. 字符串替换法:使用 PHP 的 str_replace()preg_replace() 函数,将 UBB 标签一对一地替换成对应的 HTML 标签,这种方法简单直接,适合处理少量、固定的标签。
  2. 正则表达式法:使用更强大的正则表达式 (preg_replace()),可以处理更复杂的嵌套和带属性的标签(如 [url=http://example.com]链接[/url]),这是最常用和推荐的方法。

我们将重点介绍第二种方法,因为它更灵活、更强大。


第一步:准备一个简单的 HTML 表单

我们需要一个让用户输入内容的界面,创建一个 index.php 文件。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">PHP UBB 标签演示</title>
    <style>
        body { font-family: sans-serif; line-height: 1.6; margin: 2em; }
        textarea { width: 100%; height: 150px; padding: 10px; }
        input[type="submit"] { padding: 10px 20px; }
        .ubb-preview { margin-top: 20px; padding: 15px; border: 1px solid #ccc; background-color: #f9f9f9; }
    </style>
</head>
<body>
    <h1>PHP UBB 标签演示</h1>
    <p>请输入包含 UBB 标签的文本,<code>[b]粗体[/b]</code>, <code>[i]斜体[/i]</code>, <code>[url=http://www.php.net]PHP官网[/url]</code></p>
    <form action="process.php" method="post">
        <textarea name="ubb_content" placeholder="在这里输入你的内容..."><?php echo isset($_POST['ubb_content']) ? htmlspecialchars($_POST['ubb_content']) : ''; ?></textarea>
        <br><br>
        <input type="submit" value="提交并预览">
    </form>
    <?php if (isset($_POST['ubb_content']) && !empty($_POST['ubb_content'])): ?>
        <div class="ubb-preview">
            <h3>预览效果:</h3>
            <div>
                <?php
                // 在这里我们将引入处理逻辑
                require_once 'ubb_parser.php';
                $html_output = parse_ubb($_POST['ubb_content']);
                echo $html_output;
                ?>
            </div>
        </div>
    <?php endif; ?>
</body>
</html>

第二步:创建 UBB 解析器 (ubb_parser.php)

这是核心部分,我们将创建一个单独的文件 ubb_parser.php,用于存放所有转换逻辑,使用正则表达式,我们可以非常优雅地完成这个任务。

<?php
/**
 * 将 UBB 标签转换为 HTML 标签
 * @param string $ubb_text 包含 UBB 标签的文本
 * @return string 转换后的 HTML 文本
 */
function parse_ubb($ubb_text) {
    // 1. 安全处理:防止 XSS 攻击
    // 在转换前,先将所有尖括号等特殊字符转义
    $safe_text = htmlspecialchars($ubb_text, ENT_QUOTES, 'UTF-8');
    // 2. 定义 UBB 标签的正则表达式替换规则
    // 数组的键是正则表达式,值是替换成的 HTML 字符串
    $ubb_patterns = [
        // [b]粗体[/b]
        '/\[b\](.*?)\[\/b\]/is' => '<b>$1</b>',
        // [i]斜体[/i]
        '/\[i\](.*?)\[\/i\]/is' => '<i>$1</i>',
        // [u]下划线[/u]
        '/\[u\](.*?)\[\/u\]/is' => '<u>$1</u>',
        // [size=大小]文本[/size]
        '/\[size=([1-7])\](.*?)\[\/size\]/is' => '<font size="$1">$2</font>', // HTML5已废弃,但为了演示
        // 更现代的方式是使用 CSS
        '/\[size=(\d+)px\](.*?)\[\/size\]/is' => '<span style="font-size: $1px">$2</span>',
        // [color=颜色]文本[/color]
        '/\[color=([a-zA-Z0-9#]+)\](.*?)\[\/color\]/is' => '<span style="color: $1">$2</span>',
        // [align=对齐方式]文本[/align]
        '/\[align=(left|center|right|justify)\](.*?)\[\/align\]/is' => '<div style="text-align: $1">$2</div>',
        // [list]列表项[*]列表项[/list]
        '/\[list\](.*?)\[\/list\]/is' => '<ul>$1</ul>',
        '/\[\*\](.*?)(?=\[\*\]|\[\/list\]|\z)/is' => '<li>$1</li>',
        // [url]链接[/url]
        '/\[url\](.*?)\[\/url\]/is' => '<a href="$1" target="_blank">$1</a>',
        // [url=链接地址]显示文字[/url]
        '/\[url=(.*?)\](.*?)\[\/url\]/is' => '<a href="$1" target="_blank">$2</a>',
        // [img]图片地址[/img]
        '/\[img\](.*?)\[\/img\]/is' => '<img src="$1" alt="用户图片" style="max-width: 100%; height: auto;">',
        // [email]邮箱地址[/email]
        '/\[email\](.*?)\[\/email\]/is' => '<a href="mailto:$1">$1</a>',
        // [quote]引用内容[/quote]
        '/\[quote\](.*?)\[\/quote\]/is' => '<blockquote style="border-left: 2px solid #ccc; padding-left: 10px; margin-left: 10px;">$1</blockquote>',
        // [code]代码[/code]
        '/\[code\](.*?)\[\/code\]/is' => '<pre style="background-color: #f0f0f0; padding: 10px; border-radius: 4px;"><code>$1</code></pre>',
    ];
    // 3. 执行替换
    // 使用 preg_replace_callback 可以实现更复杂的逻辑,但这里用 preg_replace 足够
    $html_text = preg_replace(array_keys($ubb_patterns), array_values($ubb_patterns), $safe_text);
    // 4. 处理换行
    // 将 UBB 的换行符 [br] 或单换行转换为 HTML 的 <br> 标签
    $html_text = str_replace("[br]", "<br>", $html_text);
    $html_text = nl2br($html_text); // 将 \n 转换为 <br>
    return $html_text;
}
?>

代码解释:

网页如何实现 php ubb标签
(图片来源网络,侵删)
  1. htmlspecialchars(): 这是至关重要的安全步骤,它将 <, >, &, , 等字符转换为 HTML 实体,这样做可以防止用户输入 <script>alert('xss')</script> 这样的恶意脚本被执行,有效防止 XSS (跨站脚本) 攻击。
  2. 正则表达式 /.../is:
    • 是定界符。
    • i: 表示 不区分大小写 (case-insensitive)。[b][B] 都能被匹配。
    • s: 表示 单行模式 (single-line),它使得 (点号) 也能匹配换行符,这对于处理 [b]多行文本[/b] 这样的情况非常重要。
  3. *`/(.?)[/tag]/is`**:
    • \[\]: 因为 [] 在正则表达式中有特殊含义,所以需要用 \ 转义。
    • 这是一个非贪婪匹配的捕获组。 匹配任意字符, 表示匹配 0 次或多次, 使其变为非贪婪模式,即匹配到第一个 [/tag] 就停止,而不是最后一个,这对于处理嵌套标签(虽然 UBB 通常不鼓励嵌套)或多重标签非常关键。
    • $1: 这是一个反向引用,代表第一个捕获组 匹配到的内容。
  4. preg_replace(): 它接受一个模式数组和一个对应的替换字符串数组,然后一次性执行所有替换,效率很高。
  5. nl2br(): 这是一个很方便的 PHP 函数,它会把字符串中的换行符 \n 转换成 HTML 的 <br> 标签,让文本段落更清晰。

第三步:安全性与优化(非常重要!)

上面的基本实现已经可以工作,但在生产环境中,你还必须考虑以下几点:

严格的安全过滤(防XSS)

我们已经在解析前使用了 htmlspecialchars(),这是第一道防线,但为了更彻底的安全,特别是当内容要存储到数据库时,应该在写入数据库前也进行一次转义。

// 假设你使用 MySQLi
$ubb_content_from_post = $_POST['ubb_content'];
$escaped_content = mysqli_real_escape_string($your_db_connection, $ubb_content_from_post);
// 然后将 $escaped_content 存入数据库

处理嵌套标签

标准的 UBB 解析器通常不支持嵌套,[b][i]粗斜体[/i][/b],上面的正则表达式会错误地处理它。

解决方案:递归解析

网页如何实现 php ubb标签
(图片来源网络,侵删)

可以创建一个递归函数来处理嵌套。

// 在 ubb_parser.php 中修改 parse_ubb 函数
function parse_ubb_recursive($text) {
    // ... (定义 $ubb_patterns 数组,和之前一样) ...
    $count = 0;
    $new_text = preg_replace(array_keys($ubb_patterns), array_values($ubb_patterns), $text, -1, $count);
    // 如果本次替换发生了,说明可能还有嵌套的标签,需要再次解析
    if ($count > 0) {
        return parse_ubb_recursive($new_text);
    }
    return $new_text;
}
// 主解析函数
function parse_ubb($ubb_text) {
    $safe_text = htmlspecialchars($ubb_text, ENT_QUOTES, 'UTF-8');
    $html_text = parse_ubb_recursive($safe_text);
    $html_text = str_replace("[br]", "<br>", $html_text);
    $html_text = nl2br($html_text);
    return $html_text;
}

这个递归函数会一直解析,直到文本中不再有任何 UBB 标签被替换为止,从而正确处理嵌套。

性能考虑

非常长,或者 UBB 标签非常复杂,大量的正则表达式替换可能会影响性能。

  • 缓存: 如果内容不经常变化(比如帖子发布后),可以将解析后的 HTML 缓存起来(例如存入数据库的一个新字段 html_content,或者使用 Redis、Memcached 等内存缓存),这样下次直接读取缓存即可,无需重复解析。
  • 简化正则: 尽量使用高效、简单的正则表达式,避免使用过于复杂的回溯模式。

总结与最终代码结构

  1. index.php: 负责展示表单和预览结果。
  2. ubb_parser.php: 包含 parse_ubb() 核心函数,负责所有转换逻辑。

index.php (最终版)

<?php
require_once 'ubb_parser.php';
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">PHP UBB 标签演示</title>
    <style>
        /* ... (CSS 保持不变) ... */
    </style>
</head>
<body>
    <h1>PHP UBB 标签演示</h1>
    <p>请输入包含 UBB 标签的文本,<code>[b]粗体[/b]</code>, <code>[i]斜体[/i]</code>, <code>[url=http://www.php.net]PHP官网[/url]</code></p>
    <form action="index.php" method="post"> <!-- 改为提交给自己 -->
        <textarea name="ubb_content" placeholder="在这里输入你的内容..."><?php echo isset($_POST['ubb_content']) ? htmlspecialchars($_POST['ubb_content'], ENT_QUOTES, 'UTF-8') : ''; ?></textarea>
        <br><br>
        <input type="submit" value="提交并预览">
    </form>
    <?php if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['ubb_content']) && !empty(trim($_POST['ubb_content']))): ?>
        <div class="ubb-preview">
            <h3>预览效果:</h3>
            <div>
                <?php
                // 直接调用解析函数并输出
                echo parse_ubb($_POST['ubb_content']);
                ?>
            </div>
        </div>
    <?php endif; ?>
</body>
</html>

ubb_parser.php (最终版,包含递归)

<?php
/**
 * 将 UBB 标签转换为 HTML 标签 (递归版本)
 * @param string $ubb_text 包含 UBB 标签的文本
 * @return string 转换后的 HTML 文本
 */
function parse_ubb($ubb_text) {
    // 1. 安全处理:防止 XSS 攻击
    $safe_text = htmlspecialchars($ubb_text, ENT_QUOTES, 'UTF-8');
    // 2. 定义 UBB 标签的正则表达式替换规则
    $ubb_patterns = [
        '/\[b\](.*?)\[\/b\]/is' => '<strong>$1</strong>', // <strong> 是语义化更好的选择
        '/\[i\](.*?)\[\/i\]/is' => '<em>$1</em>',       // <em> 是语义化更好的选择
        '/\[u\](.*?)\[\/u\]/is' => '<u>$1</u>',
        '/\[size=(\d+)px\](.*?)\[\/size\]/is' => '<span style="font-size: $1px;">$2</span>',
        '/\[color=([a-zA-Z0-9#]+)\](.*?)\[\/color\]/is' => '<span style="color: $1;">$2</span>',
        '/\[align=(left|center|right|justify)\](.*?)\[\/align\]/is' => '<div style="text-align: $1;">$2</div>',
        '/\[list\](.*?)\[\/list\]/is' => '<ul>$1</ul>',
        '/\[\*\](.*?)(?=\[\*\]|\[\/list\]|\z)/is' => '<li>$1</li>',
        '/\[url\](.*?)\[\/url\]/is' => '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>',
        '/\[url=(.*?)\](.*?)\[\/url\]/is' => '<a href="$1" target="_blank" rel="noopener noreferrer">$2</a>',
        '/\[img\](.*?)\[\/img\]/is' => '<img src="$1" alt="用户图片" style="max-width: 100%; height: auto;" loading="lazy">',
        '/\[email\](.*?)\[\/email\]/is' => '<a href="mailto:$1">$1</a>',
        '/\[quote\](.*?)\[\/quote\]/is' => '<blockquote style="border-left: 3px solid #ccc; padding-left: 15px; margin-left: 0;">$1</blockquote>',
        '/\[code\](.*?)\[\/code\]/is' => '<pre style="background-color: #f4f4f4; padding: 10px; border-radius: 4px; overflow-x: auto;"><code>$1</code></pre>',
    ];
    // 3. 执行递归替换
    $html_text = parse_ubb_recursive($safe_text, $ubb_patterns);
    // 4. 处理换行
    $html_text = str_replace("[br]", "<br>", $html_text);
    // 只有在需要时才使用 nl2br,因为它会把每个 \n 都变成 <br>
    // 如果你的编辑器是 WYSIWYG 类型,可能不需要这一步
    if (strpos($html_text, "\n") !== false) {
        $html_text = nl2br($html_text);
    }
    return $html_text;
}
/**
 * 递归解析 UBB 标签的辅助函数
 * @param string $text
 * @param array $patterns
 * @return string
 */
function parse_ubb_recursive($text, $patterns) {
    $count = 0;
    $new_text = preg_replace(array_keys($patterns), array_values($patterns), $text, -1, $count);
    if ($count > 0) {
        return parse_ubb_recursive($new_text, $patterns);
    }
    return $new_text;
}
?>

通过以上步骤,你就拥有了一个功能相对完善、安全性较高的 PHP UBB 标签解析器,你可以根据需要,在 $ubb_patterns 数组中轻松地添加或修改更多的标签规则。