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

解決 Smarty truncate 中英文混排截取亂碼與長度不一致問題

     編輯:PHP 相關知識

解決 Smarty truncate 中英文混排截取亂碼與長度不一致問題

Smarty 內建 truncate 修飾符按位元組截取,對純英文無影響,但中文 UTF-8 每字 3 位元組、GBK 每字 2 位元組,直接截取極易產生亂碼;且中英文視覺寬度差異大(一中文字≈兩英文字),定位元組數截取導致顯示長度參差不齊。本文提供完整解決方案:smartTruncate 自訂修飾符,支援 UTF-8/GBK 自動偵測,按視覺寬度(中文=1.0、英文=0.5)精準截取。

核心原理

  1. 編碼偵測:以正則判斷字串是否為合法 UTF-8,非 UTF-8 視為雙位元組編碼(GBK/Big5)。
  2. 視覺長度計算:逐字元掃描,ASCII(≤127)計 0.5,非 ASCII 依編碼計 1.0(UTF-8 中文 3 位元組、GBK 中文 2 位元組)。
  3. 精準截取:依視覺長度截取,避免截半個中文字;支援尾碼、單字邊界、中間截取等模式。

完整插件代碼:modifier.smartTruncate.php

<?php
// 放入 smarty/plugins/modifier.smartTruncate.php

function smartDetectUTF8($string) {
    static $result = array();
    $key = md5($string);
    if (!isset($result[$key])) {
        $utf8_regex = '/^(?:[\x09\x0A\x0D\x20-\x7E]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})+$/xs';
        $result[$key] = (bool)preg_match($utf8_regex, $string);
    }
    return $result[$key];
}

function smartStrlen($string) {
    $len = 0;
    $bytes_per_char = smartDetectUTF8($string) ? 3 : 2;
    for ($i = 0; $i < strlen($string); $i += $bytes) {
        $bytes = (ord($string[$i]) > 127) ? $bytes_per_char : 1;
        $len += ($bytes > 1) ? 1.0 : 0.5;
    }
    return $len;
}

function smartSubstr($string, $start, $length = null) {
    $result = '';
    $bytes_per_char = smartDetectUTF8($string) ? 3 : 2;
    // 處理負數 start
    if ($start < 0) {
        $start = max(smartStrlen($string) + $start, 0);
    }
    // 定位起始位置
    for ($i = 0; $i < strlen($string); $i += $bytes) {
        if ($start <= 0) break;
        $bytes = (ord($string[$i]) > 127) ? $bytes_per_char : 1;
        $start -= ($bytes > 1) ? 1.0 : 0.5;
    }
    // 截取
    if (is_null($length)) {
        $result = substr($string, $i);
    } else {
        for ($j = $i; $j < strlen($string); $j += $bytes) {
            if ($length <= 0) break;
            $bytes = (ord($string[$j]) > 127) ? $bytes_per_char : 1;
            if ($bytes > 1) {
                if ($length < 1.0) break;
                $result .= substr($string, $j, $bytes);
                $length -= 1.0;
            } else {
                $result .= substr($string, $j, 1);
                $length -= 0.5;
            }
        }
    }
    return $result;
}

function smarty_modifier_smartTruncate($string, $length = 80, $etc = '...', $break_words = false, $middle = false) {
    if ($length == 0) return '';
    if (smartStrlen($string) > $length) {
        $length -= smartStrlen($etc);
        if (!$break_words && !$middle) {
            $string = preg_replace('/\s+?(\S+)?$/', '', smartSubstr($string, 0, $length + 1));
        }
        if (!$middle) {
            return smartSubstr($string, 0, $length) . $etc;
        } else {
            return smartSubstr($string, 0, $length/2) . $etc . smartSubstr($string, -$length/2);
        }
    }
    return $string;
}

使用範例

{* 模板中直接使用 *}
{$content|smartTruncate:5:".."}

{* 假設 $content = "A中B華C人D民E共F和G國H" *}
{* 輸出:A中B華C.. *}
{* 說明:中文視覺長度 1.0,英文 0.5,扣除尾碼 ".." (1.0) 後,實際截取視覺長度 4.0 *}

優勢總結

  • ✅ 同時相容 UTF-8、GBK、Big5 等編碼,無需修改代碼。
  • ✅ 按視覺寬度計算,中英文混排顯示長度整齊一致。
  • ✅ 完整支援原生 truncate 所有參數(尾碼、單字邊界、中間截取)。
  • ✅ 純 PHP 實現,無擴展依賴,虛擬主機也能用。

快速替代方案(不想寫插件)

在 PHP 邏輯層預先處理,再傳給模板:

function mb_truncate($str, $width, $etc = '...') {
    $width -= mb_strwidth($etc);
    if (mb_strwidth($str) > $width) {
        return mb_strimwidth($str, 0, $width, $etc, 'UTF-8');
    }
    return $str;
}
$smarty->assign('short_title', mb_truncate($long_title, 30));

或使用 mb_substr 搭配 mb_strwidth 自行封裝。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved