Here are some code snippets that I use to add CJK (Chinese, Japanese and Korean) characters support to Ferret and acts_as_ferret.
(Update: lingr.com has released their multilingual analyzer, see http://blog.lingr.com/2007/05/a_new_plugin.html for detail).
相信許多鐵道迷都聽過雪貂(Ferret)。雪貂是一套根據Lucene所開發的全文搜尋引擎。裝上了「化身為雪貂」(acts_as_ferret)這套plug-in之後就更厲害了,任何ActiveRecord model只要加上輕量之人最愛的神秘一行,瞬間就具有了全文搜尋能力。
Ferret是用C寫成的,用語和基本觀念與Lucene一致。因此對Lucene有認識的朋友應該很容易上手。雖然說化身為雪貂很好用,不過O’Reilly的Ferret一書仍有一讀的必要。該書最後還介紹如何配合其他plug-in來index諸如PDF, JPEG EXIF等metadata,幾乎可以寫一套小型的Mac OS X Spotlight。而該書對於Ferret的構成、內部運作原理、performance tuning的介紹,也相當實用(而且,不需要先學Lucene;我也還在研讀這一部份就是了……)。
以下是我用的 RegExpAnalyzer,僅僅很簡單的把歐語的單詞拆開、數字拆開,中日韓文則以字元方式來索引。這種簡單的中日韓文tokenizing在搜尋精確度不要求高的場合,大體能用。要更好的搜尋結果,或是要做到同音字搜尋、簡繁搜尋,當然就需要更複雜的 Analyzer。
請找個地方填入以下的 regex 跟 Analyzer:
GENERIC_ANALYSIS_REGEX = /([a-zA-Z]|[\\xc0-\xdf][\\x80-\\xbf])+|[0-9]+|[\\xe0-\\xef][\\x80-\\xbf][\\x80-\\xbf]/
GENERIC_ANALYZER = Ferret::Analysis::RegExpAnalyzer.new(GENERIC_ANALYSIS_REGEX, true)
然後在想要加入搜尋的 model 裡加入:
acts_as_ferret({:fields => [ FIELDS_YOU_WANT_TO_INDEX ] }, { :analyzer => GENERIC_ANALYZER })
之所以不把 GENERIC_ANALYZER 放在 acts_as_ferret 裡,除了可重用性的原因,另外還避免掉在 Mongrel + Rails development mode 時可能造成的 bus error / segmentation fault (原因不明)。
總之,只要做完這件事,就可以:
Model.find_by_contents("hola")
acts_as_ferret很聰明,如果是第一次使用,會幫你把這個data model所用table全部讀一遍,建立必要的全文索引。之後所有的 CRUD 動作只要透過這個 model ,「化身為雪貂」會幫你做完所有該做的 Ferret indexing 動作。
先前有一些中文論壇提到用 /./ 來處理中文斷字(說「斷詞」或「分詞」有誤導之嫌)。雖然 Ruby 的 regex engine 在 $KCODE 設為 utf-8 時,可以正確地以 /./ 來掃描 Unicode 字元,但是這樣的作法是有問題的。英文詞因此會被斷成一個字元一個字元。而,單純用 [a-zA-Z] 則忽略了歐語,這是不夠的。
偏偏 Ruby 的 Unicode 支援只做了一半,不像 Perl 可以用 /\x{80}-\x{7ff}/ 的方式來表達 Unicode range,所以我們得祭出 jcode.rb 裡處理 UTF-8 的 regex (也就是利用 UTF-8 的特性),來找出實際上為 U+80 ~ U+7FF 以及 U+800 ~ U+FFFF 的字元。當然,> U+FFFF 的字元這裡並沒有處理,而且這個方式其實過於簡化。
但總之這是可以用的方法。如果要測試或改善 regex ,可以使用Ferret一書中第65頁所列的以下方法來測試:
def test_token_stream(token_stream)
puts "Start | End | PosInc | Text"
while t = token_stream.next
puts "%5d |%4d |%5d | %s" % [t.start, t.end, t.pos_inc, t.text]
end
end
然後在irb中:
str = "Café Österreich 是一間開在仮想現実空間(サイバースペース)裡的咖啡店"
test_token_stream(Ferret::Analysis::RegExpTokenizer.new(str, GENERIC_ANALYSIS_REGEX))
就可以看到 RegExpTokenizer 執行的效果。
lukhnos :: May.17.2007 ::
tekhnologia 技術或者藝術 ::
5 Comments »