有個偵測文字檔是否為BIG5編碼的需求,決定寫個小函數來實現。

要偵測BIG5編碼,有一種寫法是將字串轉為byte[]後再比對0xA440-0xC67E等編碼區間(參考)加以識別;不過,我不太喜歡這類寫法,理由是既然BIG5編碼邏輯細節已存在.NET內建Encoding類別中(可透過Encoding.GetEncoding(950)或Encoding.GetEncoding("big5")取得),編碼區間定義另外出現在我們的程式碼多少違背了"觀注點分離(SoC), Separation of Concerns"哲學。BIG5編碼所有的細節應該由專屬Encoding類別掌管,其他程式碼不需了解也不應涉及,若編碼邏輯需變動時,才不致牽連其他程式也得跟著調整,如此才符合SoC的精神。(雖然BIG5編碼規則未來改變的機率跟林志玲成為我紅粉知己的機率一樣低... 謎之聲: 大叔,你病得不輕啊~~)

決定採取先前用過的難字偵測法,取得檔案原始內容(byte[]),利用Encoding.GetString()將byte[]以BIG5編碼解讀成字串,再Encoding.GetBytesCount()將字串還原回byte[]計算長度,若陣列長度與來源陣列長度一致,即表示其中內容完全符合BIG5編碼,即便其非中文內容(例如: 純英數字),使用BIG5編碼處理也不會破壞資料。

以下為程式範例,同時建立UTF-8(無BOM)、UTF-8(含BOM)、Unicode、BIG5、英數字ASCII五種不同編碼檔案進行測試。

排版顯示純文字
using System;
using System.Text;
using System.IO;
 
namespace DetectEncoding
{
    class Program
    {
        //偵測byte[]是否為BIG5編碼
        public static bool IsBig5Encoding(byte[] bytes)
        {
            Encoding big5 = Encoding.GetEncoding(950);
            //將byte[]轉為string再轉回byte[]看位元數是否有變
            return bytes.Length ==
                big5.GetByteCount(big5.GetString(bytes));
        }
        //偵測檔案否為BIG5編碼
        public static bool IsBig5Encoding(string file)
        {
            if (!File.Exists(file)) return false;
            return IsBig5Encoding(File.ReadAllBytes(file));
        }
 
        static void Main(string[] args)
        {
            //準備5個檔案,分別存成UTF-8(無BOM), UTF-8(含BOM), Unicode, BIG5, 英數字
            string s = "中文測試";
            string[] files = new string[] {
                "D:\\UTF8.txt", "D:\\UTF8wBOM.txt", "D:\\Unicode.txt",
                "D:\\BIG5.txt", "D:\\ASCII.txt"
            };
            //WriteAllText(),預設Encoding為不含BOM的UTF-8編碼
            File.WriteAllText(files[0], s);
            //存為含BOM的UTF-8
            File.WriteAllText(files[1], s, new UTF8Encoding(true));
            //存為Unicode
            File.WriteAllText(files[2], s, Encoding.Unicode);
            //存為BIG5
            File.WriteAllText(files[3], s, Encoding.GetEncoding(950));
            //純英數字,存為ASCII
            File.WriteAllText(files[4], "123ABC", Encoding.ASCII);
            //測試結果
            foreach (string file in files)
            {
                Console.WriteLine("{0} {1}BIG5編碼.",
                    Path.GetFileName(file), 
                    IsBig5Encoding(file) ? "相容於" : "不相容於"
                    );
            }
            Console.Read();
        }
    }
}

執行結果如下:

UTF8.txt 不相容於BIG5編碼.
UTF8wBOM.txt 不相容於BIG5編碼.
Unicode.txt 不相容於BIG5編碼.
BIG5.txt 相容於BIG5編碼.
ASCII.txt 相容於BIG5編碼.


Comments

# by Allen Kuo

我開一個檔案,裡面只輸入"中",並存成簡體中文(codepage 936)時,可以通過測試, 看來要再想想是否有其他方式

# by Jeffrey

to Allen Kuo, Encoding.GetEncoding(950).GetString(Encoding.GetEncoding(936).GetBytes("中"))會得到"笢 ",換句話說,從讀取程式的觀點,這個檔案可能是一個存了"笢"字的Big5檔案,也可能是一個存了"中"字的GB2312檔案。二者都有可能也都合理,我個人認為這個情境下,無法判別它是BIG5還是GB2312。

