在 PHP 開發中,常需要截取包含 HTML 標籤的字串來生成摘要或預覽。若直接使用 strip_tags 移除標籤,雖能獲得純文字,但會失去排版結構,閱讀體驗大打折扣。更棘手的是,若截斷點落在未閉合的標籤中間,會導致整頁版面錯亂。本文介紹兩種實用的解決方案,能在指定長度截取 HTML 內容的同時,自動補全未閉合的標籤,確保輸出的 HTML 結構完整。
此方法將 HTML 字串拆解為標籤與文字節點,利用堆疊追蹤開啟的標籤,並在截斷後依序補全閉合標籤。核心邏輯包含三個輔助函數:
subHtml($html, $length):主函數,負責解析 HTML、計算文字長度、管理標籤堆疊mstrlen($str, $charset):精確計算 UTF-8 字串長度(含中英文)msubstr($string, $start, $length, $dot, $charset):多位元組安全的子字串截取function subHtml($html, $length) {
$result = '';
$tagStack = [];
$len = 0;
// 以正則拆分標籤與文字,保留分隔符
$contents = preg_split("~(<[^>]+>)~si", $html, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
foreach ($contents as $tag) {
if (trim($tag) === '') continue;
// 自閉合標籤
、
等直接輸出
if (preg_match("~<([a-z0-9]+)[^/>]*/>~si", $tag)) {
$result .= $tag;
}
// 閉合標籤 另一種思路是逐字元掃描,遇到 < 進入標籤解析模式,記錄開啟與閉合標籤名稱,最後根據未配對的開啟標籤生成閉合標籤。此法對巢狀結構處理較直觀,但需注意 HTML 實體編碼與屬性值中夾帶的 > 字元。
function phpos_chsubstr_ahtml($str, $num, $more = false) {
$leng = strlen($str);
if ($num >= $leng) return $str;
$word = 0; $i = 0;
$stag = []; $etag = [];
$sp = 0; $ep = 0;
while ($word != $num) {
if (ord($str[$i]) > 128) { // 中文字元(UTF-8 3 位元組)
$i += 3; $word++;
}
elseif ($str[$i] === '<') { // 進入標籤解析
// 解析標籤名稱,區分開啟/閉合標籤,存入 $stag/$etag
// ...(略,完整邏輯見原始碼)
$i++;
continue;
}
else { $word++; $i++; }
}
// 配對消除已閉合標籤,保留未閉合的開啟標籤
foreach ($etag as $val) {
$key = array_search($val, $stag);
if ($key !== false) unset($stag[$key]);
}
// 過濾無需閉合標籤
foreach ($stag as $key => $val) {
if (in_array($val, ['br', 'img'])) unset($stag[$key]);
}
// 反序生成閉合標籤
$stag = array_reverse($stag);
$ends = '</' . implode('></', $stag) . '>';
$re = substr($str, 0, $i) . $ends;
if ($more) $re .= '...';
return $re;
}
此法針對常見區塊標籤(div、p、table 等)進行正則匹配,配合堆疊管理巢狀關係,適合 CMS 系統的文章摘要生成:
function text_zhaiyao($text, $length) {
mb_regex_encoding("UTF-8");
if (mb_strlen($text) <= $length) return $text;
$Foremost = mb_substr($text, 0, $length);
$re = "<(/?) (P|DIV|H[1-6]|TABLE|TR|TD|TH|A|UL|OL|LI|SPAN|IMG|BR|HR)[^>]*(>?)";
$Single = "/BR|HR|IMG|INPUT/i"; // 無需閉合標籤
$Stack = []; $posStack = [];
mb_ereg_search_init($Foremost, $re, 'i');
while ($pos = mb_ereg_search_pos()) {
$match = mb_ereg_search_getregs();
// $match[1]:是否為閉合標籤 (/)
// $match[2]:標籤名稱
// $match[3]:右括號 (>)
if ($match[1] === "") { // 開啟標籤
$Elem = $match[2];
if (mb_eregi($Single, $Elem) && $match[3] !== "") continue;
array_push($Stack, $Elem);
array_push($posStack, $pos[0]);
} else { // 閉合標籤
// 匹配堆疊頂端則彈出
}
}
// 依堆疊剩餘標籤補全閉合標籤...
}
DOMDocument 解析樹狀結構(雖較慢但更健壯)。<script>、<style> 內容通常不應出現在摘要中,建議預先移除。掌握這些技巧,能讓你在開發文章列表、搜尋結果預覽、RSS 輸出等場景時,既保留豐富排版,又確保頁面結構穩定。