Firefox3でSQLite3の全文検索機能を使って日本語を検索する

mal_blue@tumblrでFirefox3に載っているSQLiteに全文検索機能がついたのを知りました。昨年12月にGoogle Japan Blog: Google デベロッパー交流会 ( 第 4 回 )に参加させていただいたとき、Google Gearsの開発者がGoogle Gearsでサポートされているlocal storage(実体はsqliteのデータベースでSQLを使ってデータを読み書きできます)について非ASCIIのfull-text searchをサポートしたいと言われていたのを思い出しました。

その後どうなったかなー、と思ってちょっと検索してみたらGoogle Gearsと直接関係ないけどSQLite Full Text Search with MeCab - mynoteという記事が。この記事はSQLiteのCのAPIにはtokenizerを指定することができて、あらかじめtokenizeする必要もなく日本語で全文検索ができる、というのが趣旨ですが、tokenizeしてあげれば日本語もふつうに全文検索可能、ということならFirefoxの拡張機能からも日本語での全文検索機能が利用できるはずー、と思って試してみたらできました。

テーブルの作成

まずSQLite CVSTracをまねして、とにかくテーブルを作ります。 USING fts3とつけるのがミソ。
 var storageService = Components.classes["@mozilla.org/storage/service;1"]
    .getService(Components.interfaces.mozIStorageService);
    var Connect = storageService.openUnsharedDatabase(file);
            Connect.executeSimpleSQL(
              "CREATE VIRTUAL TABLE recipe USING fts3(name, ingredients)"
            );

これに全文検索したい日本語の文字列をあらかじめtokenizeしてからINSERTします。

tokenize

せっかくjavascriptで全文検索できるんだからtokenizeもjavascriptで完結したいところ。というわけでtokenizeにはTinySegmenter: Javascriptだけで実装されたコンパクトな分かち書きソフトウェアを使いました。テストに使う日本語の文章にはSQLite Full Text Search with MeCab 使ってみるで使われている俳句を使います。
分け入っても分け入っても青い山
大銀杏散りつくしたる大空
松はみな枝垂れて南無観世音
まったく雲がない笠をぬぎ
やつぱり一人がよろしい雑草
捨てきれない荷物のおもさまへうしろ
すべつてころんで山がひつそり
待つでも待たぬでもない雑草の月あかり
ともかくも生かされてはゐる雑草の中
この道しかない春の雪ふるころり寝ころべば青空
を入れると | で区切られてtokenizeした結果が得られます。
分け入っ | て | も | 分け入っ | て | も | 青い山 | | 大銀 | 杏散り | つく | し | たる | 大空 | | 松 | はみ | な | 枝垂 | れ | て | 南無 | 観世音 | まっ | たく | 雲 | が | ない | 笠 | を | ぬぎ | | やつぱり | 一人 | が | よろしい | 雑草 | | 捨 | て | きれ | ない | 荷物 | の | おも | さま | へうしろ | | すべつ | て | ころん | で | 山 | が | ひつそり | | 待つ | でも | 待 | た | ぬ | でも | ない | 雑草 | の | 月 | あかり | | ともかく | も | 生か | さ | れ | て | はゐる | 雑草 | の | 中 | | この | 道し | か | ない | 春 | の | 雪ふる | ころり | 寝ころべば | 青空
これをスペース区切りに書き換えて、1レコードに1俳句でSQLiteのdbに入れました。
        var uc = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
            .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
        uc.charset = 'UTF-8';
            var tokenized = document.getSelection();
            tokenized.split(/\|/).map( function (s) {
                s = s.replace(/\s/g, "");
                return s ? s : '\n'
            } ).join(" ").split(/\n/).map( function (s) {
                documentTitle =  uc.ConvertFromUnicode(documentTitle);

                console.log(s);

                s = uc.ConvertFromUnicode(s);
                var statement = Connect.createStatement("INSERT INTO recipe VALUES (?1,$2)");
                statement.bindUTF8StringParameter(0, documentTitle);
                statement.bindUTF8StringParameter(1, s);
                statement.execute();
            } );
