拆解 DICT dictionary

摘要:拆解 DICT dictionary

DICT Protocol這個專案已經run了很久了,很多我們熟悉的網路版字典查詢或是一些PDA上手機字典,大部分都會使用該標準所產生的
字典檔與索引檔,來提供字典的查詢與使用。DICT Protocol中,幾個重點的檔案格式大致如下:
*.dict:這個是dict主要的字典資源檔,說明白一點,就是整個字典的資訊全部都放在這個檔案上。
*.idx:這個是Index檔,主要採用B-Tree的方式存放.dict檔案中所有字的索引,在DICT的使用上,會先將idx檔載入記憶體中,
   幫助快速搜尋。.idx中的檔案,其按照一固定格式排放,格式如下:
   word_str// a utf-8 string terminated by '\0'. 以utf-8存放單字名稱,並且用'\0'當結束字元。
   word_data_offset// word data's offset in .dict file. 代表該單字的資料是從那個一位元開始,舉例來說:god的資料占了30bit,
                        而下一個字good的offset就是30,他要從第31bit開始取得。在word_data_offset
                        是使用4bytes來存放資料。
   word_data_size//word data's total size in .dict file. 代表該單字的資料大小。word_data_size用4bytes存放資料。
*.ifo:資訊檔,用於紀錄該.dict的版本資訊、字數量與其他相關的參數。
其他相關的資訊,大家可參考「StarDict Dictionary Format」或是「http://code.google.com/p/babiloo/wiki/StarDict_format」。
 
StarDict這個網站提供了相當多的字庫檔,因此,我找了一個字典來當做拆解dict的練習。大致上拆解的方式,如下之解釋:
1. 讀取 .idx檔。根據 .idx的format拆解出每個字的三個重要參數:word_str、word_data_offset、word_data_size。
2. 取出的offset需要做位移的補足動作,因為在.idx中存放的是16進位之格式,因此需要做位移的調整。
3. 調整完成後,再讀取 .dict檔,並按照三個參數的值,分別移動指向該 .dict的指標進行移動,讀取資料。
4. 讀取出來的資料再轉回到 .txt或.dat的檔案之中。
 
下方的程式,主要是透過C#來撰寫,讀取檔案的方式是使用 FileStream,配合 BinaryReader,讓所有資料變成Binary的格式,
在做位移的調整上也會比較好做。另外,分別使用 word_str()、word_offset()、word_length()三個函數來取得三個主要的參數,
在餵入word_offset()與word_length()時,分別各取得固定長度4bytes,接著將4bytes的值根據16進位來調整位移度,並回結果,
最後統一交由getvocaublaryinformation()這個function將單字的資料取出,並且寫入.txt檔中。
 
