從資料庫讀取的文字內容常含 \n 換行,直接輸出 HTML 會合併為單行。nl2br() 簡單插入 <br>,但語意不佳;nl2p() 將換行轉為 <p> 段落,支援 CSS 排版。本文詳細對比兩者、提供生產級 nl2p 實作、XSS 防護與 CSS 配合建議。
<?php $text = "第一行\n第二行\n第三行"; echo nl2br($text); /* 輸出 HTML: 第一行<br /> 第二行<br /> 第三行 */ ?>
\n (含 \r\n, \r) **前**插入 <br /> (XHTML) 或 <br> (HTML5)。<script> 會直接輸出 → **XSS 風險**。<?php
function nl2p_simple($text) {
return '<p>' . str_replace("\n", '</p><p>', $text) . '</p>';
}
?>
缺點:連續多個 \n 產生空 <p></p>;單換行無法區分段落與換行。
<?php
/**
* 將純文字轉為語意化 HTML 段落
* @param string $text 輸入文字
* @param bool $lineBreak 單換行是否轉 <br> (true) 或合併段落 (false)
* @param bool $xml <br /> (true) 或 <br> (false)
* @return string
*/
function nl2p(string $text, bool $lineBreak = true, bool $xml = true): string {
// 1. 移除既有 HTML 標籤 (防止雙重包裹與 XSS)
$text = strip_tags($text, '
'); // 可保留少數安全標籤
// 2. 統一換行符為 \n
$text = str_replace(["\r\n", "\r"], "\n", $text);
// 3. 修剪首尾空白
$text = trim($text);
if ($text === '') return '';
if ($lineBreak) {
// 模式 A:雙換行 = 新段落,單換行 =
// 先將連續 2+ \n 替換為段落分隔標記
$text = preg_replace('/\n{2,}/', "\n\n", $text); // 正規化多重空行
// 單換行轉
$text = preg_replace('/([^>])\n([^<])/', '$1<br' . ($xml ? ' /' : '') . '>$2', $text);
// 雙換行包裹段落
$paragraphs = explode("\n\n", $text);
} else {
// 模式 B:所有換行皆為新段落
$paragraphs = preg_split('/\n+/', $text);
}
// 4. 包裹
標籤
$html = '';
foreach ($paragraphs as $p) {
$p = trim($p);
if ($p !== '') $html .= '<p>' . $p . '</p>';
}
return $html;
}
// 測試
$input = "標題行\n\n正文第一段\n單換行\n\n正文第二段";
echo nl2p($input);
/* 輸出:
<p>標題行</p>
<p>正文第一段<br />單換行</p>
<p>正文第二段</p>
*/
?>
strip_tags() 或 htmlspecialchars() (視業務是否允許富文本)。nl2p 內部已 strip_tags;若允許部分標籤 (如 <b><i>),用 HTMLPurifier 白名單過濾。Content-Security-Policy: default-src 'self'; script-src 'self' 纵深防禦。/* 重置瀏覽器預設 margin */
p { margin: 0 0 1em; line-height: 1.6; }
p + p { margin-top: 1em; } /* 段落間距 */
/* 首行縮進 (中文) */
p { text-indent: 2em; }
p:first-child { text-indent: 0; }
/* 代碼區塊保持預格式 */
pre { white-space: pre-wrap; word-break: break-all; }
| 特性 | nl2br() | nl2p() (生產級) |
|---|---|---|
| 語意標籤 | <br> (行內) | <p> (區塊) |
| CSS 控制 | 困難 | 靈活 (margin, indent, line-height) |
| 雙換行處理 | 兩個 <br> | 分離段落 |
| 單換行選項 | 無 | 可選 <br> 或合併 |
| XSS 防護 | 無 (需自行 htmlspecialchars) | 內建 strip_tags + 選項 |
| 適用場景 | 簡單單行輸出、代碼片段 | 文章、評論、郵件內容、CMS |