inserted

検索する

SQLiteの全文検索にはMATCHを使います。ここだけbindUTF8StringParameter でへんな問題がありました(テストに使ったのはFirefox3beta3です)。 ほんとうは
var q = "青空";
sql = "SELECT docid,ingredients FROM recipe WHERE ingredients MATCH $1";
statement.bindUTF8StringParameter(0, q);
とすればうまく検索できるはずなのですが、bindUTF8StringParameterを使ってパラメータを与えると、パラメータがASCIIであるか日本語であるかに関わらず、全文検索が正しくできませんでした。でもSQLの中に直接このように

sql = "SELECT docid,ingredients FROM recipe WHERE ingredients MATCH '\u9752\u7A7A' "; // 青空
記述するとちゃんと検索することができました(Unicodeエスケープしているのは、今回スクリプトを実行するのに使ったJSActionsが使っているloadSubScriptに起因する文字化け回避のためです。参考: Bug 377498 – loadSubScript does not support JavaScript 1.7 and does not support specifying the character encoding)。

実行するとこのとおり、青空を含んでいるこの道しかない春の雪ふるころり寝ころべば青空がマッチします。

P2
ちゃんと全文検索になっているかどうかを確認するために空だけで検索してみます。 空という文字を含んでいる句は
  • 大銀杏散りつくしたる大空
  • この道しかない春の雪ふるころり寝ころべば青空
の二つがありますが、どちらも |大空|, |青空| でtokenizeされているので空にはマッチしないのが期待される動作です。

 sql = "SELECT docid,ingredients FROM recipe WHERE ingredients MATCH "+"'\u7A7A'"; // 空
で検索してみると
P3-2
期待通りヒットしません。

まとめ

というわけでFirefox3では拡張機能の中でSQLiteを使って日本語の全文検索を利用することができます。 Firefox3beta3では検索する文字列をSQLに入れるのに、bindUTF8StringParameterが使えず、日本語を扱うにはへんな書き方をする必要がありそうです(でも明らかにバグなので新しいバージョンでは直っているかもしれません)。

TinySegmenter: Javascriptだけで実装されたコンパクトな分かち書きソフトウェアと組み合わせて使用すれば、Firefoxのみで日本語の全文検索をすることが可能になります。

ThumbStrips :: Firefox Add-onsは見たページの全文をSQLiteに保存しておいて検索することができるextensionですが、日本語に対しては正しく機能しません。上述のように、日本語をtokenizeしてからSQLiteに保存する方法を使えば、日本語のページでも閲覧したページの全文を保存しておいてあとから検索することができます。

コード

テストに使ったコードです。
var console = {
        log: function () {
                Firebug.Console.log(arguments);
        }
};

