データベースから投稿データを拾ってXML::RSSでRSS 1.0を生成

ユーザが投稿した内容を保存しているデータベースからデータを拾い上げて、RSSを作ることにするか…… (´∀`)

定番のPerlXML::RSSを使って、MySQLのデータベースに入っているとする。こんな流れになるかな。サンプルソースコードをまとめたものは、この記事の最後にあります。

#!/usr/bin/perl

use strict;
use warnings;
use CGI;
use DBI;
use YAML;
use XML::RSS;

my $CONF = YAML::LoadFile("/your/apps/path/conf.yaml");
my $rss = new XML::RSS (version => '1.0');

my $form = new CGI;
$form->charset('utf-8');

まずコンフィグファイルはYAMLが便利なのでこれを使う。私の趣味。

RSSは、new XML::RSSする時にバージョンを選べる。ここを2.0にするだけでRSS 2.0を吐いてくれるから楽チン。

CGIでも無いのにCGI.pmを使っているが、これはユーザがポストした中身を出力するときにescapeHTML関数を使いたいから。それだけのために使っている。(´∀`) charsetをutf-8しておかないと文字化けすることがある。

my $DB_OPT = { mysql_enable_utf8 => 1,
               on_connect_do => [ "SET NAMES 'utf8'", "SET CHARACTER SET 'utf8'" ], };
my $dbh = DBI->connect(
 "dbi:mysql:dbname=$CONF->{db_name};host=$CONF->{db_host};mysql_server_prepare=1",
  $CONF->{db_user}, $CONF->{db_passwd}, $DB_OPT
) or die "$!\n Error: failed to connect to DB.\n";

my $ary_ref = $dbh->selectall_arrayref(
 "SELECT DATE_FORMAT(settime,'%Y-%m-%dT%H:%I:%S'), title, name, posttext, filepath " .
 "FROM userpost ORDER BY settime DESC LIMIT 10");

connectするところまでは、MySQLPerlからDBIで接続するときの定番なので省略……。

今回は面倒なので、SQL文はselectall_arrayrefを使ってしまった。本来ならfetchrow_arrayrefとか使うべきで、あまり推奨されないメソッドなのだが、LIMIT 10と行数制限のあるSQLなので、ま、いいか。(LIMIT付けないSQL文でselectall_arrayrefを無造作に使って、対象が巨大なテーブルで1億行とか返された日には、大変なことになる)

SELECTする時に、RSSで使う'%Y-%m-%dT%H:%I:%S'というISO形式の時刻フォーマットで取ってきている。

$rss->channel(
   title        => "<![CDATA[RSS title]]>",
   link         => "http://www.example.org/hoge/",
   description  => "<![CDATA[Recent posts of Hoge]]>",
   dc => {
     date       => $ary_ref->[0][0],
     subject    => '<![CDATA[Recent posts]]>',
     creator    => 'ozuma',
     publisher  => 'ozuma',
     rights     => 'Copyright 2012-, ozuma',
   }
 );

RSSのチャンネル要素をもりもり設定する。

foreach my $record (@$ary_ref) {
  my $settime = $record->[0];
  my $title = $record->[1];
  my $name = $record->[2];
  my $posttext = $form->escapeHTML($record->[3]);
  my $filepath = $record->[4];

$rss->add_item(
   title       => $title,
   link        => "$CONF->{top_url}$filepath",
   description => '<![CDATA[' . $posttext . ']]>',
   dc => {
     subject  => $title,
     creator  => $name,
     date => $settime,
   },
 );
}

次に各レコードごとに値を取り出して、RSSのitemをひとつひとつaddしていく。$posttextはユーザが入力した値で、XSSの危険があるためここでCGI.pmのescapeHTML関数を借りた。

print $rss->as_string;

$dbh->disconnect;

最後にRSSをas_string関数で出力し、忘れちゃいけないデータベースハンドルをdisconnetしてオシマイ。お疲れ様(´∀`)

サンプルコード全文

#!/usr/bin/perl

use strict;
use warnings;
use CGI;
use DBI;
use YAML;
use XML::RSS;

my $CONF = YAML::LoadFile("/your/apps/path/conf.yaml");
my $rss = new XML::RSS (version => '1.0');

my $form = new CGI;
$form->charset('utf-8');

my $DB_OPT = { mysql_enable_utf8 => 1,
               on_connect_do => [ "SET NAMES 'utf8'", "SET CHARACTER SET 'utf8'" ], };
my $dbh = DBI->connect(
 "dbi:mysql:dbname=$CONF->{db_name};host=$CONF->{db_host};mysql_server_prepare=1",
  $CONF->{db_user}, $CONF->{db_passwd}, $DB_OPT
) or die "$!\n Error: failed to connect to DB.\n";

my $ary_ref = $dbh->selectall_arrayref(
 "SELECT DATE_FORMAT(settime,'%Y-%m-%dT%H:%I:%S'), title, name, posttext, filepath " .
 "FROM userpost ORDER BY settime DESC LIMIT 10");

$rss->channel(
   title        => "<![CDATA[RSS title]]>",
   link         => "http://www.example.org/hoge/",
   description  => "<![CDATA[Recent posts of Hoge]]>",
   dc => {
     date       => $ary_ref->[0][0],
     subject    => '<![CDATA[Recent posts]]>',
     creator    => 'ozuma',
     publisher  => 'ozuma',
     rights     => 'Copyright 2012-, ozuma',
   }
 );

foreach my $record (@$ary_ref) {
  my $settime = $record->[0];
  my $title = $record->[1];
  my $name = $record->[2];
  my $posttext = $form->escapeHTML($record->[3]);
  my $filepath = $record->[4];

$rss->add_item(
   title       => $title,
   link        => "$CONF->{top_url}$filepath",
   description => '<![CDATA[' . $posttext . ']]>',
   dc => {
     subject  => $title,
     creator  => $name,
     date => $settime,
   },
 );
}

print $rss->as_string;

$dbh->disconnect;