SheepDev

編碼與字元集

看起來相同的文字,為何不相等?

使用工具

外觀相同的文字,碼位序列可能不同。搜尋或去重與肉眼判斷不一致時,查看內部往往比反覆重輸更快。結合 Unicode 資訊與正規化工具查找實際差異。

不要把外觀相似的內容全合併。姓名、商品名、公式和識別碼可能需要保留區別。正規化不是糾錯,也不是萬能危險字元過濾器,而是制定等價規則的依據。

用兩種方式組成 é

逐行輸入字元資訊工具
é
é

第一行是 U+00E9,第二行是 U+0065 後接 U+0301。通常都顯示為 é,卻分別有一、兩個碼位。分開檢查,避免把換行帶入。換字型只改外觀,不改序列。

第二行正規化後,NFC 合成為 é,NFD 保留 e 加音符的分解形式。兩者都提供確定的比較形式。NFC 結果再次 NFC 應不變。對照前後碼位與 UTF-8 位元組數可說明變化。

全形 A 與圈號 ① 需要另一種判斷

相容字元實驗
A①

NFC 保持原樣,NFKC 變成 A1。相容正規化可幫助搜尋比對,卻會遺失圈號和全形區別。可能需要正規化搜尋鍵,同時保留顯示原文。

NFKD 也做相容轉換,但使用分解形式。不要從四種結果中選最短的當正確答案。系統需統一形式,並接受它消除的區別。以後改策略還可能影響既有 ID 和搜尋行為。

正規化不一定會刪掉不可見字元

第二行 a 與 b 間有 U+200B
ab
a​b

檢查可見 a、b 之間有 U+200B,雖無可見寬度,仍是碼位。NFC、NFKC 都不會把此例變成 ab。正規化不是刪除一切不可見字元的清理,允許字元規則應另行考慮。

分類用於提示空白、組合符號與控制格式字元,不是完整名稱庫或安全判決。零寬字元可能有語言與排版用途,不能一概視為惡意或多餘。除問題例,還應測試必須正確支援的文字。

像 a 的字,不一定屬於同一文字系統

拉丁 a 與西里爾 а
a
а

前者 U+0061,後者 U+0430,字型下可能很像,卻屬於不同文字系統,NFKC 不會合併。已正規化不等於沒有可冒充的形似字,URL 與帳號安全仍需另查。

反過來用自製表替換所有形似字,可能破壞其他語言的合法名字。搜尋候選的寬鬆比對,與登入權限的身分比對不必用同一規則。這裡檢查差異,最終策略按服務用途決定。

變化位置不是編輯歷史

變化位置按碼位陣列索引比較。一個字元分解後會使後續位置偏移,可能標出多處差異。這不是編輯次數或錯字數,應看完整前後序列和上下文。

UTF-16 位置與碼位位置也可能不同,尤其前面有表情時。JavaScript 位移與碼位計數是不同單位。核對報錯位置要統一單位以及從 0 還是 1 開始。

先定位原因,再決定轉換策略

保留原文,確定不同碼位,再區分 NFC 能統一、需要相容轉換、或正規化無效。然後決定哪些可轉換、哪些必須保留原樣,不要把 NFKC 當萬能修復。

記錄可見文字與碼位序列,把組合與分解 é、全形 A、零寬字和跨文字系統 a 留作測試,同時驗證該相同與該不同的情況。可靠比對不僅修好一例,還要避免誤合併。

搜尋中視為相同,不等於改寫原資料

商品搜尋可只正規化比較用字串,讓全形A找到 ASCII A,而不改正式商品名。原表記與搜尋表記分開可兼顧可發現性和資料保留。比較 NFC、NFKC 是決定用途允許哪些等價,不是改得少或多就更好。

對已有規則的使用者 ID、簽名字串或密碼後來加正規化,可能改變相容性。外觀相同不是修改儲存資料的依據。先查碼位,再看各正規化形式改變哪裡,最後核對目標系統比較規則。找到原因與允許遷移資料是不同決定。

將合成與分解的 é、全形與 ASCII A、零寬字元插入分別做小例子,混合原因會難以判斷哪步有效。除外觀外還看碼位和 UTF-8 大小。明確用途需要的有限等價範圍,比試圖讓所有相似字元相同更能避免誤比對。

參考資料

使用工具

指南 →