var file = Components.classes["@mozilla.org/file/directory_service;1"]
                                .getService(Components.interfaces.nsIProperties)
        .get("ProfD", Components.interfaces.nsIFile);

        file.append("FTS3.sqlite");

        var storageService = Components.classes["@mozilla.org/storage/service;1"]
        .getService(Components.interfaces.mozIStorageService);
        var Connect = storageService.openUnsharedDatabase(file);

        if(!Connect.tableExists("recipe")){
                try {
                        Connect.executeSimpleSQL(
                          "CREATE VIRTUAL TABLE recipe USING fts3(name, ingredients)"
                        );
                }catch(e) {
                        console.log(e);
                }
        } else {
                var selectedText = (document.getSelection());
                var documentTitle = document.title;

                var uc = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
                        .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
                uc.charset = 'UTF-8';

                if ( selectedText ) {


                        var tokenized = document.getSelection();
                        tokenized.split(/\|/).map( function (s) {
                                s = s.replace(/\s/g, "");
                                return s ? s : '\n'
                        } ).join(" ").split(/\n/).map( function (s) {
                                documentTitle =  uc.ConvertFromUnicode(documentTitle);

                                console.log(s);

                                s = uc.ConvertFromUnicode(s);
                                var statement = Connect.createStatement("INSERT INTO recipe VALUES (?1,$2)");
                                statement.bindUTF8StringParameter(0, documentTitle);
                                statement.bindUTF8StringParameter(1, s);
                                statement.execute();
                        } );
                } else {
                        var sql;
                //sql = "DELETE  FROM recipe";
                //sql = "SELECT *  FROM recipe";
                        sql = "SELECT docid,ingredients FROM recipe WHERE ingredients MATCH "+"'\u9752\u7A7A'";
                        console.log(sql);
                        sql =  uc.ConvertFromUnicode(sql);
                        statement = Connect.createStatement(sql);
                        //statement.bindStringParameter(0, 'XPCOM');
                        //statement.bindUTF8StringParameter(0, q);
                        statement.execute();

                        var i = 0;
                        while ( statement.executeStep() ) {
                                var title = statement.getString(0);
                                var data = uc.ConvertToUnicode( statement.getString(1) );
                                Firebug.Console.log([i++, {text: data }]);
                        }

                        console.log("hit(s)", i);

                }

        }

tags

  • Firefox
  • extension
  • javascript
  • 「Firefox3でSQLite3の全文検索機能を使って日本語を検索する」のはてなブックマーク数
  • 「Firefox3でSQLite3の全文検索機能を使って日本語を検索する」deliciousブックマーク数
  • 「Firefox3でSQLite3の全文検索機能を使って日本語を検索する」をはてなブックマークに追加
  • save "Firefox3でSQLite3の全文検索機能を使って日本語を検索する" to del.icio.us
  • 「Firefox3でSQLite3の全文検索機能を使って日本語を検索する」をリアルタイムブログ検索
  • permalink
  • てきとうにクリックしたらてきとうにWeb::Scraperのコードを作ってくれるWebScraper IDE
  • WiiリモコンとFirefoxをjavascriptでつなげるWiiRemoCom Firefox3対応版

comments

TypeKey Enabled
スタイル用のHTMLタグが使えます。
*required

trackbacks

トラックバック元エントリにこのエントリへのリンクがない場合はトラックバックを受け付けません。

http://labs.gmo.jp/mt/mt-tb.cgi/208
©2010 Kentaro Kumagai, GMO Internet Labs., GMO Internet, inc.
bits and bytes
2008 .03. 21 17:54

tagcloud

  • API1
  • C/C++2
  • E4X1
  • FUSE2
  • Firefox22
  • HTML4
  • IE1
  • MySQL1
  • OSX4
  • Opera2
  • PHP4
  • UI1
  • XML1
  • XPCOM4
  • XPath3
  • apache2
  • binary2
  • book1
  • data12
  • debug5
  • design1
  • experiments3
  • extension13
  • google gears1
  • google maps API1
  • greasemonkey4
  • httpd5
  • javascript19
  • linux1
  • logging2
  • mobile3
  • perl4
  • tips5
  • tool11
  • vim2
  • visualization2
  • widget1
  • wii2
  • windows7
  • サービス6
  • 和訳1

Archives

  • 2008.03 (4)
  • 2008.02 (6)
  • 2008.01 (3)
  • 2007.12 (4)
  • 2007.11 (5)
  • 2007.10 (4)
  • 2007.09 (4)
  • 2007.08 (4)
  • 2007.07 (8)
  • 2007.06 (7)
  • 2007.05 (4)
  • 2007.04 (5)
  • 2007.03 (6)
  • 2007.02 (4)
  • 2007.01 (6)

about

  • bits and bytesのXML
  • 「bits and bytes」のCreative Commons
  • Powered by Movable Type
  • イベントと地図 - モグ
  • Use ecto to blog!
  • bits and bytesのはてなブックマーク数
  • bits and bytesをMy Yahoo!に追加
  • Subscribe with Bloglines