CNS11643中文全字庫轉換Unicode注音字碼表之實作

因為有機會要用到中文輸入法的調用,查了半天,全部都是依存在Windows每一版本的作業系統中所提供的不同輸入法的DLL程式庫,再進行所謂的DLL Hook,因為不想自己寫的系統元件或程式,被Windows的作業系統版本綁住,所以乾脆把研究方向轉向到自己自建字碼表(在本篇以注音輸入法為例),這樣一來就可以完全的從作業系統底層解脫了。

當然啦,我們不可能自己建立這些字碼表,因此行政院國家發展委員會的全字庫自然是我這次所有研究的重點,再繼續挖掘下去,發現全字庫已經有在政府開放資料網站上進駐,也就是說,我們可以不用辛苦的到他的網站上寫程式慢慢的crawler了。網站在這裡:政府資料開放平台:CNS11643中文標準交換碼全字庫

將全字庫資料轉換成Unicode實體文字,以及對照的注音字碼表

有了全字庫的資料,再稍微地讀一下他的檔案結構,對於有程式設計基礎的人來說,轉換這些數值變成文字,應該都不是太難的事情,以下是我寫的程式碼範例。在轉換的過程中,先暫時捨棄Private Use Area-A,只保留基本多文種平面Basic Multilingual Plane以及表意文字補充平面Supplementary Ideographic Plane。

