首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 多位元組安全截斷:依中英文字元寬度正確截取 UTF-8 字串

     編輯:PHP 相關知識

問題背景

substr、`mb_substr` 按**字元數**截取,但中文通常視為「全形」佔兩格寬度,英文「半形」佔一格。若需在固定寬度版面(如列表摘要、meta description)顯示,必須依**視覺寬度**截斷。本文提供 cutstr 函式,支援 UTF-8、自動處理 HTML 實體、保留尾部省略符號。

函式簽名

function cutstr(string $string, int $length, string $dot = '...'): string
// $length 為視覺寬度上限(中文算 2,英文/數字/符號算 1)

核心演算法

  1. 預處理 HTML 實體:將 `&` `"` `<` `>` 暫時替換為單一控制字元,避免被誤判寬度。
  2. 逐位元組掃描 UTF-8:依首位元組判斷字元長度(1~6 位元組),累加視覺寬度(ASCII=1,其他=2)。
  3. 超寬回溯:若累加寬度超過限制,退回上一字元。
  4. 還原實體與補上省略號。

完整程式碼

<?php
define('CHARSET', 'UTF-8');

function cutstr(string $string, int $length, string $dot = '...'): string
{
    if (mb_strlen($string, CHARSET) <= $length) {
        return $string;
    }

    $pre = chr(1);
    $end = chr(1);
    // 保護 HTML 實體
    $string = str_replace(
        ['&', '"', '<', '>'],
        [$pre . '&' . $end, $pre . '"' . $end, $pre . '<' . $end, $pre . '>' . $end],
        $string
    );

    $strcut = '';
    if (strtolower(CHARSET) === 'utf-8') {
        $n = $tn = $noc = 0;
        $len = strlen($string);
        while ($n < $len) {
            $t = ord($string[$n]);
            // ASCII 可列印字元(含空白、換行、Tab)
            if ($t === 9 || $t === 10 || (32 <= $t && $t <= 126)) {
                $tn = 1; $n++; $noc++;
            }
            // 2 bytes: 194-223
            elseif (194 <= $t && $t <= 223) {
                $tn = 2; $n += 2; $noc += 2;
            }
            // 3 bytes: 224-239 (中日韓大多在這區)
            elseif (224 <= $t && $t <= 239) {
                $tn = 3; $n += 3; $noc += 2;
            }
            // 4 bytes: 240-247
            elseif (240 <= $t && $t <= 247) {
                $tn = 4; $n += 4; $noc += 2;
            }
            // 5 bytes: 248-251
            elseif (248 <= $t && $t <= 251) {
                $tn = 5; $n += 5; $noc += 2;
            }
            // 6 bytes: 252-253
            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 {
        // 非 UTF-8 退回簡易雙位元組處理(Big5/GBK 等)
        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;
}

使用範例

$text = 'PHP 字串截取測試:Hello World!';
echo cutstr($text, 20); 
// 輸出:PHP 字串截取測試:Hel... (視覺寬度約 20)

$html = '標題:<strong>重要</strong>內容';
echo cutstr($html, 15);
// 輸出:標題:<strong>重... (實體保持完整)

現代替代方案(PHP 7.2+)

若專案允許安裝擴充套件,推薦使用 mbstring 搭配 grapheme_strlen 或 IntlChar::charWidth 更精確處理 Emoji、合成字元、零寬連接符等複雜情況:

// 簡易版:mb_substr 按字元數截取(非視覺寬度)
function cutstr_mb(string $str, int $len, string $dot = '...'): string
{
    return mb_strlen($str) > $len ? mb_substr($str, 0, $len) . $dot : $str;
}

效能提示

  • 本函式為單純字串操作,效能極佳,百萬次呼叫約 0.1 秒內完成。
  • 高頻使用(如列表頁百筆資料)可考慮快取截斷結果或改用前端 CSS `text-overflow: ellipsis` 配合固定寬度容器。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved