Atomフィードリーダー - atom2msrdb_read_js.cgi
動作の概要と外観
図2は、著者のサイトのAtomフィードデータをAtomフィードデータベースから読み出して表示したFirefoxの画面です。データベースから読み出されたカテゴリとタイトルと更新日付がtext型の入力フォームで表示され、記事本体はIFRAMEで表示しており、中身はentry主要素のlink要素に設定されているURLで参照先される文章そのものとなっています。また、[PREV]ボタン(戻る)、[NEXT]ボタン(次へ)で時系列で記事の表示を切り替えることができます。記事は時系列降順で表示されます。
スクリプト
連載第5回の「atom2msrdb_read.cgi」と比べて、スクリプトの新しい構成要素は以下のようになっています。
CGIパラメータ
パラメータとして、AtomフィードのURLを受け取り動作します。コマンドラインの最初の引数からでもパラメータの受け取りができます。また、入力がない場合は、著者のサイトのAtomフィードを使います。従って、単にブラウザで本CGIを実行すると著者のサイトを表示します。
SQLデータベースへの問い合わせ
「feeds」テーブルへのクエリ部分は簡単なので省略します。「entries」テーブルへのSQLクエリ部分を次に示します。ORDER BY updated DESC
によりカラム名降順で結果を取得します。
SELECT atom, issued, published, link, title, content, name, modified, created, id, updated, summary, category FROM entries WHERE atom = \"$atomurl\" ORDER BY updated DESC
JavaScript
ソースの「### JavaScript部分のCGI出力生成」と「# CGI 最後の部分の出力」の部分を参照してください。
Atomフィードデータベースから読み取った、title
要素、category
要素、updated
要素、link
要素をJavaScriptの配列として出力します。それぞれtitles
、categories
、updateds
、links
配列となります。
link
要素のURLが示すページへジャンプしてIFRAME(self.frames['r1']
オブジェクトで示される)に表示させるためには、location.href
プロパティを用います。JavaScript関数のスクリプトがどのIFRAMEやFORMのどの部分で動作するかは、IFRAME
タグやFORM
タグのname
属性で結び付けられています。
#!/Perl5.8/bin/perl.exe use strict; use warnings; use DBI; use CGI qw(:cgi); my $atomurl = ""; # Atom URL データの取得 if(param('atom')){ # CGI で URL を取得する $atomurl = param('atom'); }elsif($ARGV[0]){ # コマンドラインから URL を取得する $atomurl = $ARGV[0]; }else{ $atomurl = "http://homepage1.nifty.com/kazuf/renewal_atom.xml"; # print "Atom URL データが存在しない。\n";exit;# 終了する } # CGI 最初の部分の出力 print <<HEADER; Content-type: text/html; charset=UTF-8 <html> <head> <title>ATOM DB Reader</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <link rel="stylesheet" type="text/css" href="/mystyle.css" /> <script language="Javascript"> <!-- if(navigator.appCodeName.indexOf("Mozilla") != -1){ document.write( "<link rel='stylesheet' type='text/css' href='/firefox.css' />"); } //--> </script> </head> <body onLoad="msg(0);"> <div class="emph">Atom DB Reader</div> <script> HEADER # Atomフィードデータベース(atomfeeds)へのアクセス my $db = DBI->connect( "DBI:mysql:database=atomfeeds;host=localhost;port=3306", "root", "password", {'RaiseError' => 1}); ## feedsテーブルへのアクセス my $sth = $dbh->prepare(qq{ SELECT atom,link,ver,title,tagline,modified,info, logo,icon,subtitle,updated FROM feeds WHERE atom = \"$atomurl\" }); $sth->execute(); my $feed; while(my $row = $sth->fetchrow_hashref()){ $feed .= "<p><a href=\"$row->{link}\">$row->{title}</a></p><p>"; if($row->{logo}){ $feed .= "<img src=\"$row->{logo}\">"; }elsif($row->{icon}){ $feed .= "<img src=\"$row->{icon}\">"; } $feed .= "$row->{subtitle}</p><p>$row->{updated}</p>"; } ## entriesテーブルへのアクセス $sth = $dbh->prepare(qq{ SELECT atom, issued, published, link, title, content, name, modified, created, id, updated, summary, category FROM entries WHERE atom = \"$atomurl\" ORDER BY updated DESC }); $sth->execute(); ### JavaScript部分のCGI出力生成 my $title = "var titles = new Array("; my $category = "var categories = new Array("; my $updated = "var updateds = new Array("; my $link = "var links = new Array("; my $count = 0; my $firstlink; my $year;my $cyear = (localtime(time))[5] + 1900; my $mon;(my $cmon = (localtime(time))[4] + 1) =~ s/^(\d)$/0$1/; while(my $row = $sth->fetchrow_hashref()){ $title .= "\"$row->{title}\","; $category .= "\"$row->{category}\","; $updated .= "\"$row->{updated}\","; $count++; if($atomurl eq 'http://homepage1.nifty.com/kazuf/renewal_atom.xml'){ if($row->{updated} =~ /^(\d{4})\-(\d{2})/){ $year = $1;$mon = $2; if($cyear != $year or $cmon != $mon){ $row->{link} =~ s/(renewal)(\.html)/$1_${year}_$mon$2/; } } } if($count == 1){ $firstlink = $row->{link}; } $link .= "\"$row->{link}\","; } chop $title;print $title . ");\n"; chop $category;print $category . ");\n"; chop $updated;print $updated . ");\n"; chop $link;print $link . ");\n"; # Atomデータベースの終了 $sth->finish(); $dbh->disconnect(); # CGI 最後の部分の出力 print <<FOOTER; var i = 0; function nextLink () { i++; if(i >= links.length) i = 0; self.frames['r1'].location.href = links[i]; msg(i); } function prevLink () { i--; if(i < 0) i = links.length - 1; self.frames['r1'].location.href = links[i]; msg(i); } function msg(i) { document.form.title.value = titles[i]; document.form.category.value = categories[i]; document.form.updated.value = updateds[i]; } //--> </script> $feed <form name="form"> <p> <input type="text" name="category" size="20"> <input type="text" name="title" size="80"> </p> <p> <input type="text" name="updated" size="40"> </p> <p> <iframe name="r1" src="$firstlink" width="95%" height="65%"> </iframe> </p> <p> <input type="button" value="PREV" onclick="prevLink ();"> <-----> <input type="button" value="NEXT" onclick="nextLink ();"> </p> </form> </body> </html> FOOTER exit(0); # マークアップ記号の実体参照デコード # 文字実体参照と数値文字参照(注1)の両方に対応 sub entities_decode{ my($str) = @_; $str =~ s/(<|<|<)/</g; $str =~ s/(>|>|>)/>/g; $str =~ s/('|'|')/'/g; $str =~ s/("|"|")/"/g; $str =~ s/(&|&|&)/&/g; return $str; }
entities_decode
のサブルーチンは、10進数で表現された数値文字参照だけでなく16進数で表されたものもデコードできるように対応しました。問題点と対策
「atom2msrdb_read_js.cgi」の検討において、さまざまなサイトのAtomフィードを表示させてみると、entry
主要素のlink
要素のURLは、必ずしもentry要素に収録された元の記事を指し示すとは限らないことが分かりました。大体、次の3通りの応答があります。
- 元記事のURL
- 該当記事の
enrty
要素のXMLを返す - 「No authentication info」のエラーを返す
期待通りに元記事のURLを示していたとしても該当記事が見やすく表示されない場合もありますので、まったく実用的とは言えません。entry
要素のlink
要素から元記事にアクセスする方法は本稿の目的に合致しないので、content
要素そのものを表示する方法を考えてみましょう。次項では、content
要素を埋め込んだHTMLを生成するCGIの出力を、IFRAMEに表示するように書き換えます。