namespace Slashview
{
  class Program
  {
    static void Main(string[] args)
    {
      string cBasePath = System.Environment.CurrentDirectory;
      //OpenData CNS 主要檔案
      string cCNS_PhoneticPath = "CNS_phonetic.txt";
      //OpenData CNS 2 Unicode 對照檔案
      string cCNS2UNICODE_Unicode_BMP = "CNS2UNICODE_Unicode BMP.txt";  //Basic Multilingual Plane(BMP)基本多文種平面
      string cCNS2UNICODE_Unicode_2 = "CNS2UNICODE_Unicode 2.txt";      //Supplementary Ideographic Plane(SIP)表意文字補充平面
      string cCNS2UNICODE_Unicode_15 = "CNS2UNICODE_Unicode 15.txt";    //Private Use Area-A(PUA-A)保留作為私人使用區(A區)
      //檢查檔案是否存在
      if (!System.IO.File.Exists($"{cBasePath}\\{cCNS_PhoneticPath}")) { WriteLine($"找不到檔案:{cCNS_PhoneticPath}"); return; }
      if (!System.IO.File.Exists($"{cBasePath}\\{cCNS2UNICODE_Unicode_BMP}")) { WriteLine($"找不到檔案:{cCNS2UNICODE_Unicode_BMP}"); return; }
      if (!System.IO.File.Exists($"{cBasePath}\\{cCNS2UNICODE_Unicode_2}")) { WriteLine($"找不到檔案:{cCNS2UNICODE_Unicode_2}"); return; }
      if (!System.IO.File.Exists($"{cBasePath}\\{cCNS2UNICODE_Unicode_15}")) { WriteLine($"找不到檔案:{cCNS2UNICODE_Unicode_15}"); return; }

      //讀取主要檔案
      WriteLine("讀取主要檔案...");
      System.Collections.Generic.List<TableCnsPhonetic> oMain = new System.Collections.Generic.List<TableCnsPhonetic>();
      using (System.IO.StreamReader oSR = new System.IO.StreamReader($"{cBasePath}\\{cCNS_PhoneticPath}"))
      {
        string oLine;
        while (!string.IsNullOrWhiteSpace(oLine = oSR.ReadLine()))
        {
          string[] oTemp = oLine.Split('\t');
          oMain.Add(new TableCnsPhonetic {
            cCNSCode = $"{oTemp[0]}-{oTemp[1]}",
            cPhonetic = $"{oTemp[2]}"
          });
        }
      }
      //讀取字典檔
      WriteLine("讀取字典檔案...");
      System.Collections.Generic.List<TableCnsUnicode> oDictionary = new System.Collections.Generic.List<TableCnsUnicode>();
      using (System.IO.StreamReader oSR = new System.IO.StreamReader($"{cBasePath}\\{cCNS2UNICODE_Unicode_2}"))
      {
        string oLine;
        while (!string.IsNullOrWhiteSpace(oLine = oSR.ReadLine()))
        {
          string[] oTemp = oLine.Split('\t');
          oDictionary.Add(new TableCnsUnicode
          {
            cCNSCode = $"{oTemp[0]}-{oTemp[1]}",
            cUnicode = $"{oTemp[2]}"
          });
        }
      }
      using (System.IO.StreamReader oSR = new System.IO.StreamReader($"{cBasePath}\\{cCNS2UNICODE_Unicode_15}"))
      {
        string oLine;
        while (!string.IsNullOrWhiteSpace(oLine = oSR.ReadLine()))
        {
          string[] oTemp = oLine.Split('\t');
          oDictionary.Add(new TableCnsUnicode
          {
            cCNSCode = $"{oTemp[0]}-{oTemp[1]}",
            cUnicode = $"{oTemp[2]}"
          });
        }
      }
      using (System.IO.StreamReader oSR = new System.IO.StreamReader($"{cBasePath}\\{cCNS2UNICODE_Unicode_BMP}"))
      {
        string oLine;
        while (!string.IsNullOrWhiteSpace(oLine = oSR.ReadLine()))
        {
          string[] oTemp = oLine.Split('\t');
          oDictionary.Add(new TableCnsUnicode
          {
            cCNSCode = $"{oTemp[0]}-{oTemp[1]}",
            cUnicode = $"{oTemp[2]}"
          });
        }
      }
      //比對與儲存
      WriteLine("比對主要檔案與字典檔案,並運算成資料庫...");
      System.Collections.Generic.List<TableFinalResult> oResult = new System.Collections.Generic.List<TableFinalResult>();
      int iCounter = 1;
      foreach (var oTempS in oMain)
      {
        Console.CursorLeft = 0;
        Write($"處理進度:{iCounter}/{oMain.Count}");
        string cHexCode = "";
        foreach (var oTempT in oDictionary) { if (oTempS.cCNSCode == oTempT.cCNSCode) { cHexCode = oTempT.cUnicode; break; }}
        //將Unicode Private Use Area-A(PUA-A)過濾掉,因為一般使用者不可能會有這些字形檔
        if (cHexCode.Length == 5 && cHexCode.Substring(0, 1) == "F") { cHexCode = ""; }
        //比對後儲存
        if (cHexCode.Length != 0)
        {
          int iTemp = System.Convert.ToInt32(cHexCode, 16);
          oResult.Add(new TableFinalResult()
          { 
            iUnicode = iTemp,
            cUnicode = cHexCode,
            cPhonetic = oTempS.cPhonetic,
            cString = System.Text.Encoding.UTF32.GetString(System.BitConverter.GetBytes(iTemp))
          });
        }
        iCounter++;
      }
      //寫成檔案
      WriteLine("將資料庫寫成檔案...");
      using (System.IO.StreamWriter oSW = new System.IO.StreamWriter($"{cBasePath}\\中文全字庫注音資料庫檔案.txt", false, System.Text.Encoding.UTF8))
      {
        foreach (var oTemp in oResult)
        {
          oSW.WriteLine($"{oTemp.iUnicode}\t{oTemp.cUnicode}\t{oTemp.cPhonetic}\t{oTemp.cString}");
        }
      }
    }
  }
  public class TableCnsPhonetic
  {
    public string cCNSCode { get; set; }
    public string cPhonetic { get; set; }
  }
  public class TableCnsUnicode
  {
    public string cCNSCode { get; set; }
    public string cUnicode { get; set; }
  }
  public class TableFinalResult
  {
    public int iUnicode { get; set; }
    public string cUnicode { get; set; }
    public string cPhonetic { get; set; }
    public string cString { get; set; }
  }
}

經過轉換後,我們將會得到十進制(Dec)的Unicode編碼、十六進制(Hex)的Unicode編碼、注音字碼表、真實的Unicode文字,好歡樂啊!接下來就可以快樂的倒進資料庫中,快快樂樂的設計實作自己的輸入法嘍!

CNS11643 Unicode UTF8 UTF16 UTF32 字面 轉換 字典檔