RSS feed を JavaScript で HTML に埋め込む
RSS を利用すると、自分の Blog サイトのサイドバーなどに、お気に入りの Blog サイトの更新情報などを表示することができます。このように、サイト間でのリンクの導線を動的に生成することができるのも、Blog の魅力の1つと言えます。今回は JavaScript Include と呼ばれる手法を使って、既存の Blog サイトに負荷をかけることなく、RSS を HTML に埋め込む手法を紹介します。JavaScript Include
他サイトの RSS を自分の Blog サイトに持ってくるには、通常 Blog ツールに付属するプラグインやサードパーティツール(*1) を利用します。これらのツールは定期的に RSS を GET し、静的に生成される HTML を再構築したり、静的な HTML に書き出して、出力時に Include するといった手法をとります。 しかし、こうした手法は、- Blog ツールに特化しており汎用的でない
- MT など静的ビルドする Blog ツールの場合 cron 等で rebuild する設定が必要
- blosxom など動的ビルドの場合、サーバ側で動的に出力する分の負荷がかかる
<script language="JavaScript" src="http://example.com/rss.js"></script>といった JavaScript の Include 命令を書き込み、インクルードされる .js ファイル内には
document.write
で HTML を出力する JavaScript コードを書き込んでおくと、ブラウザ上でそれらのコードが実行されてマージされ、レンダリングされるという仕組みです。
この JavaScript Include 手法を RSS Feed の HTML 埋め込みに適用すると、
- cron 等で、RSS を javascript 化しておく (
foo.js
とする) - Blog のテンプレートに
<script src=".../foo.js"></script>
を記述する
- Blog ツールや RSS とりこみプラグインを使っていない他のサイトでも、JavaScript コードを張りつけることでコンテンツのヘッドラインを取り込める
サンプルコード
今回は RSS ファイルの URL を指定すると、それを JavaScript 化するスクリプト List 1 を作ってみました。HTTP GET で RSS を取得し、XML::RSS でパースしながら JavaScript のdocument.write
によるコードに変換します。
use Digest::MD5 qw(md5_hex); use Encode; use HTML::Template; use LWP::Simple; use XML::RSS;出力する HTML のテンプレートを記述するために HTML::Template を使用しています。若干オーバースペック気味ですが、元々バッチ起動ですので問題ないでしょう。
our $CacheDir = "cache"; mkdir $CacheDir, 0755 unless -e $CacheDir;RSS ファイルをキャッシュするディレクトリを指定、存在しなければ初期化しています。
my $url = shift or die "URL needed!"; my $num = shift || 10; my $encoding = shift || "utf-8";コマンドラインの引数には RSS ファイルの URL, JavaScript に記録するエントリ数、出力する JavaScript のエンコーディングを指定します。それぞれデフォルトはエントリ数は 10, エンコーディングは UTF-8 としています。
my $digest = md5_hex($url); my $cache = "$CacheDir/$digest.xml"; my $status = LWP::Simple::mirror($url, $cache); if (is_error($status)) { die "$url not found!\n"; } else { rss2js($cache, $num, $encoding); }RSS を HTTP GET します。
mirror
を使用して無駄なトラフィックが発生しないようにしています。
sub rss2js { my($xml, $num, $encoding) = @_; my $rss = XML::RSS->new(); $rss->parsefile($xml);XML::RSS で RSS を
parsefile
します。
my @items = map { +{ title => $_->{title}, link => $_->{link} } } splice(@{$rss->items}, 0, $num);
$rss->items
のうち先頭 $num
を splice
で取り出し、map
でハッシュリファレンスを生成しています。
my $template = HTML::Template->new(filehandle => \*DATA); $template->param( title => $rss->{channel}->{title}, link => $rss->{channel}->{link}, items => \@items, );
DATA
ファイルハンドル(*3)をテンプレートとして、HTML::Template オブジェクトを生成し、RSS の channel 要素と、先ほど生成した @items
をテンプレートの変数にバインドします。
__DATA__ <div class="rssChannel"> <h3 class="rssTitle"> <a href="<TMPL_VAR name=link escape=HTML>"> <TMPL_VAR name=title escape=HTML></a></h3> <div class="rssItem"> <TMPL_LOOP name=items> <a href="<TMPL_VAR name=link escape=HTML>"> <TMPL_VAR name=title escape=HTML></a><br /> </TMPL_LOOP></div> </div>DATA テンプレートはこのようになっており、
items
で TMPL_LOOP を回して、各エントリへのリンクを作っています。デザインを CSS でカスタマイズできるよう、rssChannel
や rssTitle
, rssItem
といったクラスを設定してあります。
binmode STDOUT, ":encoding($encoding)"; js_print($template->output());
binmode
を使用して、STDOUT を $encoding
のエンコーディングに設定します。例えば $encoding = "euc-jp";
であれば、STDOUT に print すると自動的に euc-jp にエンコードされる設定となります。encoding プラグマでも同様の効果を得ることができますが、binmode
では任意のファイルハンドルに対して、実行時にエンコーディングレイヤを設定できます(*4)。
HTML::Template の output
メソッドで得られるテンプレートの出力を js_print
でエスケープして出力します。
sub js_print { my @lines = split /\n/, shift; for (@lines) { s/\x27/'/g; # ' print "document.writeln('$_');\n"; } }LF (LineFeed) で
split
して、\x27
すなわち ' (シングルクォート) を HTML 数値参照 (') に置換しながら、document.writeln()
で囲んで出力します。シングルクォートは document.writeln
のセパレータとバッティングするため、エスケープしています。他にも、s/'/\\'/g;
のようにバックスラッシュでエスケープする方法でも問題ありません。
実行例
RSS の URL とエントリ数、エンコーディングを指定してコマンドラインで実行します。% ./rss2js.pl http://blog.bulknews.net/mt/index.rdf 10 utf-8 > bulknews.js出来上がった js ファイルは List 2 のようになります。これを HTTP アクセス可能な場所に配置し、URL を
<script src="..."></script>
で指定すれば、任意の HTML ページに、blog.bulknews.net の最新10件のエントリを埋め込むことができます。
このコマンドを crontab 等で定期的に実行して js ファイルを更新してやればよいでしょう。
JavaScript Include Tips
JavaScript Include のデメリットは、lynx や w3m といった JavaScript を理解しないブラウザで表示ができないことです。また Googlebot などの検索エンジンスパイダーも JavaScript を理解しませんが、これはページの HTML に余計な要素が紛れこまないため、SEO 的によい効果をもたらすこともあります。 また今回のスクリプトでは、出力する JavaScript のエンコーディングを指定可能にしていますが、埋め込む HTML のscript
タグの charset
属性で、このエンコーディングを指定することができます。
<script language="JavaScript" src="..." charset="utf-8"></script>これを使用すれば、元の HTML ページが euc-jp、JavaScript が UTF-8 という状況でも、文字化けせずに埋め込みが可能です(*5)。
Apache::JavaScript::DocumentWrite
このように HTML を生成しておいて JavaScript Include するというのは様々な場面で使用できます。筆者の作成した CPAN モジュール Apache::JavaScript::DocumentWrite は、任意の HTML ファイルの URL 末尾 に.js
を付加することによって、コンテンツの中身を JavaScript Include でインクルード可能にする mod_perl モジュールです。
See Also
- 山下達雄 さんの くっつき RSS - 今回のネタ元です。
- XML::RSS::JavaScript - 今回のスクリプトと同様の処理を行う Perl モジュールです。
- NDO::Weblog: RSS を整形して SSI で表示してみました - RSS を SSI でとりこみ表示する方法
Listings
List 1: rss2js.pl
#!/usr/local/bin/perl -w # rss2js - aggregate RSS to JavaScript use strict; use Digest::MD5 qw(md5_hex); use Encode; use HTML::Template; use LWP::Simple; use XML::RSS; our $CacheDir = "cache"; mkdir $CacheDir, 0755 unless -e $CacheDir; my $url = shift or die "URL needed!"; my $num = shift || 10; my $encoding = shift || "utf-8"; my $digest = md5_hex($url); my $cache = "$CacheDir/$digest.xml"; my $status = LWP::Simple::mirror($url, $cache); if (is_error($status)) { die "$url not found!\n"; } else { rss2js($cache, $num, $encoding); } sub rss2js { my($xml, $num, $encoding) = @_; my $rss = XML::RSS->new(); $rss->parsefile($xml); my @items = map { +{ title => $_->{title}, link => $_->{link} } } splice(@{$rss->items}, 0, $num); my $template = HTML::Template->new(filehandle => \*DATA); $template->param( title => $rss->{channel}->{title}, link => $rss->{channel}->{link}, items => \@items, ); binmode STDOUT, ":encoding($encoding)"; js_print($template->output()); } sub js_print { my @lines = split /\n/, shift; for (@lines) { s/\x27/'/g; # ' print "document.writeln('$_');\n"; } } __DATA__ <div class="rssChannel"> <h3 class="rssTitle"> <a href="<TMPL_VAR name=link escape=HTML>"> <TMPL_VAR name=title escape=HTML></a></h3> <div class="rssItem"> <TMPL_LOOP name=items> <a href="<TMPL_VAR name=link escape=HTML>"> <TMPL_VAR name=title escape=HTML></a><br /> </TMPL_LOOP></div> </div>List 2: bulknews.js
document.writeln('<div class="rssChannel">'); document.writeln('<h3 class="rssTitle">'); document.writeln('<a href="http://blog.bulknews.net/mt/">'); document.writeln('blog.bulknews.net</a></h3>'); document.writeln('<div class="rssItem">'); document.writeln(''); document.writeln('<a href="http://blog.bulknews.net/mt/archives/000492.html">'); document.writeln('AllConsuming</a><br />'); document.writeln(''); document.writeln('<a href="http://blog.bulknews.net/mt/archives/000491.html">'); document.writeln('perl 5.8.2 RC1 / perl 5.9.0</a><br />'); document.writeln(''); document.writeln('<a href="http://blog.bulknews.net/mt/archives/000490.html">'); document.writeln('LAMP JukeBox</a><br />'); document.writeln(''); document.writeln('<a href="http://blog.bulknews.net/mt/archives/000489.html">'); document.writeln('Weblog SPAM</a><br />'); document.writeln(''); document.writeln('<a href="http://blog.bulknews.net/mt/archives/000488.html">'); document.writeln('CPAN 8th birthday</a><br />'); document.writeln(''); document.writeln('<a href="http://blog.bulknews.net/mt/archives/000487.html">'); document.writeln('script タグに charset アトリビュート</a><br />'); document.writeln(''); document.writeln('<a href="http://blog.bulknews.net/mt/archives/000486.html">'); document.writeln('Mail::TempAddress</a><br />'); document.writeln(''); document.writeln('<a href="http://blog.bulknews.net/mt/archives/000485.html">'); document.writeln('less presentation</a><br />'); document.writeln(''); document.writeln('<a href="http://blog.bulknews.net/mt/archives/000484.html">'); document.writeln('iPod アップデータ</a><br />'); document.writeln(''); document.writeln('<a href="http://blog.bulknews.net/mt/archives/000483.html">'); document.writeln('Syndication ML</a><br />'); document.writeln('</div>'); document.writeln('</div>');
*1) MovableType であれば mt-rssfeed, blosxom であれば blagg などのプラグインが利用できます。
*2) もちろんそれが手間だと思わなければ、別にデメリットではないです ;-)
*3)
*4)
*5) 手元で検証した限りでは、Windows IE 6.0 では OK ですが、Windows IE 5.5 ではこの charset 指定は無効なようでした。
*2) もちろんそれが手間だと思わなければ、別にデメリットではないです ;-)
*3)
__DATA__
以降に記述した文字列を、DATA ファイルハンドルとして扱うことができます。*4)
binmode
でエンコーディングを指定できるのは、Perl 5.8 からの機能です。5.6 以前では、Win32 環境でファイルハンドルをバイナリモードにするのに使われていました。*5) 手元で検証した限りでは、Windows IE 6.0 では OK ですが、Windows IE 5.5 ではこの charset 指定は無効なようでした。
posted at: 17:06 by miyagawa | category: RSS | permalink
Trackback (6) | Printer Friendly | Email this blog