はじめに
2年くらい前,お客さんに頼まれてちょこっと調べたり作ったりしたものについて今さらアウトプット.
本エントリでは,「Outlook Express」で管理されている「dbxファイル」に保存されている複数のメールを,perlで処理するための一方法について,簡単なサンプルを交えながらざっと解説します.
あ,サンプルは本日書いたものですw
頼まれた内容
頼まれた内容は,Windowsののメーラ「Outlook Express」(以下,oe)の特定のフォルダに入っているメールの内容を集計する,というようなものでした.
oeでは,「名前を付けて保存」メニューにより,単一のメールを「emlファイル」として保存できます.その内容をテキストエディタで確認することも簡単です.
しかし,複数のメールを選択した状態では,このメニューを選択できません.これはひどい.(せめて入力した名前+連番とかで保存できればねぇ...)
dbxファイルの存在
メールの集合がどこかに,何らかの形で保存されているはずなので,oeのメニューをいろいろ試してみたり,アプリケーションの関連フォルダを覗いてみたりしました.
結果,「受信トレイ」や「送信トレイ」,自分で作成したフォルダなど(以下,フォルダ)に入っているメールは,「dbxファイル」としてフォルダごとにひとまとめになっているっぽいことがわかりました.
しかし,テキストエディタで開いても残念な感じでした.(上スクリーンショットはcarbon emacsにて.)
「dbxファイル」で検索しても,これ!と個人的に感じるものが見あたりませんでした,多分.
ちなみに,dbxファイルはデフォルトでは次のようなフォルダにあります.
- C:
- Documents and Settings
- ユーザ名
- Local Settings
- Application Data
- Identities
- {UUID文字列}
- Microsoft
- Outlook Express
このファイルが壊れると,多分oeでもメールが見られなくなる可能性があるのでご注意を.
cpanモジュール Mail::Transport::Dbx
そこで,困ったときの(困る前でも)cpanモジュール検索.(当時は今よりもcpanモジュールを探すクセがついていなかったので,この行動に至るまで結構間が空いてしまいました.)
いくつか見つかった中でよさげだったのが,Mail::Transport::Dbxというモジュール.
指定したdbxファイルにアクセスして,保存されているメールの数やメール1つ1つの各種情報を取得したりできます.
当時も,このモジュールを使うことで,問題がすんなり解決してしまいました.
サンプル
件名のリストを取得する
dbxの中のメールそれぞれの件名を抜き出してみます.
#!/usr/bin/perl use strict; use warnings; use utf8; use Mail::Transport::Dbx; use Encode; use Data::Dumper; my $dbx = Mail::Transport::Dbx->new( 'ほげほげ.dbx' ); my @emails = $dbx->emails; my $email_title = [ map { encode( 'utf-8', decode( 'sjis', $_->subject ) ); } @emails ]; print Dumper $email_title;
結果は次のとおり.
% ./sample.pl
$VAR1 = [
'Mail::Transport::Dbx ほげほげ',
'Mail::Transport::Dbx ほげほげ その2',
'Mail::Transport::Dbx ほげほげ その3',
'Mail::Transport::Dbx ほげほげ その4',
'Mail::Transport::Dbx ほげほげ その5'
];
%件名と受信時間で構成されるハッシュ,のリストを取得する
my $email_info = [ map { my $i = { subject => encode( 'utf-8', decode( 'sjis', $_->subject ) ), received => $_->rcvd_localtime .'', # スカラコンテキストとして #received => $_->rcvd_gmtime .'', # スカラコンテキストとして }; } @emails ]; print Dumper $email_info;
結果は次のとおり.
% ./sample.pl
$VAR1 = [
{
'subject' => 'Mail::Transport::Dbx ほげほげ',
'received' => 'Thu Jul 16 00:41:06 2009'
},
{
'subject' => 'Mail::Transport::Dbx ほげほげ その2',
'received' => 'Thu Jul 16 00:41:19 2009'
},
{
'subject' => 'Mail::Transport::Dbx ほげほげ その3',
'received' => 'Thu Jul 16 00:41:30 2009'
},
{
'subject' => 'Mail::Transport::Dbx ほげほげ その4',
'received' => 'Thu Jul 16 00:41:44 2009'
},
{
'subject' => 'Mail::Transport::Dbx ほげほげ その5',
'received' => 'Thu Jul 16 00:41:57 2009'
}
];
%さらに本文の内容を加工してみる
本文の内容を加工しておくと,後から参照しやすくていいかもです.ここでは,本文がYAML形式であることを前提に進めます.
use YAML::Syck; my $email_info_more = [ map { my $i = { head => { subject => encode( 'utf-8', decode( 'sjis', $_->subject ) ), received => $_->rcvd_localtime .'', }, body => {}, }; my $body = encode( 'utf-8', decode( 'jis', $_->body ) ); my $yaml_body = Load( $body ); $yaml_body->{comment} =~ s{(?:\x0d\x0a?|\x0a)}{<NL>}g; # 改行を置換 $i->{body}->{$_} = $yaml_body->{$_} for qw/ name age zipcode address email comment /; $i; } @emails ]; print Dumper $email_info_more;
結果は次のとおり.
% ./sample.pl
$VAR1 = [
{
'body' => {
'email' => 'hoge1@hoge.com',
'comment' => 'ほげほげ<NL>ふがふが<NL>ぴよぴよ.<NL>',
'name' => 'ほげ田 ほげ一郎',
'address' => '愛知県名古屋市昭和区どこどこ',
'zipcode' => '123-4567',
'age' => '20'
},
'head' => {
'subject' => 'Mail::Transport::Dbx ほげほげ',
'received' => 'Thu Jul 16 00:41:06 2009'
}
},
{
'body' => {
'email' => 'hoge2@hoge.com',
'comment' => 'ほげほげ<NL>ふがふが<NL>ぴよぴよ.<NL>',
'name' => 'ほげ田 保げ二',
'address' => '愛知県名古屋市昭和区どこそこ',
'zipcode' => '123-4567',
'age' => '36'
},
'head' => {
'subject' => 'Mail::Transport::Dbx ほげほげ その2',
'received' => 'Thu Jul 16 00:41:19 2009'
}
},
{
'body' => {
'email' => 'hoge3@hoge.com',
'comment' => 'ほげほげ<NL>ふがふが<NL>ぴよぴよ.<NL>',
'name' => 'ほげ田 美保げ',
'address' => '愛知県名古屋市昭和区どこかしこ',
'zipcode' => '123-4567',
'age' => '26'
},
'head' => {
'subject' => 'Mail::Transport::Dbx ほげほげ その3',
'received' => 'Thu Jul 16 00:41:30 2009'
}
},
{
'body' => {
'email' => 'hoge4@hoge.com',
'comment' => 'ほげほげ<NL>ふがふが<NL>ぴよぴよ.<NL>',
'name' => 'ほげ田 ほげ志',
'address' => '愛知県名古屋市昭和区あそこ',
'zipcode' => '123-4567',
'age' => '47'
},
'head' => {
'subject' => 'Mail::Transport::Dbx ほげほげ その4',
'received' => 'Thu Jul 16 00:41:44 2009'
}
},
{
'body' => {
'email' => 'hoge5@hoge.com',
'comment' => 'ほげほげ<NL>ふがふが<NL>ぴよぴよ.<NL>',
'name' => 'ほげ田 ほげ五郎',
'address' => '愛知県名古屋市昭和区ここ',
'zipcode' => '123-4567',
'age' => '53'
},
'head' => {
'subject' => 'Mail::Transport::Dbx ほげほげ その5',
'received' => 'Thu Jul 16 00:41:57 2009'
}
}
];
%ここで得られたものについて,名前と年齢を,年齢が大きい順に抜き出してみます.
my $email_info_more_sorted = [ map { { name => $_->{body}->{name}, age => $_->{body}->{age}, }; } sort { int $b->{body}->{age} cmp int $a->{body}->{age} } @$email_info_more ]; print Dumper $email_info_more_sorted;
結果は次のとおり.
% ./sample.pl
$VAR1 = [
{
'name' => 'ほげ田 ほげ五郎',
'age' => 53
},
{
'name' => 'ほげ田 ほげ志',
'age' => 47
},
{
'name' => 'ほげ田 保げ二',
'age' => 36
},
{
'name' => 'ほげ田 美保げ',
'age' => 26
},
{
'name' => 'ほげ田 ほげ一郎',
'age' => 20
}
];
%csv形式に加工する
集計結果を再利用するには,csv形式とかに加工しておくといいですね.
my $csv = [ '名前,年齢,メールアドレス,郵便番号,住所,コメント', map { my $em = $_; my @values = map { sprintf '"%s"', decode( 'utf-8',$em->{body}->{$_} ); } qw/ name age zipcode address comment /; join ',', @values; } @$email_info_more ]; $csv = join "\n", @$csv; print encode( 'utf-8', $csv ), "\n";
結果は次のとおり.
% ./sample.pl 名前,年齢,メールアドレス,郵便番号,住所,コメント "ほげ田 ほげ一郎","20","123-4567","愛知県名古屋市昭和区どこどこ","ほげほげ<NL>ふがふが<NL>ぴよぴよ.<NL>" "ほげ田 保げ二","36","123-4567","愛知県名古屋市昭和区どこそこ","ほげほげ<NL>ふがふが<NL>ぴよぴよ.<NL>" "ほげ田 美保げ","26","123-4567","愛知県名古屋市昭和区どこかしこ","ほげほげ<NL>ふがふが<NL>ぴよぴよ.<NL>" "ほげ田 ほげ志","47","123-4567","愛知県名古屋市昭和区あそこ","ほげほげ<NL>ふがふが<NL>ぴよぴよ.<NL>" "ほげ田 ほげ五郎","53","123-4567","愛知県名古屋市昭和区ここ","ほげほげ<NL>ふがふが<NL>ぴよぴよ.<NL>" %
おわりに
以上,cpanモジュールMail::Transport::Dbxを使って,「Outlook Express」のdbxファイルを処理してみました.
ここで挙げたサンプルでは,dbxファイルに対応するフォルダのサブフォルダを考慮していませんが,同モジュールはそのようなものについて処理できるようです.
本エントリのネタは,「次にとるべき行動」リストに加えてから63週間,ずっと手をつけていない状態でした...><
おまけ
dbxファイルの保存先は変更することができます.




