libTabe - library for Taiwan And Big5 Encoding
專案目標: More general Chinese language processing
專案首頁: http://libtabe.sourceforge.net/
作者: 蕭百翔
xcin 叫用 libtabe 做的的詞音輸入法程式架構:
- 建造詞庫 .db 檔:
- 從詞庫 tsi.src 檔建造以詞為索引的 Berkeley DB 檔 test_tsi.db: tsiadd -d test_tsi.db -f tsi.src -r -y
- 建造以注音為索引的 Berkeley DB 檔 test_yin.db: tsiyindump -d test_tsi.db -y test_yin.db
- 合併兩個詞庫:先用 tsiadd 從第一個詞庫檔 (.src) 建出一個 .db 檔,再用 tsiadd 把第二個詞庫檔合併到剛剛產生的 .db 檔中。
第一個 libTabe 的應用:bims (詞音輸入法)
以下是從蕭百翔寫的 libtabe.sgml 文件檔轉出來的
libtabe -- TaBE 函式庫
$Id: libtabe.sgml,v 1.2 2001/08/20 03:53:02 thhsieh Exp $
在電腦上處理中文,跟原有的 C 函式庫一直很難順利的整合。 以 Big5 碼來說,一個字佔了兩個 byte,不管是計算字串長度, 或者是要考慮中文字的字序,甚至要處理詞或句子的時候,都需要做特別的調整, 原有的 C 函式庫根本不敷使用。 另一方面,由於每個程式開發人員在處理中文的時候,都有自己的方法, 所以不同程式之間很難分享函式庫,更不用提重覆利用了。 TaBE 計畫希望能夠提供一個更好的中文使用環境,因此, 就很需要一個能處理中文字、詞、句的統一介面與函式庫, 來做為所有應用程式的基礎。 libtabe,就是希望成為這樣的一套函式庫,提供統一的介面與足夠的功能, 讓應用程式有強大的基礎可以發揮。
介紹
libtabe 將中文的處理分成幾個階段,由小到大分別是:
注音符號(ZuYin Symbols):
- 每一個中文字都由一個或多個的注音符號加上一個聲調符號所組成。 注音符號加上聲調符號的總數是四十二個 (三十七個注音符號加上五個聲調符號)
讀音(Yin):
- 一個讀音是由一個或多個的注音符號加上一個聲調符號組成。 每個讀音都是具有意義的,可以代表一個中文字。當然, 不同的中文字也許會有相同的讀音。一個字也可能有數種讀音。
字(Zhi):
- 也就是中文字。
詞音(TsiYin):
- 一個詞的讀音。一個詞可能有數種讀音。
詞(Tsi):
- 由一到數個字所組成。在中文語言上,是最基本又具有意義的單位。
句(Chu):
- 一個句子可以表達一個完整的概念,由一到數個詞所組成。
libtabe 針對以上各個處理階段都有提供一些函式。也在相臨的處理階段之間, 提供轉換的函式。往後的章節,將一一介紹每個處理階段中所提供的函式, 及與相鄰處理階段間的轉換函式。
使用不同的編碼處理中文時,可能會有不同的性質, 所以這個函式庫採用 Big5 碼做內碼,需要轉換成其他中文碼時, 可以使用轉換的函數。
資料結構
前言
在本節中,我們將介紹在 libtabe 中所會使用到的幾個基本資料結構。 有些資料結構雖然只是其他資料形態的重新定義而已,不過, 為了不與 C 語言中的資料形態相混淆,我們還是使用這樣的方式。
定義
typedef unsigned char *ZuYinSymbol; typedef unsigned char *ZuYinSymbolSequence; typedef unsigned int ZuYinIndex; typedef unsigned int ZhiCode; typedef unsigned char *Zhi; typedef unsigned char *ZhiStr; typedef unsigned long int Yin; struct ZhiInfo { ZhiCode code; Zhi chct; Yin yin[4]; unsigned long int refcount; /* should be obsoleted soon */ }; struct TsiInfo { ZhiStr tsi; unsigned long int refcount; unsigned long int yinnum; Yin *yindata; }; struct TsiYinInfo { Yin *yin ; unsigned long int yinlen; unsigned long int tsinum; ZhiStr tsidata; }; struct ChunkInfo { ZhiStr chunk; int num_tsi; struct TsiInfo *tsi; }; struct ChuInfo { ZhiStr chu; int num_chunk; struct ChunkInfo *chunk; }; struct TsiDB { int type; int flags; char *db_name; void *dbp; void *dbcp; . . . }; struct TsiYinDB { int type; int flags; char *db_name; void *dbp; void *dbcp; . . . };
說明
ZhiCode:存放 Big5 碼。 Zhi:存放一個 Big5 碼表示的中文字。這不一定是一個字串,可能只有兩個 byte 而已。 ZhiStr:存放一些 Big5 碼表示的中文字。這一定是一個字串,由 NULL 終結。 Yin:存放一個讀音。 ZhiInfo:一個中文字的資料結構。透過這個資料結構,可以取得許多有關這個字的資訊。yin 中存放這個字的讀音,最多四個。 TsiInfo:一個詞的資料結構。透過這個資料結構,可以取得許多有關這個字的資訊。yinnum 與 yindata 中存放的是這個詞的正確讀音,也許不只一個。 TsiYinInfo:一個詞音的資料結構。yinlen 是這個詞音的長度,tsinum 是這個讀音共有幾個詞,tsidata 是這些詞首尾相接存放。 TsiDB:詞庫的資料結構。透過這個資料結構,可以存取詞庫中的詞,與其他相關資訊。 TsiYinDB:詞音庫的資料結構。透過這個資料結構,可以存取詞音庫中的同音詞,與其他相關資訊。 ChunkInfo:用來斷詞的資料結構。要斷詞的片段存放在這裡,斷出來的詞則存放在 tsi 中。 ChuInfo:描述一個句子的資料結構。一個句子又可再分為數個片段。
注音符號
前言
注音符號是台灣地區所有人在學習國語時,用來學習發音方法的工具。這樣的方法,可以明確的表示一個字的讀音,不會造成混淆。所以,libtabe 使用注音符號做為比讀音更小的處理單位。
注音符號共有三十七個,再加上五個聲調符號,總計是四十二個。為了方便在電腦中處理注音符號,libtabe 從 "ㄅ" 到 "ㄦ",依序給與 1 到 37 的注音編號 (Index)。38 是輕聲(˙),39 到 42 分別是一聲到四聲。0 保留不使用,以方便與讀音處理階段的轉換。
- tabeZuYinIndexToZuYinSymbol()
const Zhi tabeZuYinIndexToZuyinSymbol(ZuYinIndex idx);
在前面所提過的注音編碼 (Index) 與注音符號 (ZuYinSymbol) 之間做轉換。
- tabeZuYinSymbolToZuYinIndex()
int tabeZuYinSymbolToZuYinIndex(ZuYinSymbol sym);
與 tabeZuYinIndexToZuYinSymbol() 相似,只不過是相反的轉換。 - tabeZozyKeyToZuYinIndex()
int tabeZozyKeyToZuYinIndex(int key);
本函式提供零一中文注音輸入法的按鍵與注音編號之間的轉換。
讀音
前言
從中文微電腦推廣基金會 (CMEX) 所提供的屬性表中統計得到,Big5 碼中所包含的中文字共有 1302 個不同的讀音。而每個讀音是由注音符號加聲調符號所組成,所以我們就用注音編碼來組成讀音的編碼,方便轉換。又,為了與傳統的使用慣例結合,當該讀音是第一聲時,我們並不使用該聲調符號。
比方說,"ㄓㄨㄥ" 這個讀音就是 ((((15*43)+23)*43+36)*43)+0)*43。15、23、36 分別是 "ㄓ"、"ㄨ"、"ㄥ" 的注音編號。由於一個讀音最多有三個注音符號加一個聲調符號,採用這種方式比較就不會混淆,也不需要注音符號出現的位置做特殊的比對。
CMEX 的屬性檔中,一個中文字最多有四種讀音。在 Big5 碼中慣用的 13060 個中文字中,12098 個字只有一個讀音,888 個字有兩個讀音,62 個字有三個讀音,10 個字有四個讀音。另外,還有兩個字沒有讀音,分別是兀 (0xC94A) 與嗀 (0xDDFC) 這兩個次常用字。(因為他們是重覆的字。而且 CMEX 的字形檔中也沒有這兩個字) 由於我們使用 CMEX 的屬性檔轉出來的對照表,所以這兩個字就不給予讀音。
- tabeYinToZuYinSymbolSequence()
ZuYinSymbolSequence tabeYinToZuyinSymbolSequence(Yin yin);
將讀音轉為所組成的注音符號與聲調符號。 - tabeZuYinSymbolSequenceToYin()
Yin tabeZuYinSymbolSequenceToYin(ZuYinSymbolSequence str);
將注音符號與聲調符號的組合轉成讀音。 - tabeYinLookupZhiList()
ZhiStr tabeYinLookupZhiList(Yin yin);
查詢所有具有這個讀音的字。這些字存放在一個字串中。
字
前言
在台灣所慣用的 Big5 碼中,共有 13060 個中文字,分別是 5401 個常用字,7652 個次常用字,再加上倚天延伸字集的 7 個中文字。
- tabeZhiInfoLookupYin()
int tabeZhiInfoLookupYin(struct ZhiInfo *h);
查詢 ZhiInfo 這個資料結構中的 code 所代表的中文字所有的讀音,存放在 ZhiInfo 的 yin 中。傳回值如果小於 0,代表該字沒有讀音。
詞
前言
在這套函式庫中,詞是最重要的處理階段。對中文語言來說,詞跟英文中的單字一樣,都是具有意義的最小單位。但是跟英文不同的是,中文的詞與詞間沒有明顯的分隔,或是說像英文中的空白一樣,可以區隔兩個單字的分隔符號。
因此,我們需要斷詞。斷詞能將一串中文字中的詞分隔出來。
中文的斷詞目前都是採用比對詞庫的方式來作,所以一個足夠大且效率好的詞庫是需要的。 所以,libtabe 中用 Berkeley DB 來管理詞庫,如此一來,既使詞庫很大時,也還能維持很好的效能。
- tabeTsiInfoLookupPossibleTsiYin()
int tabeTsiInfoLookupPossibleTsiYin(struct TsiInfo *tsi);
查出 TsiInfo 中的 tsi 這個字串所有的詞音。詞音的總數存在 TsiInfo 的 yinnum 中,yindata 則是這些詞音,連續存放。
- tabeTsiDBOpen()
struct TsiDB *tabeTsiDBOpen(int type, const char *db_name, int flags);
開啟一個詞庫。type 是詞庫的種類,目前只有 DB_TYPE_DB 一種,也就是用 Berkeley DB。flags 是這個資料庫的一些特性,如下: DB_FLAG_OVERWRITE:如果在寫入詞庫時已有重覆的詞,覆寫掉它。DB_FLAG_CREATEDB:如果開啟詞庫時找不到此詞庫,新增該檔。DB_FLAG_READONLY:開啟詞庫為唯讀狀態。不能與前兩個 flag 混用。 傳回的 TsiDB 就是當呼叫其他詞庫處理函數時第一個參數。 tsidb->Close()
void tsidb->Close(struct TsiDB *tsidb);
關閉詞庫。tsidb->RecordNumber()
int tsidb->RecordNumber(struct TsiDB *tsidb);
查詢該詞庫中共有多少筆詞。tsidb->Put()
int tsidb->Put(struct TsiDB *tsidb, struct TsiInfo *tsi);
將 TsiInfo 中的 tsi、refcount、yinnum、yindata 存入詞庫中。
tsidb->Get()
int tsidb->Get(struct TsiDB *tsidb, struct TsiInfo *tsi);
tsidb->CursorSet()
int tsidb->CursorSet(struct TsiDB *tsidb, struct TsiInfo *tsi);
將詞庫的游標設到 TsiInfo 的 tsi 所指向的地方。如果 tsi 不存在,則指向第一筆記錄。同時也將該詞填入 TsiInfo 中。
tsidb->CursorNext()
int tsidb->CursorNext(struct TsiDB *tsidb, struct TsiInfo *tsi);
讀取下一筆詞。tsidb->CursorPrev()
int tsidb->CursorPrev(struct TsiDB *tsidb, struct TsiInfo *tsi);
讀取上一筆詞。
詞音
前言
詞音為詞的讀音。
- tabeTsiYinDBOpen()
struct TsiYinDB *tabeTsiYinDBOpen(int type, const char *db_name, int flags);
開啟一個詞音庫。type 是詞庫的種類,目前只有 DB_TYPE_DB 一種,也就是用 Berkeley DB。flags 是這個資料庫的一些特性,如下: DB_FLAG_OVERWRITE:如果在寫入詞音庫時已有重覆的詞音,覆寫掉它。DB_FLAG_CREATEDB:如果開啟詞音庫時找不到此詞音庫,新增該檔。DB_FLAG_READONLY:開啟詞音庫為唯讀狀態。不能與前兩個 flag 混用。 傳回的 TsiYinDB 就是當呼叫其他詞庫處理函數時第一個參數。 yindb->Close()
void yindb->Close(struct TsiYinDB *yindb);
關閉詞音庫。yindb->RecordNumber()
int tabeTsiYinDBRecordNumber(struct Big5TsiYinDB *yindb);
查詢該詞音庫中共有多少筆詞音。yindb->Put()
int yindb->Put(struct TsiYinDB *yindb, struct TsiYinInfo *yin);
將 TsiYinInfo 中的 yin、yinlen、tsinum、tsidata 存入詞音庫中。
yindb->Get()
int yindb->Get(struct TsiYinDB *yindb, struct TsiYinInfo *yin);
查詢詞庫中是否有 TsiYinInfo 中的 yin 這個詞音。有的話將其他資料由詞音庫中填入 TsiYinInfo 中。
yindb->CursorSet()
int yindb->CursorSet(struct TsiYinDB *yindb, struct TsiYinInfo *tsiyin);
將詞音庫的游標設到 TsiYinInfo 的 yin 所指向的地方。如果 yin 不存在,則指向第一筆記錄。同時也將該詞填入 TsiYinInfo 中。
yindb->CursorNext()
int yindb->CursorNext(struct TsiYinDB *yindb, struct TsiYinInfo *yin);
讀取下一筆詞音。yindb->CursorPrev()
int yindb->CursorPrev(struct TsiYinDB *yindb, struct TsiYinInfo *yin);
讀取上一筆詞音。
句
前言
為了讓人與電腦在字與詞的處理上取得平衡,我們設定人跟電腦溝通的最小單位是句子。不過句子的定義太過模糊,再加上個人在電腦上使用中文的習慣不盡相同,所以何為『句』的界限很難界定。『片段』是我們在句與詞之間加入的一個中間媒介。一個『片段』全為中文字所組成,為非中文字所區隔。一個句子可以有許多片段。
針對句子的處理,除了把它細分為字,更重要的是要有詞的觀念。所以斷詞是這個階段的重要處理函數。
- tabeChuInfoToChunkInfo()
int tabeChuInfoToChunkInfo(struct ChuInfo *chu);
將 ChuInfo 中的 ChunkInfo 找出來。ChunkInfo 總數存放於 num_chunk 中,而 chunk[i] 是指第 i+1 個 ChunkInfo。
- tabeChunkInfoSegmentationSimplex()
int tabeChunkInfoSegmentationSimplex(struct TsiDB *tsidb, struct ChunkInfo *chunk);
使用最基本的『長詞優先法則』將 ChunkInfo 中的詞都找出來。每個詞都存放於一個 TsiInfo 資料結構中,tsi[i] 是指第 i+1 個詞。詞的總數存放於 num_tsi 中。
- tabeChunkInfoSegmentationComplex()
int tabeChunkInfoSegmentationComplex(struct TsiDB *tsidb, struct ChunkInfo *chunk);
使用蔡志浩的斷詞演算法將 ChunkInfo 中的詞都找出來。每個詞都存放於一個 TsiInfo 資料結構中,tsi[i] 是指第 i+1 個詞。詞的總數存放於 num_tsi 中。
有關蔡志浩的斷詞演算法可參考 http://casper.beckman.uiuc.edu/~c-tsai4/chinese/wordseg/mmseg.html
- tabeChunkInfoSegmentationBackward()
int tabeChunkInfoSegmentationBackward(struct TsiDB *tsidb, struct ChunkInfo *chunk);
使用林宣華 ([email protected]) 的斷詞演算法將 ChunkInfo 中的詞都找出來。每個詞都存放於一個 TsiInfo 資料結構中,tsi[i] 是指第 i+1 個詞。詞的總數存放於 num_tsi 中。