private void separate()
       {
           FileStream objFsIdx = new FileStream("21shijishuangxiangcidian-big5.idx", FileMode.Open);
           BinaryReader objBrIdx= new BinaryReader(objFsIdx);
 
           FileStream objFsDict = new FileStream("21shijishuangxiangcidian-big5.dict", FileMode.Open);
           BinaryReader objBrDict = new BinaryReader(objFsDict);
 
           TextWriter objTW = new StreamWriter("21data.txt");
          
           //basic parameters
           int intOffest = 0;
           int intLength = 0;
           int intPoint = 0;
           int[] intArray=new int[100];
 
 
           while (objBrIdx.BaseStream.Position < objBrIdx.BaseStream.Length)
           {
               int intNow = objBrIdx.ReadByte();
               intArray[intPoint] = intNow;
               if (intNow == 00)
               {
                   //Console.WriteLine(word_str(intArray));
                   intOffest = word_offset(objBrIdx.ReadBytes(4));
                   intLength=word_length(objBrIdx.ReadBytes(4));
                   objTW.WriteLine (word_str(intArray,intPoint) +"\\n"+getvocabularyinformation(objBrDict, intOffest, intLength));
                   intPoint = 0;
                   break;
                   //continue;                   
               }
               intPoint += 1;
           }
           objTW.Close();
 
       }
       private String word_str(int[] intArray, int intEnd)
       {
           String word = "";
           for (int intX = 0; intX <intEnd; intX++)
           {
               if (intArray[intX] != 00)
               {
                   word += Convert.ToChar(intArray[intX]);
               }
           }
           return word;
       }
 
       private int word_offset(byte[] byteArray)
       {
           int offset = 0;
           String strHex = "";
           for (int intX = 0; intX < byteArray.Length; intX++)
           {
               strHex+=Convert.ToString(byteArray[intX],16).PadLeft(2,'0');
           }
           offset = Convert.ToInt32(strHex, 16);
           return offset;
       }
 
       private int word_length(byte[] byteArray)
       {
           int length = 0;
           String strHex = "";
           for (int intX = 0; intX < byteArray.Length; intX++)
           {
               strHex += Convert.ToString(byteArray[intX], 16).PadLeft(2, '0');
           }
           length = Convert.ToInt32(strHex, 16);
           return length;    
       }
 
       private String getvocabularyinformation(BinaryReader objBR, int intO, int intL)
       {
           String strX="";
           while (objBR.BaseStream.Position < objBR.BaseStream.Length)
           {
               objBR.BaseStream.Position = intO;
               byte[] objBytes=objBR.ReadBytes(intL);
               strX= System.Text.Encoding.UTF8.GetString(objBytes);
 
               strX = strX.Replace("\n", "\\n");
 
               //Console.WriteLine(strX);
               break;
           }
           return strX;            
       }
以上是個人跟學長在比賽撰寫拆解DICT字典的比賽中一些心得,不過學長用java寫的比我還好,果然薑還是老的辣,希望能對大家有所幫助。
 
[以下是小紀所提供程式修改的部分]
查StartDict格式時, 剛好看到你的Blog, 我想說薑可能是老的辣, 但語言不一定是老的好, C# 不應會比 JAVA 差
我看了你的程式, 覺得可以改的地方很多, 首先 word_str()、word_offset()、word_length() 這三個函數是多餘的
其中offset 及 length 可以用 位移 的方式來計算, 直接轉char輸出 word_str就可省了.
我把 separate() 改了一下, getvocabularyinformation() 也省, 你看看...
01 private void separate()
02 {
03 FileStream objFsIdx = new FileStream("Babylon_English_Chinese_T_.idx", FileMode.Open);  
04 FileStream objFsDict = new FileStream("Babylon_English_Chinese_T_.dict", FileMode.Open);  
05
06 TextWriter objTW = new StreamWriter("dict.txt");
07
08 int intNow;
09 int intOffest = 0;
10 int intLength = 0;
11 byte[] baBuffer = new byte[512];
12
13 int i = 0;
14
15 for (i = 0; i < objFsIdx.Length; i++)
16 {
17 intNow = objFsIdx.ReadByte();
18 if (intNow == 0)
19 {
20 intOffest = (((((objFsIdx.ReadByte() << 8) | objFsIdx.ReadByte()) << 8) | objFsIdx.ReadByte()) << 8) | objFsIdx.ReadByte();
21 intLength = (((((objFsIdx.ReadByte() << 8) | objFsIdx.ReadByte()) << 8) | objFsIdx.ReadByte()) << 8) | objFsIdx.ReadByte();
22
23 objFsDict.Seek(intOffest, SeekOrigin.Begin);
24 objFsDict.Read(baBuffer, 0, intLength);
25 objTW.Write("\\n{0}\r\n", System.Text.Encoding.UTF8.GetString(baBuffer, 0, intLength).Replace("\n", "\\n"));
26
27 i += 8;
28 }

29 else
30 objTW.Write((char)intNow);
31 }

32 objTW.Dispose();
33 objFsIdx.Dispose();
34 objFsDict.Dispose();
35 }
 
References:
DICT.TW 線上字典:http://dict.tw/