英辞郎 - コマンドラインから和英/英和単語翻訳
英辞郎というサイトがあります。ALCという会社のサイト上で、フォームから和英/英和の単語翻訳ができる優れものです。技術用語なども豊富なのでなかなか使い勝手があります。
これを、シェル上から使えるようにしたい、というのが今回の企画です。職場 で英語の技術書を読んでいるとき、またドキュメントを英語でかく必要がでて きたとき、いちいち重いブラウザを使うことなく (w3m や lynx を常用してい るひとならそれでも構いませんが)、コマンドラインからちょっとした単語の 訳が出せると、非常に便利だと思うのです。
インターフェースとしては、nslookup コマンドのように、
$ eijiro.pl foo $ eijiro.pl ほげほげ $ eijiro.pl eijjiro> foo 引数で渡すものと、インタラクティブなシェルのものの双方をサポートしよう と思います。
ブラウザのフォームからCGIをたたく部分を実装するには、Perl の libwww-perl パッケージを使うと簡単です。LWP は、Gisle Aas さんによって メンテナンスされており、HTTP 通信まわりを、すっきりとしたクラス階層で 実装しています。
簡単な使い方として、LWP::Simple というクラスが提供されています。単純に
あるURLからコンテンツやヘッダ情報を取得したり、ローカルディスクに保存
したりするのであれば、この LWP::Simple モジュールによって提供される関 数
get(), head(), mirror()
などを使って簡単に実装することができます。
たとえば、ある URL に対してGETリクエストを投げ、HTMLを取得して表示する スクリプトは以下のようになります。
1 #!/usr/local/bin/perl
2
3 use strict;
4 use LWP::Simple;
5
6 print get('http://www.asahi.com/');
簡単ですね。
単純に GET|HEAD を投げるだけでなく、パラメータも投げる、Cookie も受け 渡す、プロキシを通す、Basic 認証を行う、となると LWP::Simple では厳し くなってきます。この場合、LWP::UserAgent クラスを使って実装することが できます。
あるサイトに対して Basic 認証を行い、正しく反応がかえってきた場合に、 その内容を表示するスクリプトは以下のようになります。
1 #!/usr/local/bin/perl
2
3 use strict;
4 use LWP::UserAgent;
5
6 my $url = 'http://www.some.domain/auth_required/';
7 my $username = 'user';
8 my $password = 'XXXX';
9
10 my $ua = LWP::UserAgent->new;
11 my $request = HTTP::Request->new(GET => $url);
12 $request->authorization_basic($username, $password);
13 my HTTP::Response $response = $ua->request($request);
14
15 if ($response->is_success) {
16 print $response->as_string;
17 }
このように、LWP::UserAgent の request() メソッドに
HTTP::Request クラ スを渡すことにより、HTTP::Response
クラスが返ってくるという仕様になっ ています。HTTP::Request, HTTP::Response
は双方とも HTTP::Message のサ ブクラスとして実装されています。詳細は
perldoc lwpcook をみてください。
和英翻訳の場合、日本語をフォームに入力することになりますが、HTTPリクエ ストにそのまま日本語を渡すのではなく、ブラウザが非ASCII文字を URI-escape という方式でエンコードして渡しています。
URI エスケープの仕様は非常に簡単です。ある文字列 $str
をエンコードする には、以下のようにします。
$str =~ s/(\W)/'%' . unpack('H2', $1)/eg;
sprintf() と ord()
を使うなどいろいろな方法がありますが、モジュールを 使うのであれば
URI::Escape が用意されています。使い方は以下の通りです。
1 #!/usr/local/bin/perl
2
3 use strict;
4 use URI::Escape;
5
6 my $query = 'エスケープ=escape';
7 print uri_escape($query, '\W');
ここで uri_escape() の第2引数に \W を渡しているところがポイントです。 uri_escape
関数は、デフォルトでは「URIに使ってはいけない文字」を escape
します。今回、CGIに入力した値のエスケープを考えていますから、こ
れではまずいのです。=, & といった文字は、URI としては使用可能で すが、CGI
のパラメータとして渡す場合、= や & は特殊な意味を持ち ます。ですから、[a-zA-Z0-9_] でない文字はすべて escape しなければなりま せん。
以上の2つで、引数から翻訳結果を表示するものはできそうです。インタラク ティブにそれ自身、シェルとして実行するものはどうすればよいでしょうか。
CPAN モジュールをインストールするときに、
perl -MCPAN -e shell
で立ち上がる CPAN シェル、これのシェル部分を実装している Term::ReadLine が使えます。実際には Term::ReadLine::Perl, Term::ReadLine::Gnu のどちらかを利用するのですが、これによって Ctrl-P でヒストリをさかのぼったり、といったことが簡単にできるようになります。
準備は整いました。あとはユーザの入力を受け取って、HTTPリクエストを生成 し eijiro に問い合わせ、結果を表示すればよいことになります。
細かい実装として以下のところを工夫しています。
まず和英/英和モードの切り替えは、正規表現によって自動で行います。また、 返ってくる HTML を、シェルで見やすくするためにはレンダリングエンジンが 必要になってきますが、ここでは HTML::TreeBuilder, HTML::FormatText を 使用しています。
インタラクティブシェルの場合には、終了して次に立ち上げた際にヒストリ補 完できるよう、ヒストリファイルに記録する、といったこともしています。イ ンタラクティブモードは Ctrl-D もしくは !exit という入力で終了するよう になっています。
1 #!/usr/local/bin/perl
2 # eijiro.pl - http://www.alc.co.jp/eijiro/
3
4 use strict;
5 use Jcode;
6 use LWP::Simple;
7 use FileHandle;
8 use URI::Escape;
9 use HTML::FormatText;
10 use HTML::TreeBuilder;
11 use Term::ReadLine;
12
13 my $historyfile = $ENV{HOME} . '/.eijirohistory';
14 my $pager = $ENV{PAGER} || 'less';
15
16 my $action = 'http://www.alc.co.jp/eijiro351.php3';
17
18 # Terminal mode / Argv mode
19 if (@ARGV) {
20 my $line = join ' ', @ARGV;
21 translate($line);
22 } else {
23 my $term = Term::ReadLine->new('Eijiro');
24 # read history
25 if (my $fh = FileHandle->new($historyfile)) {
26 my @h = $fh->getlines;
27 chomp @h;
28 $fh->close;
29 my %seen;
30 $term->addhistory($_) foreach (grep { /\S/ && !$seen{$_}++ } @h);
31 }
32 # readline & translate
33 while ( defined ($_ = $term->readline('eijiro> ')) ) {
34 exit if /^!exit/;
35 translate($_);
36 # Add history
37 {
38 my $fh = FileHandle->new(">>$historyfile") or die $!;
39 $fh->print("$_\n");
40 $fh->close;
41 }
42 $term->addhistory($_) if /\S/;
43 }
44 }
45
46 sub translate {
47 my $line = shift or return;
48
49 # ej or je
50 my $type_in = $line =~ /^[\x00-\x7f]*$/ ? 'ej' : 'je';
51
52 # URI-Escape
53 my $word_in = Jcode->new($line)->sjis;
54 $word_in = uri_escape($word_in, '\W');
55
56 # get Simply
57 my $url = sprintf '%s?word_in=%s&type_in=%s', $action, $word_in, $type_in;
58 my $content = LWP::Simple::get($url) or die $!;
59
60 my $parser = new HTML::TreeBuilder;
61 my $html = $parser->parse(Jcode->new($content)->euc);
62 my $format = new HTML::FormatText(leftmargin=>0);
63
64 my $p = new FileHandle "| $pager";
65 $p->print($format->format($html));
66 $p->close;
67 }
http://www.alc.co.jp/ http://member.nifty.ne.jp/eijiro/index.html
Tatsuhiko Miyagawa <miyagawa@bulknews.net>