PHP 內建 substr() 僅按位元組截取,對中文等多位元組編碼會造成亂碼。本文提供一個健全的 cutstr() 函式,支援 UTF-8 與 GBK/Big5,正確按「字元寬度」截斷並保留 HTML 實體。
<?php
function cutstr($string, $length, $dot = ' ...') {
if (strlen($string) <= $length) {
return $string;
}
// 保護 HTML 實體不被截斷
$pre = chr(1);
$end = chr(1);
$string = str_replace(
['&', '"', '<', '>'],
[$pre . '&' . $end, $pre . '"' . $end, $pre . '<' . $end, $pre . '>' . $end],
$string
);
$strcut = '';
if (strtolower(CHARSET) == 'utf-8') {
// UTF-8 多位元組邏輯
$n = $tn = $noc = 0;
while ($n < strlen($string)) {
$t = ord($string[$n]);
if ($t == 9 || $t == 10 || (32 <= $t && $t <= 126)) {
// ASCII 字元:1 位元組,寬度 1
$tn = 1; $n++; $noc++;
} elseif (194 <= $t && $t <= 223) {
// 2 位元組字元
$tn = 2; $n += 2; $noc += 2;
} elseif (224 <= $t && $t <= 239) {
// 3 位元組字元(中日韓常見)
$tn = 3; $n += 3; $noc += 2;
} elseif (240 <= $t && $t <= 247) {
$tn = 4; $n += 4; $noc += 2;
} elseif (248 <= $t && $t <= 251) {
$tn = 5; $n += 5; $noc += 2;
} elseif ($t == 252 || $t == 253) {
$tn = 6; $n += 6; $noc += 2;
} else {
$n++;
}
if ($noc >= $length) break;
}
if ($noc > $length) $n -= $tn;
$strcut = substr($string, 0, $n);
} else {
// GBK/Big5:雙位元組判斷
for ($i = 0; $i < $length; $i++) {
$strcut .= ord($string[$i]) > 127 ? $string[$i] . $string[++$i] : $string[$i];
}
}
// 還原 HTML 實體
$strcut = str_replace(
[$pre . '&' . $end, $pre . '"' . $end, $pre . '<' . $end, $pre . '>' . $end],
['&', '"', '<', '>'],
$strcut
);
// 移除截斷處可能殘留的保護標記
$pos = strrpos($strcut, chr(1));
if ($pos !== false) $strcut = substr($strcut, 0, $pos);
return $strcut . $dot;
}
?>
// 定義編碼常數(視專案實際編碼設定)
define('CHARSET', 'utf-8');
$text = '這是一段測試中文內容,包含 English & 數字 123。';
echo cutstr($text, 20);
// 輸出:這是一段測試中文內容,包含 English ...
若專案使用 PHP 7.2+,建議直接使用 mb_substr() 搭配 mb_strlen(),原生支援多位元組編碼,無需自行實作:
function mb_cutstr($string, $length, $dot = '...', $encoding = 'UTF-8') {
if (mb_strlen($string, $encoding) <= $length) return $string;
return mb_substr($string, 0, $length, $encoding) . $dot;
}
若內容含 HTML 標籤,建議搭配 strip_tags() 或使用 mb_substr() 後再配合正則補全標籤,或引用成熟套件如 htmlpurifier。
CHARSET 區分 UTF-8 與雙位元組編碼邏輯。chr(1) 標記實體邊界,避免 & 被截成 &am。