2014-07-15

偵測檔案使用的編碼是Utf-8或Big5-新版

前一篇文章討論到如何偵測檔案的編碼方式,但由於還是有很小的機會會誤判,心裡總是有疙瘩在,不確定廠商到底會丟怎樣的資料來(廠商的代名詞就是腦X),而且訂單XML內中文字的數量很少,若只有一筆,很可能只有三個中文字而已,於是從utf-8是如何編碼開始研究起。

沒想到規則意外的簡單,因為我不須知道太詳細的細節,只需知道幾個重點:

  1. 只要是0開頭的byte就表示是ascii編碼,也就是0xxxxxxx後面的七碼x相容於傳統ascii編碼
  2. 非ascii編碼,一律以1開頭再接0,並且最少兩個1
  3. 1的數量指出這個字是由幾個byte所組成,如1110xxxxx表示這個字要3個byte
  4. 其後每個子byte都為10開頭
  5. 最多每個字4個byte
知道規則之後,要判斷就很簡單了,底下是判斷的流程和邏輯:
  1. 依照規格,只要有非0、非110、非1110、非11110開頭的byte,就是非utf-8
  2. 110開頭的byte,需檢查是否有下一個byte,或底下一個byte是否為10開頭
  3. 1110開頭的byte,需檢查是否有下兩個byte,或底下兩個byte是否皆為10開頭
  4. 11110開頭的byte,需檢查是否有下三個byte,或底下三個byte是否皆為10開頭
  5. 全部byte跑完之後的指標是否等於檔案長度
底下就是程式碼了:
  1. /// <summary>
  2. /// 檢查是否符合utf-8編碼的核心程式
  3. /// </summary>
  4. /// <param name="bytes">文字原始byte陣列</param>
  5. /// <returns></returns>
  6. private static bool _IsUtf8(byte[] bytes) {
  7. int i = 0;
  8. int size = bytes.Length;
  9.  
  10. //跑每一個byte,需要每個byte都符合utf-8編碼規則才算是utf-8編碼
  11. while (i < size) {
  12. int step = 0;
  13. if ((bytes[i] & 0x80) == 0x00) {
  14. //utf-8的ascii字元,為0開頭,OK跑下一個位元
  15. step = 1;
  16. }
  17. else if ((bytes[i] & 0xE0) == 0xC0) {
  18. /*
  19. 110xxxxx
  20. & 11100000 (0xE0)
  21. ----------
  22. 11000000 (0xC0)
  23. */
  24. //utf-8使用2bytes編碼格式為 110xxxxx 10xxxxxx
  25. //如果是110xxxxx開頭,要判斷下個位元是否是10開頭
  26.  
  27. //如果下一個已經沒有資料了,表示這不是utf-8編碼
  28. if (i + 1 >= size) { return false; }
  29.  
  30. //如果下個位元不是10開頭,也表示這不是utf-8編碼
  31. if ((bytes[i + 1] & 0xC0) != 0x80) { return false; }
  32.  
  33. //繼續跑下2個
  34. step = 2;
  35. }
  36. else if ((bytes[i] & 0xF0) == 0xE0) {
  37. /*
  38. 1110xxxx
  39. & 11110000 (0xF0)
  40. ----------
  41. 11100000 (0xE0)
  42. */
  43. //utf-8使用3bytes編碼格式為 1110xxxx 10xxxxxx 10xxxxxx
  44. //所以如果是1110xxxx開頭,要判斷下兩個byte是否都是10開頭
  45.  
  46. //如果下兩個已經沒有資料了,表示不是utf-8編碼
  47. if (i + 2 >= size) { return false; }
  48.  
  49. //如果下兩個位元不是10開頭,也不是utf-8編碼
  50. if ((bytes[i + 1] & 0xC0) != 0x80) { return false; }
  51. if ((bytes[i + 2] & 0xC0) != 0x80) { return false; }
  52.  
  53. //繼續跑下3個
  54. step = 3;
  55. }
  56. else if ((bytes[i] & 0xF8) == 0xF0) {
  57. /*
  58. 11110xxx
  59. & 11111000 (0xF8)
  60. ----------
  61. 11110000 (0xF0)
  62. */
  63. //utf-8使用4bytes編碼格式為 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
  64. //所以如果是11110xxx開頭,要判斷下3個byte是否都是10開頭
  65.  
  66. //如果下3個已經沒有資料了,表示不是utf-8編碼
  67. if (i + 3 >= size) { return false; }
  68.  
  69. //如果下3個位元不是10開頭,也不是utf-8編碼
  70. if ((bytes[i + 1] & 0xC0) != 0x80) { return false; }
  71. if ((bytes[i + 2] & 0xC0) != 0x80) { return false; }
  72. if ((bytes[i + 3] & 0xC0) != 0x80) { return false; }
  73.  
  74. //繼續跑下4個
  75. step = 4;
  76. }
  77. else {
  78. //如果都不是當然就不是utf-8了
  79. return false;
  80. }
  81. i += step;
  82. }
  83. if (i == size) { return true; }
  84. return false;
  85. }

參考資料:

沒有留言:

張貼留言