Emacs + sdicから英辞郎v135を使えるようにするまでの顛末
前から買おう買おうと思ってましたが、仕事も辞めることですし、1980円で買えたので(データのみ)、
こいつをsdic経由でEmacsから利用できるようにしてみたいと思います。
なお、同じようなことは昔から様々な方が書いており、
などなど、探せば色々出てきます。
ただ、私のようにデータだけ購入した人間はそういないらしいので、出来るようになるまでの顛末と共に記録しておきたいと思います。
今回私が購入したのはEDPのVersion.135 というものです。
英辞郎の書籍はCD-ROMだそうですが、こいつは二つの形式が入っています。
- PDIC/Unicode形式
- CD-ROMでの検索に利用するための形式・・・だそうです。ぶっちゃけ次のテキスト形式が大事です。
- テキスト形式
- 上のPDIC/Unicode形式をPDIC一行形式?に変換したものです。ちなみにVer.135の時点では全部合わせて502MBありました。
Webの情報を見ると、ほとんどの方がまずPDICを変換するところからスタートしているようですが、今回はすでに手元にテキストデータがあるので
これをsdic形式に変換していくことにします。
ただし、このテキストデータは他で変換しているような、PDIC1行テキスト形式とかいうものではなく、次のようなフォーマット
になっています。この形式は、sdicに添付されているeijirou.perlの中身から読み取らせて頂きました。
全体は、一行につきひとつの語と訳語のセットが入れられた形式。 1. 先頭に全角記号■ 2. 単語/例文と訳語は:(コロン)で区切られる 3. 単語と:の間で{}で区切られているのは品詞 4. 訳語側の{}は漢字のルビ 5. 訳語側の()は、日本語に対応する英語 6. 訳語側の◆は、その前の文字列に対する補足情報(参考、簡単な解説など) 7. 複数の訳語がある場合は●で区切る 8. ;についてがよくわかりませんでした(ぉぃ
こんな形式になっているようです。実際のデータを眺めてみても、この形式であるのはほぼ間違いないようでした。
さて、普通はじゃあ変換するかー、ということになるのでしょうが、ここで回り道(笑)をすることにしました。せっかく今現在OCamlを
勉強しているのですから、それで書こうではないか、となりました。せっかく仕様も(大体)わかったことですし。
とりあえず↓のようなOCamlを書いてみました。いや、Rubyとかの方が色々と楽なのはわかっているのですが・・・。
let waei = ref false (* regexps *) let reg_newline = ref (Str.regexp "\\(\n\\|\r\n\\|\r\\)$") let reg_amp = ref (Str.regexp "&") let reg_gt = ref (Str.regexp ">") let reg_lt = ref (Str.regexp "<") let reg_rectangle = ref (Str.regexp "^■") let reg_split = ref (Str.regexp " +: +") let reg_translation = ref (Str.regexp " +{[^}]+}") let reg_ruby = ref (Str.regexp " +([a-zA-Z0-9]+)") let reg_truncate = ref (Str.regexp "[\t ]+") let reg_diamond = ref (Str.regexp "^\\(.*?\\)◆.*$") let reg_multi = ref (Str.regexp "^\\(.*?\\)●") (* helper functions *) let replace_newline = Str.global_replace !reg_newline "" let replace_amp = Str.global_replace !reg_amp "&" let replace_gt = Str.global_replace !reg_gt ">" let replace_lt = Str.global_replace !reg_lt "<" let replace_rectangle = Str.replace_first !reg_rectangle "" let split_colon = Str.split !reg_split let delete_translation = Str.global_replace !reg_translation "" let delete_ruby = Str.global_replace !reg_ruby "" let space_truncate = Str.global_replace !reg_truncate " " let delete_diamond = Str.global_replace !reg_diamond "\1" let replace_multi = Str.replace_first !reg_multi "\1 / " let has_multi s = Str.string_match !reg_multi s 0 (* convert eijiro format into sdic format per line *) let convert_line line = let rec all_replace_multi content = if has_multi content then all_replace_multi (replace_multi content) else content in let struct_content head key content = if key = head then "<K>" ^ key ^ "</K>" ^ content else "<H>" ^ head ^ "</H>" ^ "<K>" ^ key ^ "</K>" ^ content in let line = replace_newline line in let line = replace_amp line in let line = replace_gt line in let line = replace_lt line in let line = replace_rectangle line in let splitted = split_colon line in let (head, content) = (List.nth splitted 0, List.nth splitted 1) in let key = String.copy head in let key = delete_translation key in let key = delete_ruby key in let key = space_truncate key in if !waei then let content = all_replace_multi content in let key = delete_diamond key in struct_content head key content else struct_content head key content let convert_eijiro_to_sdic _ = let rec inner_convert_line _ = begin let unconvert_line = input_line stdin in let converted_line = convert_line unconvert_line in (* converted_line don't have newline *) print_endline converted_line; inner_convert_line (); end in try inner_convert_line () with End_of_file -> () (* this tool only accept stdin and utf-8 string, and newline as Unix(\n). *) let _ = let argv = List.tl (Array.to_list Sys.argv) in if List.exists (fun s -> s = "--waei") argv then waei := true; convert_eijiro_to_sdic ()
・・・長すぎたので反省します。正規表現リテラルがある言語とそうでない言語の差だと思いますが。
ちなみに、これを154MBの英和テキストに対して、以下のようにして実行してみると、大体以下の時間で変換が終わりました。
nkfをかましているのは、テキストファイルがおそらくShift-JISでOCamlがUTF-8なので、それを合わせるためです。
% ocamlfind ocamlopt -package str -c eijiro2sdic.ml % ocamlfind ocamlopt -package str -o eijiro2sdic eijiro2sdic.cmx % time cat EIJI-135.TXT | nkf -w80 | eijiro2sdic > eijiro.sdic cat EIJI-135.TXT 0.01s user 0.36s system 2% cpu 18.319 total nkf -w80 16.89s user 0.11s system 92% cpu 18.328 total eijiro2sdic > eijiro.sdic 12.00s user 3.70s system 85% cpu 18.335 total % time cat EIJI-135.TXT | nkf -w80 | eijiro2sdic --waei > eijiro.sdic
ということで、30秒もかからずに変換できました。全部変換しても5分とかかりません。これは予想以上に変換が速かったのでいい意味で驚きました。いや、Rubyとかと比較していないからあれですが。
ちなみに一番サイズがでかいのは和英テキストで、大体230Mくらいあります。ただしこれは単体の話で、例示とかを含めると英和の方が大きくなります。今回は英和と和英のみ変換してます。
さて、これでsdic形式に変換することができましたが、他の方も書いていますが、サイズがサイズなので、とてもじゃありませんが普通に使うと遅いです。
ですので、suffix array形式に変換してやりましょう。
以前はsufaryというのを利用するようでしたが、これはどうやらすでに開発が止まっている?ようなので、これの後継であるsaryを使うことにします。Gentooにこっちの方しかパッケージなかったし。
saryへの変換については、OSSはアルミニウムの翼で飛ぶ Emacs 英辞郎 + sdic + sary の方で紹介されています。
saryの導入からの流れは以下の通りです。変換したものをそれぞれ eijiro.sdic, waeijiro.sdicとしてあるとします。
% ls -l -rw-r--r-- 1 derui derui 161184928 9月 5 08:04 EIJI-135.TXT -rw-r--r-- 1 derui derui 117719636 9月 6 16:45 REIJI135.TXT -rw-r--r-- 1 derui derui 3824193 9月 5 08:04 RYAKU135.TXT -rw-r--r-- 1 derui derui 242712112 9月 8 08:58 WAEI-135.TXT -rw-r--r-- 1 derui derui 217177218 9月 23 19:14 eijiro.sdic -rw-r--r-- 1 derui derui 308097921 9月 23 19:13 waeijiro.sdic % sudo emerge sary % mksary -c UTF-8 eijiro.sdic % mksary -c UTF-8 waeijiro.sdic % ls -l -rw-r--r-- 1 derui derui 161184928 9月 5 08:04 EIJI-135.TXT -rw-r--r-- 1 derui derui 117719636 9月 6 16:45 REIJI135.TXT -rw-r--r-- 1 derui derui 3824193 9月 5 08:04 RYAKU135.TXT -rw-r--r-- 1 derui derui 242712112 9月 8 08:58 WAEI-135.TXT -rw-r--r-- 1 derui derui 217177218 9月 23 19:14 eijiro.sdic -rw-r--r-- 1 derui derui 508658844 9月 23 19:18 eijiro.sdic.ary -rw-r--r-- 1 derui derui 308097921 9月 23 19:13 waeijiro.sdic -rw-r--r-- 1 derui derui 800311576 9月 23 19:25 waeijiro.sdic.ary %
変換にはそれなりの時間がかかります。それと、見てわかるように、サイズがかなりの勢いで増えます。まぁ、今時のTB単位なら屁の河童と思います。ただまぁサイズ的にもそれ以外の意味でも、Dropboxで共有するとかはやめておいた方が無難でしょう。
なにはともあれ、これで無事に変換できましたので、Emacsから使えるようにしていきます。
といっても、すでに先人が達成されていることではありますので、先程のサイトを参考に以下のようなものをinit.elでもinit.dのファイルでもいいので追記します。
キー設定はあえて書きませんので、C-c wでも何でも使いやすいようにしてください。
;; --------------------------------------------------- ;; sdic ;; --------------------------------------------------- (eval-after-load "sdic" '(progn (setq sdicf-array-command "/usr/bin/sary") ; コマンドパス (setq sdic-eiwa-dictionary-list '((sdicf-client "path/to/eijiro.sdic" (strategy array))) sdic-waei-dictionary-list '((sdicf-client "path/to/waeijiro.sdic" (strategy array)))) ;; saryを直接使用できるように sdicf.el 内に定義されているarrayコマンド用関数を強制的に置換 (fset 'sdicf-array-init 'sdicf-common-init) (fset 'sdicf-array-quit 'sdicf-common-quit) (fset 'sdicf-array-search (lambda (sdic pattern &optional case regexp) (sdicf-array-init sdic) (if regexp (signal 'sdicf-invalid-method '(regexp)) (save-excursion (set-buffer (sdicf-get-buffer sdic)) (delete-region (point-min) (point-max)) (apply 'sdicf-call-process sdicf-array-command (sdicf-get-coding-system sdic) nil t nil (if case (list "-i" pattern (sdicf-get-filename sdic)) (list pattern (sdicf-get-filename sdic)))) (goto-char (point-min)) (let (entries) (while (not (eobp)) (sdicf-search-internal)) (nreverse entries)))))) (defadvice sdic-forward-item (after sdic-forward-item-always-top activate) (recenter 0)) (defadvice sdic-backward-item (after sdic-backward-item-always-top activate) (recenter 0)))) (setq sdic-default-coding-system 'utf-8-unix)
これで使えるようになります。saryを使っているおかげで検索速度は非常に速く、私の環境ではほとんど待ち時間はありませんでした。
一番時間がかかったのが、OCamlを書く時間だったというのは反省すべき点ですが、これで一々Firefoxで英辞郎を引かなくてよくなりました。結局メインが英辞郎の紹介よりもそっちの方がメインのような。まぁいいか。