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

PHP ASCII/Unicode 轉換類深度解析:實體編碼與 UTF-8 解碼實作

     編輯:PHP 相關知識

在處理 legacy 系統資料、爬蟲內容清洗、或修復編碼錯誤的資料時,常需在 數值字符實體 (NCR, `〹`)、UTF-8 位元組序列與字元代碼點之間轉換。本文深度剖析一個自訂的 ascii 類別,實現 decode() (實體 → UTF-8) 與 encode() (UTF-8 → 實體) 兩大核心功能,並補充現代 PHP 內建函數的最佳實踐。

類別原始碼分析

<?php
class AsciiConverter
{
    /**
     * 解碼:數值字符實體字串 → UTF-8 字串
     * 輸入範例: "中文" → 輸出: "中文"
     *
     * @param string $str 包含 &#數字; 格式的字串
     * @return string UTF-8 字串
     */
    public function decode(string $str): string
    {
        // 提取所有 &#數字; 實體
        preg_match_all('/&#(\d{2,5});/', $str, $matches);
        $entities = $matches[1];

        $utf = '';
        foreach ($entities as $dec) {
            $codepoint = (int)$dec;

            if ($codepoint < 0x80) {                    // U+0000 ~ U+007F (ASCII)
                $utf .= chr($codepoint);
            } elseif ($codepoint < 0x800) {             // U+0080 ~ U+07FF (2 bytes)
                $utf .= chr(0xC0 | ($codepoint >> 6));
                $utf .= chr(0x80 | ($codepoint & 0x3F));
            } elseif ($codepoint < 0x10000) {           // U+0800 ~ U+FFFF (3 bytes, BMP)
                $utf .= chr(0xE0 | ($codepoint >> 12));
                $utf .= chr(0x80 | (($codepoint >> 6) & 0x3F));
                $utf .= chr(0x80 | ($codepoint & 0x3F));
            } elseif ($codepoint < 0x110000) {          // U+10000 ~ U+10FFFF (4 bytes, 輔助平面)
                $utf .= chr(0xF0 | ($codepoint >> 18));
                $utf .= chr(0x80 | (($codepoint >> 12) & 0x3F));
                $utf .= chr(0x80 | (($codepoint >> 6) & 0x3F));
                $utf .= chr(0x80 | ($codepoint & 0x3F));
            }
            // 忽略超出 Unicode 範圍的代碼點
        }
        return $utf;
    }

    /**
     * 編碼:UTF-8 字串 → 數值字符實體字串
     * 輸入: "中文" → 輸出: "中文"
     *
     * @param string $str UTF-8 編碼字串
     * @return string 實體編碼字串
     */
    public function encode(string $str): string
    {
        $len = strlen($str);
        $i = 0;
        $result = '';

        while ($i < $len) {
            $byte1 = ord($str[$i]);
            $codepoint = 0;
            $bytes = 1;

            if ($byte1 < 0x80) {                        // 1-byte: 0xxxxxxx
                $codepoint = $byte1;
                $bytes = 1;
            } elseif (($byte1 & 0xE0) === 0xC0) {       // 2-byte: 110xxxxx 10xxxxxx
                $byte2 = ord($str[$i + 1] ?? '');
                $codepoint = (($byte1 & 0x1F) << 6) | ($byte2 & 0x3F);
                $bytes = 2;
            } elseif (($byte1 & 0xF0) === 0xE0) {       // 3-byte: 1110xxxx 10xxxxxx 10xxxxxx
                $byte2 = ord($str[$i + 1] ?? '');
                $byte3 = ord($str[$i + 2] ?? '');
                $codepoint = (($byte1 & 0x0F) << 12) | (($byte2 & 0x3F) << 6) | ($byte3 & 0x3F);
                $bytes = 3;
            } elseif (($byte1 & 0xF8) === 0xF0) {       // 4-byte: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
                $byte2 = ord($str[$i + 1] ?? '');
                $byte3 = ord($str[$i + 2] ?? '');
                $byte4 = ord($str[$i + 3] ?? '');
                $codepoint = (($byte1 & 0x07) << 18) | (($byte2 & 0x3F) << 12) | (($byte3 & 0x3F) << 6) | ($byte4 & 0x3F);
                $bytes = 4;
            } else {
                // 無效 UTF-8 序列,跳過單一位元組
                $codepoint = 0xFFFD; // Replacement Character
                $bytes = 1;
            }

            $result .= "&#{$codepoint};";
            $i += $bytes;
        }

        return $result;
    }
}

核心邏輯詳解

UTF-8 編碼規則 (RFC 3629)