# by Allen Kuo

嗯, 原本我想寫一個可以自動判別簡中或繁中編碼的程式,不過看來無法做到

# by Eric

感謝您,解決了我一個大問題!! Utf-8編碼的檔案如果有BOM還好解決,但沒有BOM的Utf-8就無法判斷是big5還是utf-8了。 剛好這個Function轉換了我的思維,原來還可以用轉換後陣列長度的方式來判斷!!太棒了!!

# by Eric

您好~可否轉載此篇到我的blog呢?想做個筆記~ 會註明出處的,謝謝!!

# by Eric

您好,後來我發現個問題,當utf-8(無BOM)編碼的文字檔內僅有"測試"這兩個中文字時,使用函數判斷是會回傳True的,也就是那六組byte,每兩組都有相對應的Big5編碼文字,所以會誤判。 萬一檔案內所有的byte都有相對應的big5文字的話,此方法就不能用了耶@@

# by Jeffrey

to Eric, 歡迎轉載應用。你所說的誤判狀況是存在的,因UTF-8與BIG5的編碼區間重疊,故此法要達100%精準判斷是不可能的。 所幸,當文字數愈多,其UTF-8編碼要完全相容BIG5的機率就愈低,在我的實務經驗裡,這個極簡便但非絕對精準的做法處理實際資料尚未出錯過(原因是系統所輸入文字檔均有一定長度),加上誤判造成的損失有限,仍可視為有效的解決方案。

# by Eric

Hi Jeffrey大您好: 小弟因公司專案需要處理檔案,但檔案內中文字最少很可能只有三個字而已,非常有可能會誤判,所以自己研究了一下utf-8的規格,發現編碼並不難,所以撰寫了C#版的程式,應該可大幅降低誤判的機率(今日發生過一次,尚在研究中)。 也請不吝指教,小弟發表在 http://eric0806.blogspot.tw/2014/07/detect-file-encoding-utf8.html

# by Jeffrey

to Eric, 謝謝你的回饋分享。 依我淺見,解析UTF-8編碼並無法解決模稜兩可的情境。理由是若有個檔案只有6個Byte,可被解釋成兩個UTF-8字元或三個BIG5字元,則你會得到IsUtf8()及IsBig5()都傳回true的結果,仍無法判斷是哪一種。 我在處理簡繁體自動辨識時遇過極為相似的情境(http://blog.darkthread.net/post-2014-04-27-big5-gb2312-auto-detect.aspx),曾用過另一種輔助解法: 將中文字區分為常用字及罕用字,一般而言,常用字出現的機率高於罕用字。因此,若BIG5解析出的文字為常用字,而UTF-8解出的字為罕用字,我就推測它是BIG5的可能性"比較高",但這些都只能提高猜對的機率,真的遇上原文是罕用字的特殊案例仍會誤判。基本上,要做到絕對精準,我認為無解。

# by 迪納敏斯

HI 最近剛好需要這個東西.. 因為之前VS2013用T4產生的檔案忘記指定編碼變成BIG5 VS2015的編譯器下會有問題 不過我簡單的加了一個 var bytes = File.ReadAllBytes(file); if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) return false; 判斷是否有UTF8 BOM 如此一來"至少" 有BOM的UTF8檔案不會被誤判

# by DeanLai

感謝黑大,解決了最近的一個大問題!! 也是與 Eric 的情況相同,遇到回傳回來的是 NoBom的Utf-8 原來用轉換後陣列長度會不同!!太開心了!!

# by adiot

># 2014-07-14 11:20 PM by Eric >您好,後來我發現個問題,當utf-8(無BOM)編碼的文字檔內僅有"測試"這兩個中文字時,使用函數判斷是會回傳True的,也就是那六組byte,每兩組都有相對應的Big5編>碼文字,所以會誤判。 >萬一檔案內所有的byte都有相對應的big5文字的話,此方法就不能用了耶@@ 再多加一個utf-8字串長度測試: big5長度對而utf-8長度錯=>big5編碼 兩種長度均對=>utf-8編碼 可以進一步排除

Post a comment