(註:其實這應該要畫很多圖來說明的,在此先略)
ilya先前和我討論到:有沒有可能拿OpenVanilla來做地理資訊的輸入工具?他的需求,簡單說來,有以下兩類:
- 輸入中文地名,出一地的英文地名或古地名
- 輸入中文地名,出一地的經緯度或其他座標資料
在和ilya以及中研院OpenGIS Lab的張欽隆討論後,我們決定將這個輸入法的開發,變成一種公開的實驗。
這篇文章便是這一系列實驗的第一篇。我的初步構想是:藉這個機會,來實現OpenVanilla最初設計的其中一個理想──簡化輸入法的開發過程,讓開發者能專心於輸入法的邏輯和演算法,而不需煩心於各種作業系統平台的實作細節。
同時,我也希望藉由這個實驗,來說明OpenVanilla晚近加入的一個新設計──所謂的「詞彙管理架構」,以及為什麼詞彙管理架構能夠填補現有輸入法設計的不足。
最後,也希望藉由這個實驗,來走一遍一個 open source project 的開發流程,包括了建立程式碼的版本管理倉庫、選擇版本授權等等雖然屬於「周邊」(peripheral)、但對於計劃延續不可或缺的部份。
問題分析
地理資訊的輸入是一件麻煩的事。由於與一個地名相關的資訊種類繁多,如果能以「地名」為中心,也就是拿地名來當key,便能輸入各種資料,對於地理相關工作應該是很有幫助的。Ilya的需求是由此出發的。
同時,地名也是一種有歷史性(historicity)的東西。地名不只是現時、此刻的存在,也代表了歷史的累積。打當今地名而能出古地名的需求,也是由此而衍生出來的。
輸入法所可能提供的協助
輸入法在軟體工程上是一種奇特的存在。雖然輸入法最早是輸入亞洲語文所不可或缺的工具(sine qua non)[1],但是本質上輸入法是一種變換(transform):輸入一串組字鍵碼,得出組字結果。嚴格說來輸入法其實就是一種索引鍵─鍵值(key-value)的查找關係。
而,因為輸入法在現代桌面環境上,可以與任何需要文字輸入的GUI元件共同工作,於是很奇異的,輸入法變成一種增加軟體功能的方法。也就是說,藉由擴充輸入法的功能,我們等於同時也擴充了應用程式的功能(簡繁轉換及標點符號轉換均為其例)。因此,輸入法可以被當成一種「非侵入式的外掛」(non-intrusive plug-in)。
然而,由於主流的輸入法架構,僅僅作到一次性的key-value查找(例如:以倉頡組字碼找中文字),因此就算我們打出了「台北」兩字,我們依然只能把這兩個字剪貼到另一套資料庫程式,藉以檢索與「台北」兩字相關的地理資訊。
OpenVanilla早期設計了所謂的「輸出過濾器」(output filter)的架構,是可以針對「以詞為輸出單元的輸入法」(例如酷音,每一次輸入都是一個詞)做輸出處理。例如,我們可以設計一個「打『台北』出Taipei」filter。然而過濾器的缺點是:在UI邏輯上,filter是一種單向的、多對一的(one-direction, many-to-one)轉換。對於地理資訊來說,我們需要一個反饋的過程,亦即,當打完「台北」之後,我們可能要讓用戶再一次選擇與「台北」相關的地理資訊。
這種類似「二次選字」的事情,用filter是無法做到的。
OpenVanilla的詞彙管理架構
OpenVanilla於2006年初提出了「詞彙管理架構」。事實上一套「詞彙管理架構」是由兩個元件組成的:置如輸入法之前的「鍵碼前置處理器」(Key Preprocessor)跟「詞彙補捉器」(Phrase Catcher)。詞彙補捉器其實是一個filter,只是不停地把使用者輸入的文字給收集進來。
這樣的好處是,讓非以詞彙為輸出單位的輸入法(例如倉頡,一次只輸出一個字),也能夠收集詞彙。而,透過置於輸入法之前的「鍵碼前置處理器」,我們可以在還沒進到輸入法之前,就先對使用者的按鍵做處理:例如,把從「詞彙補捉器」收集來的詞,丟進資料庫裡,或是拿來查找。
也就是說,透過 key preprocessor ,我們可以做到一個「超輸入法」(meta-input method),讓使用者可以用任何一種輸入法打完「台北」兩個字,然後再拿「台北」兩字做一些事。
由於 key processor + phrase catcher 這樣的架構可以做到「再一次的查找與轉換」,我們的地理資訊輸入需求,似乎可以再利用(reuse)這個 pattern 來達成我們的需求。
實作的方法
通常,在原型階段,我們會先用一些 dirty hack 以及 top-down approach ,很快地把程式的功能原型寫出來,來證明概念可行。在此之後,我們再改走 bottom-up approach ,細細烹煮(cook)每個所需的元件,最後再放到一起大火快炒,勾芡上桌。
由於設計樣式已經有了,這個設計也被證明可行,我們要做的是依照需求,重寫一套依地名查找地理資料的輸入法。
在此之前,我們得先把一些材料準備好。
材料:地理資訊的 XML 資料表
我們收到的是一份長相如下的 XML 資料表:
<place id='1'>
<zh_name>漁人</zh_name>
<en_name>Yuren</en_name>
<lon>121˚ 32' 40"</lon>
<lat>22˚ 1' 5"</lat>
</place>
以上是該 XML 資料表的簡化版,我們先列出中文地名、英文地名與經緯度四項資料。
生的XML資料並不是那麼好利用。OpenVanilla推薦使用SQLite3。因此我們得先把XML煮成SQLite3能用的資料。
最懶的方法:XML::Simple
煮 XML 文件的方法很多,最懶又最快的方法,當然是使用 Perl 的 XML::Simple 模組了。
#!/usr/bin/perl -w
use strict;
use XML::Simple;
my $filename=shift or die "Must specify a filename";
my $data=XMLin($filename);
嗯,好像這樣就煮完了,似乎沒什麼挑戰性。當然,如果你只裝 XML::Simple ,在 OS X 上可能會發生一些問題(XML::Simple 預設的 XML parser 是 XML::SAX::PurePerl ,在 UTF-8 判斷上似乎有點 suck),因此建議再安裝 XML::SAX::Expat ,就會順心如意多了:
sudo cpan install XML::SAX::Expat
sudo cpan install XML::Simple
然後是怎麼轉換到 SQLite3 的問題。比較笨的作法是用 Perl 的 DBI 模組,然後實際開啟一個 SQLite3 資料庫,把轉換好的 $data 依欄位一個一個寫進去。
但是,這樣做,就實在太不懶惰了。SQLite3 的好處是,我們完全可以把資料生成另一堆生的 SQL 指令,然後讓 SQLite3 來吃。因為對 SQLite3 來說,一個資料庫就是一個檔案,所以萬一不高興,反正砍掉重練就是了。
所以,我們把煮成了 Perl hash 的資料一個個倒出來:
my $places=$data->{'place'};
for my $x (sort(keys(%$places))) {
my $d = $places->{$x};
print "INSERT INTO geoinfo (zh_name, en_name, lon, lat) VALUES (",
sqlquote($d->{zh_name}), ", ",
sqlquote($d->{en_name}), ", ",
sqlquote($d->{lon}), ", ",
sqlquote($d->{lat}), ");\\n";
}
也就是說呢,我們把資料 dump 成一條又一條的 SQL INSERT 指令。
那個 sqlquote 是一個小的函數,幫忙我們把字串加上 SQL 的 quote:
sub sqlquote {
my $x=shift;
$x =~ s/\\'/\\'\\'/g;
return "'$x'";
}
然後,我們在倒資料前,先加上建立資料庫 schema 和 index 的指令:
print "CREATE TABLE geoinfo (zh_name, en_name, lon, lat);\\n";
print "CREATE INDEX idx_zh_name ON geoinfo (zh_name);\\n";
print "BEGIN;\\n";
並在最後加上
print "COMMIT;\\n";
之所以要用 SQL transaction 來做插入,是因為 SQLite 在 non-transaction insert 的表現慢得嚇死人。用了 BEGIN…COMMIT 之後大體上會瞬間完成所有 insert 的動作。
好了,這樣差不多了,只要再做點水管工(plumbing),我們就可以把 XML 檔案轉成 SQLite3 的 DB 了:
./gendb.pl source.xml | sqlite3 geoinfo.db
從這裡開始……
我們還沒談到去哪找地方放這些程式的問題,因此目前還不會有可以下載的地方。有了資料庫,我們就可以開始想想怎麼設計輸入法了。希望我很快就有時間來寫下一篇。
註解
- T. C. Chiang, D. Liu, K. M. Liu, W. Z. Yang, P. T. Tan, M. J. Hsieh, T. H. Chang, W. L. Hsu, “OpenVanilla – A Non-Intrusive Plug-In Framework of Text Services”. 2005. (Abstract).
lukhnos :: Jun.29.2006 ::
tekhnologia 技術或者藝術 ::
11 Comments »