RSS 出力対応した作業ログ
こんにちわ。Perl 界の桜井章一と呼ばれている mattn です。キメゼリフは「あんた、コードが煤けてるぜ」です*1。
今回初めて JPerl Advent Calendar に参加しましたが、この Advent Calendar を動かしてるサーバアプリのソースをガチャガチャと触っている、とてもしつけのなっていない大人です。
このサーバアプリで今回私が手を入れたのは、RSS の出力部分ですが今日はどの様に対応したかを説明します。どちらかというと作業ログです。
App::AdventCalendar は kan さんがこの JPerl Advent Calendar の為に書いたサーバアプリで、テキストファイルを食わしてブログ形式に仕上げる代物です。中身は PSGI 形式のリクエスト handler と、Text::Xatena を使った整形、Router::Simple を使ったルーティング、Text::Xslate をテンプレートエンジンに使っています。結構人気のある物を集めた作りだと思います。
非常に分かりやすく作ってあると思うので、ぜひ参考にソースを見ると勉強になるかと思います。ちなみに私が確認しているので Windows 上での動作は保障します。
RSS 対応するにあたり、まずルーティングしている部分を触りました。通常の記事をサーブしているハンドラは以下の部分です。
$router->connect(
'/{year:\d{4}}/{name:[a-zA-Z0-9_-]+?}/',
{
tmpl => 'index.html',
act => sub {
# snip
},
}
);
この URL の後ろに rss を付けて RSS を出力する様にしたいので、この部分をコピペして以下を追加。
$router->connect(
'/{year:\d{4}}/{name:[a-zA-Z0-9_-]+?}/rss',
{
content_type => 'application/xml',
tmpl => 'index.xml',
act => sub {
# snip
},
}
);
各ハンドラに共通的に設定されている content_type を "application/xml" に、tmpl を "index.xml" に変更しました。ハンドラの中身は通常のHTML版と変わりませんが、RSS を配信するには幾らか情報が足りません。link、category、author に設定する内容です。
link は HTML 版のリンクを指せば良いのでカテゴリと日付番号を足して設定。残る category と author については記事を書いた本人が設定すべきものなので、メタ情報を吸い上げる仕組みを別途実装しました。
どんな風に記述するかは、先日書いた記事を見て頂く事とします。
RSS 出力対応を行った際、通常 HTML 版と共通するテキストファイル解析処理を parse_entry というサブルーチンにまとめましたが、そこに処理を追加しました。テキストを読み込んでいる部分を見ると
my $text = $file->slurp( iomode => '<:utf8' ); my ( $title, $body ) = split( "\n\n", $text, 2 );
テキストと本文は最初の空行で区切られるので、上記リンク先で説明している記述方法の場合、タイトル側にメタ情報が含まれれますね。
my ( $tmp, %meta ) = ( '', () );
for ( split /\n/, $title ) {
if ($tmp) {
my ( $key, $value ) = m{^meta-(\w+):\s*(.+)$};
if ($key) {
$meta{$key} = $value;
}
} else {
$tmp = $_;
}
}
$title = $tmp;
こんな感じにタイトル部から「meta-XXX」となっている部分を拾い上げ、タイトルを再設定します。また parse_entry が返すハッシュリファレンスに対して以下の様にメタ情報をマージしました。
return {
title => $title,
text => $text,
update_at => $ftime->strftime( '%c' ),
pubdate => $ftime->strftime( '%Y-%m-%dT%H:%M:%S' ),
footnotes => $inline->can('footnotes') ? $inline->footnotes : {},
%meta, # ここ
};
これで RSS テンプレートで entry.author という変数が参照出来る様になります。
さて最後に、category です。meta-tags というメタ情報から複数の category ノードを作る必要があります。meta-tags で記事の著者に設定してもらったカンマセパレート文字列を配列に乗せ変えます。
メタ情報の吸い上げは汎用的に作ったので簡単ですね。
my @tags = split /,\s*/, $entry->{tags} || '';
$entry->{categories} = \@tags;
あとはテンプレートで entry.categories をループで回してあげればok。
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>[% name %] - [% conf.title %]</title>
<link>[% uri_for(year _ '/' _ name _ '/') %]</link>
<description>[% name %] - [% conf.title %]</description>
[% FOR entry IN entries %]<item>
<title>[% entry.title %]</title>
<pubDate>[% entry.pubdate %]</pubDate>
<link>[% uri_for(entry.link) %]</link>
[% FOR category IN entry.categories %]<category>[% category %]</category>
[% END %]
<guid isPermaLink="true">[% uri_for(entry.link) %]</guid>
<description>
[% entry.text | unmark_raw %]
</description>
<author>[% entry.author %]</author>
</item>
[% END %]
</channel>
</rss>
簡単ですね。