前言
最近同事開會時,提到一個 unicode 的問題,
而 unicode 的問題,大多是 Big5 編碼時才會遇到,例如「堃」這個字。
當使用 utf8 編碼後,常常遇到的問題,大多是「自造字」(EUDC)的問題。
同事給的字是 「善」 這個字,但它卻跟一般的 「善」 不同。
「善」 vs 「善」 差在那裡呢?
研究
如果把 「善」 vs 「善」 貼到 Notepad (Windows 10 繁體中文版)中,會發現,前面的 善 變成了一個”白方框”,如下,
直覺來看,應該是「自造字」,但以 Chrome Browser 來開啟它,卻又可以正常顯示,這就表示它 不是「自造字」(EUDC)。
所以可以從 unicode-table.com 來查詢,
一般 「善」 這個字則是 CJK, escape 出來內容是 %u5584
1 | escape("善"); |
而有問題的 「善」 這個字是 CJK Compatibility Ideograph-2F846 ,
escape 出來卻是 %uD87E%uDC46
1 | escape("善"); |
它在 unicode-table.com 可以找到,所以它是 unicode,
從 %uD87E%uDC46 來看,別人只有一個 code unit,它卻有 2 個。
所以它是 Surrogate pair
Surrogate pair is a representation for a single abstract character that consists of a sequence of code units of two 16-bit code units, where the first value of the pair is a high-surrogate code unit and the second value is a low-surrogate code unit.
Surrogate pair(High-surrogate: 0xD800 ~ 0xDBFF, Low-surrogate: 0xDC00 ~ 0xDFFF) ,
大多用在 emoji (表情符號)及擴充字,這個 「善」 就是 CJK 擴充字。
那回過頭來,Surrogate pair 會有什麼問題呢?
就是透過 for loop 處理字串時,它會變亂碼,
JavaScript 處理
1 | var str = "a善b善c善d"; //length:9 |
那要怎麼辦才能取到正常的字呢?
判斷如果是 High-surrogate 就取下一個 code unit,再判斷是否為 Low-surrogate,是則一次取 2 個 code unit,如下,
1 | function getSymbols(string) { |
如果 ES6 直接使用 Array.from 就行了,
1 | var strAry = Array.from(str); //strAry length:7 |
- 註: Most JavaScript engines use UTF-16 encoding
.NET 處理
如果直接 for each 也是會變亂碼,
1 | var str = "a善b善c善d"; |
所以也是要判斷是否為 Surrogate pair,可利用 Char.IsSurrogatePair + Char.ConvertToUtf32 ,如下,
1 | var str = "a善b善c善d"; |
所以 for-loop 處理字串時,要檢查是否會有 Surrogate Pair 的狀況。
參考資料
What every JavaScript developer should know about Unicode
JavaScript has a Unicode problem
The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)
Javascript String Offsets And Unicode Surrogate Pairs
CJK Compatibility Ideographs Supplement