代碼點範圍 位元組數 位元組 1 位元組 2 位元組 3 位元組 4
U+0000 ~ U+007F10xxxxxxx---
U+0080 ~ U+07FF2110xxxxx10xxxxxx--
U+0800 ~ U+FFFF31110xxxx10xxxxxx10xxxxxx-
U+10000 ~ U+10FFFF411110xxx10xxxxxx10xxxxxx10xxxxxx

decode() 依據代碼點大小決定輸出位元組數與位模式;encode() 透過位元組首位標記 (0x80, 0xC0, 0xE0, 0xF0) 識別序列長度,再還原代碼點。

使用範例

<?php
$converter = new AsciiConverter();

// 編碼測試
$original = "Hello 中文 🚀"; // U+1F680 = 128640 (4-byte)
$encoded = $converter->encode($original);
echo "Encoded: {$encoded}\n";
// 輸出: Encoded: Hello 中文 🚀

// 解碼測試
$decoded = $converter->decode($encoded);
echo "Decoded: {$decoded}\n";
// 輸出: Decoded: Hello 中文 🚀

// 實際場景:修復資料庫中被錯誤儲存為實體的欄位
$dirtyData = "语定义标"; // "文字編碼"
$clean = $converter->decode($dirtyData);
echo "Clean: {$clean}\n";

現代 PHP 內建函數替代方案 (推薦生產環境使用)

1. HTML 實體編碼/解碼

<?php
$str = " 中文";

// 編碼為實體 (預設雙引號、單引號、<、>、&)
$encoded = htmlspecialchars($str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
// 輸出: <script>alert('XSS')</script> 中文

// 解碼實體
$decoded = html_entity_decode($encoded, ENT_QUOTES, 'UTF-8');
// 輸出:  中文

// 取得所有實體對照表
$table = get_html_translation_table(HTML_ENTITIES, ENT_QUOTES, 'UTF-8');

2. Unicode 代碼點轉換 (PHP 7.0+)

<?php
// 字串 → 代碼點陣列
$codepoints = mb_convert_encoding($str, 'UCS-4BE', 'UTF-8');
// 或使用 IntlChar (需 intl 擴展)
$codepoints = [];
for ($i = 0, $len = mb_strlen($str); $i < $len; $i++) {
    $char = mb_substr($str, $i, 1);
    $codepoints[] = IntlChar::ord($char);
}

// 代碼點 → 字串
$str = '';
foreach ($codepoints as $cp) {
    $str .= IntlChar::chr($cp);
}
// 或
$str = mb_convert_encoding(pack('N*', ...$codepoints), 'UTF-8', 'UCS-4BE');

3. 直接處理 NCR (數值字符實體)

<?php
// 解碼 〹 格式 (PHP 內建無直接函數,但可用正則 + IntlChar)
$decoded = preg_replace_callback('/&#(\d+);/', function($m) {
    $cp = (int)$m[1];
    return ($cp < 0x110000) ? IntlChar::chr($cp) : '';
}, $entityString);

// 編碼為 〹 格式
$encoded = preg_replace_callback('/./u', function($m) {
    return '&#' . IntlChar::ord($m[0]) . ';';
}, $utf8String);

效能比較與選型建議

方案 適用場景 效能 依賴
自訂 AsciiConverter 類別純 PHP 環境、無 intl 擴展、需精確控制 NCR 格式中等 (純 PHP 迴圈)無
htmlspecialchars/html_entity_decodeHTML 輸出防 XSS、表單資料清洗極高 (C 實現)核心內建
IntlChar + mbstringUnicode 正規化、代碼點運算、多語言處理高 (C 實現)intl 擴展
preg_replace_callback + IntlChar批次 NCR 編解碼、資料遷移腳本中高intl 擴展

常見陷阱

  • 雙重編碼:已為實體的字串再次 encode() 會產生 &#12345;,使用前檢查 strpos($str, '&#') === false。
  • 無效代碼點:> U+10FFFF 或代理區 (U+D800-U+DFFF) 應拋棄或替換為 U+FFFD。
  • BOM 處理:UTF-8 BOM (EF BB BF) 若被誤判為字元會產生錯誤實體,預處理移除 ltrim($str, "\xEF\xBB\xBF")。
  • 效能陷阱:大量字串 (MB 級) 請勿使用純 PHP 迴圈,改用 mb_convert_encoding 或分批處理。

結語

理解 ASCII/Unicode/UTF-8/NCR 之間的轉換關係,是處理國際化、資料清洗、編碼修復的核心技能。自訂類別適合學習原理與無擴展環境,生產環境請優先使用 htmlspecialchars (HTML 語境)、IntlChar (Unicode 操作)、mbstring (多位元組字串),獲得更好效能、正確性與安全性。

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