Firefox/Apache用AtomリーダーCGIの作成
さて、ようやく本論に入りましょう。今回は、「feeds.cgi」と「atom2html.cgi」の2つのスクリプトを作成します。「feeds.cgi」は、Firefoxのブックマークから特定のパターンを持つ名前のAtomフィードのURLを抽出して、リストアップして、HTML出力します。その際、Atomフィード名に、Atomフィードを解析し、内容をHTML表示する「atom2html.cgi」にURLをパラメータとして渡すハイパーリンクを作成します。
各スクリプトを理解するために必要な知識については、それぞれ小見出しを作って説明します。CGIスクリプトそのものは、手続き型の単純なものですから、Perlの基礎的な知識があれば、最初から順に読んでいくと理解はそれほど難しくないと思います。理解を助けるために、できるだけコメントを付けておきます。
FirefoxのブックマークをAtomフィードの表示に利用する「feeds.cgi」の作成
Atomフィードの収集と管理
Firefoxのツールバーから、[ブックマーク]→[ブックマークの管理]→[新しいフォルダ]を使用して、あらかじめ、ブックマークに「ATOM」というフォルダを作成しておきます。Webページ上のAtomフィードのリンクにマウスのカレットを合わせ、右クリックで「このリンクをブックマーク」します。ポップアップする「ブックマークを追加」用ダイアログの「名前」に、適当なタイトルが入っていない場合には、判別できるようなタイトルを入れ、「作成先」には「ATOM」のフォルダを選びます。この繰り返しで、Atomフィードの収集ができますから、テスト用にお気に入りのサイトでAtomフィードを収集してみてください。
Firefoxのブックマーク
Firefoxのブックマークは、「bookmarks.html」というHTMLファイルです。「<USERPROFILE>\Application Data\Mozilla\Firefox\Profiles\<12ab3456>.default」というフォルダに入っています。<USERPROFILE>の部分には、環境変数USERPROFILEが入ります。通常、「C:\Documents and Settings\<USERNAME>」のような値になります。環境変数USERNAMEにはWindowsにログインするときの使用者名が入ります。使用者名に漢字を使っていると、文字コードがShiftJISになっていることを覚えておきましょう。
「bookmarks.html」は文字コードがUTF-8N(BOMなし)のテキストファイルになっています。TeraPadなどのUTF-8N対応のテキストエディタを使って中味を見てみましょう。次に、著者の「bookmarks.html」のATOMフォルダの部分の一部を示しておきます。「feeds.cgi」では、この部分をPerlの正規表現によるパターンマッチングで読み取ります。
<DT><H3 ADD_DATE="1136649204" LAST_MODIFIED="1144450656" ID="rdf:#$kfg8O1">ATOM</H3> <DL><p> <DT><A HREF="http://www.oreillynet.com/pub/feed/20" ADD_DATE="1136796552" ID="rdf:#$Zw3WM1">XML.com Atom 1.0 Feed</A> <DT><A HREF="http://po3a.blogspot.com/atom.xml" ADD_DATE="1140925486" ID="rdf:#$9yYbY3">SATOLOG</A> <DT><A HREF="http://copia.ogbuji.net/blog/index.atom" ADD_DATE="1141385414" ID="rdf:#$BMXXz"> Copia Ogbujis on just about everything</A> <DT><A HREF="http://jtauber.com/atom/full/" ADD_DATE="1143371932" ID="rdf:#$KVaJA2">James Tauber - journeyman of some</A> </DL><p>
ブックマークのATOMフォルダは、DTタグの後にH3タグで囲んで示されています。ブックマークはキーワードリストを表すDLタグで囲まれていて、キーワード要素を示すDTタグの後にH3タグで囲んでフォルダを示しています。ATOMフォルダ行の後には、DLタグが入れ子で現れ、ブックマーク・フォルダの階層構造を構成しています。ATOMフォルダのDLタグに囲まれたDTタグの各行はそれぞれ、AタグのHREF属性にブックマークのURLを置き、ブックーマーク名をAタグで囲んで、ハイパーリンクを示しています。
feeds.cgi
CGIモジュール
拙著『実践実用Perl』では、CGIのパラメータの解析には、「cgi-lib.pl」を使用していました。ShiftJIS文字コード対応のjperlはCGIモジュールと相性がよくなかったからです。Perl 5.8をUTF-8文字コード、バイト指向で使えば、CGIモジュールも特に問題はなさそうなので、CGIパラメータの値を取得するparam()メソッドを使うために、:cgiの機能セットをインポートして使うことにしました。
param('parameter_name')で、parameter_nameというパラメータの値を取得できます。「feeds.cgi」では、patternというパラメータを取得し、検索パターン文字列を表す変数$patternに代入しています。このパラメータが未定義の場合には、「.」を代入します。「.」の正規表現は、通常、改行文字以外の文字一文字にマッチします。
「bookmarks.html」のパスの設定
Firefoxのブックマークファイル、「bookmarks.html」の入っているフォルダ名を設定する必要があります。スクリプトのコメントに従って変更してください。
環境変数の取得とブックマークファイルの読み込み
特殊連想配列の$ENV{'env'}によって、環境変数名envの環境変数値を得ることができます。%ENVで取得する文字列はバイト指向で取り扱われます。
$ENV{'USERDIR'}はFirefoxのブックマークファイルのフルパスを得るために使いますが、フォルダ名に漢字などのShiftJIS文字を使っていても、ブックマークファイルをopenする場合に支障はありません。Perlは、%ENVやopenなどの、OSやファイルシステムに依存するインターフェイス部分では、引数と得られる結果の両方についてバイト列を仮定するからです。
詳細については、Perlのコア文書である『perlunicode - Unicode support in Perl』の『When Unicode Does Not Happen』の項を参照してください。ActivePerl User Guideに含まれています。日本語で読みたい場合には、木村浩一氏のスクリプティング言語資料室(仮)の翻訳を参照してください(perlunicode - PerlにおけるUnicodeサポート)。このサイトには、Encode、encoding、Unicode、正規表現、FAQ関連の翻訳があり、Perlを学習する場合に大変参考になります。
CGI出力
CGI出力の最初の部分は、ヒアドキュメントを使用しています。注意点は、リクエストヘッダフィールド行です。通常は、「Content-Type: text/html\n\n」を出力すればよいのですが、セミコロンで区切って「charset=UTF-8」を加えています。これは、Apacheの文字コードのデフォルト設定が他の文字コードになっている場合に必要です(本稿の設定では、jperlユーザーのためにShiftJISにしています)。これがないと、ブラウザで表示したときに文字エンコーディングが適切に設定されなくて文字化けしてしまいます。
Content-Type: text/html; charset=UTF-8
ブックマークごとに出力するHTML部分は次のように、Feeds検索パターン$patternにマッチするブックマークのマッチ回数を表すNo.、ブックマークタイトルとブックマークのURLを表形式で出力します。ブックマークタイトルには、「atom2html.cgi」を起動するリンクがあります。 URL文字列には、URLそのものをリンクしています。
# フォント色と表の背景色の設定 print "<font color=\"#000000\"><tr bgcolor=\"#eeeeee\">"; print "<td>$num</td>";# マッチ回数 # ブックマークタイトルに「atom2html.cgi」をリンクする print "<td><a href=\"$cgidir/atom2html.cgi?atom=$url_encoding\">$bookmark</a></td>"; # ブックマークのURL文字列にブックマークのURLをリンクする。 print "<td><a href=\"$url\">$url</a></td></tr></font>\n";
ブックマークファイルの解析とデータの抽出
ブックマークの解析とデータの抽出は、ファイルを行単位で読み込むwhile(<IN>){...}ループの中で、if ... elsif ... else ...の構文による正規表現によるパターンマッチの組み合わせを使用して行います。スクリプトの次の部分が、ブックマークファイルのATOMフォルダ行を検出するパターンマッチです。正規表現の詳細については、前出の木村氏のサイトにあるperlreの翻訳を参考にしてください。参考資料にリストアップしています。Perl 5.005用のものですが、通常使用する正規表現はすべて含まれています。
if(/^\s+<DT><H3[^>]+>ATOM<\/H3>$/){ $insw = 1;# ATOMフォルダの検出 }
ここで、$insw = 1としているのは、読み込んだ行がATOMフォルダに入ったことをスクリプトに知らせるためで、次のif文のATOMフォルダのブックマーク行の検出部分に使っています。正規表現のパターンマッチだけでは、ATOMフォルダのブックマーク行かどうかが分からないので、$insw == 1という条件を加えることで判断します。
if($insw == 1 && $_ =~ /^\s+<DT><A HREF="(.*?)".*?>(.*?)<\/A>$/i){ $url = $1;# ブックマークのハイパーリンクから、URLを取得 $bookmark = $2;# ブックマークのタイトルを取得
次の部分が、ATOMフォルダの終了を判定するif文です。ATOMブックマークフォルダのブックマーク行を囲んでいるDLタグの終了タグにマッチしたところで、whileループを抜けます。
if($insw == 1 && $_ =~ /^\s+<\/DL>/i){ last;# ATOMフォルダ終了判定で、whileループを抜ける }
正規表現によるパターンマッチングを使ったテキストの解析は、以上のようなテクニックの組み合わせから成り立っています。理解を深めるためには、さまざまな事例に応用して、テキスト処理のスクリプトを実際に作ってみることが大変重要です。
URLエンコーデイング
Webブラウザ上のform入力によるデータなどは、ブラウザがURLエンコーディングしてくれますが、CGIスクリプトで、パラメータを持つ他のCGIへのURLを出力する場合は、URLエンコーデイングしたパラメータ値を渡す必要があります。URLエンコーディングには、URI::Escapeモジュールのuri_escape()を使用しています。
「feeds.cgi」の出力画面とスクリプト
以下に、CGIのFirefoxの出力画面とスクリプトを示します。
#!/Perl5.8/bin/perl.exe use strict; use warnings; use URI::Escape; use CGI qw(:cgi); my $pattern; my $title; if(param('pattern')){ $pattern = param('pattern');# CGIからのパラメータ入力 $title = "Feeds検索パターン: $pattern"; }else{ $pattern = ".";# コマンドラインのデバッグ用、すべてにマッチする $title = "Feedsリスト"; } # 環境変数の取得 # bookmarks.htmlのパスの設定(ユーザー設定が必要) my $bookmarks = $ENV{'USERDIR'}."\\Application Data\\Mozilla\\Firefox\\Profiles\\"; $bookmarks .= "12ab3456.default";# 12ab3456 の部分を書き直す $bookmarks .= "\\bookmarks.html"; my $cgidir = $ENV{'CGIDIR'};# ローカル HTTP サーバーの CGI 相対ディレクトリ print <<HTML; Content-type: text/html; charset=UTF-8 <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>$title</title> </head> <body> <h3>$title</h3> <table> <tr bgcolor="#eeeeee"> <th>No.</th> <th>ブックマーク</th> <th>URL</th> </tr> HTML my $insw = 0; my $url; my $bookmark; my $num; my $url_encoding; open(IN, "<$bookmarks"); while(<IN>){ if(/^\s+<DT><H3[^>]+>ATOM<\/H3>$/){ $insw = 1; }elsif($insw == 1 && $_ =~ /^\s+<\/DL>/i){ last; } if($insw == 1 && $_ =~ /^\s+<DT><A HREF="(.*?)".*?>(.*?)<\/A>$/i){ $url = $1; $bookmark = $2; if($bookmark =~ /$pattern/o){ $num++; $url_encoding = uri_escape($url); print "<font color=\"#000000\"><tr bgcolor=\"#eeeeee\"><td>$num</td>"; print "<td><a href=\"$cgidir/atom2html.cgi?atom=$url_encoding\">
$bookmark</a></td>"; print "<td><a href=\"$url\">$url</a></td></tr></font>\n"; } } } close(IN); print "</table><hr></body></html>\n";

$bookmark