Movesのデータをgoole mapに載せる

ASCII.jp:Movesのライフログや写真をGoogleマップやGoogleアースに表示するワザ (1/2)|柳谷智宣の「真似したくなるPC活用術」
なるほど便利だな。
https://www.google.com/maps/d/viewer?mid=z-EYc6GDOZHg.k8yF7Lfrue1A
無駄なデータを切り取ったりしたので若干不整合があるかもしれない。

ラスベガス -> グランドキャニオン -> ホースシューベンド -> アンテロープ -> モニュメントバレー -> ブレイスキャニオン -> ラスベガス、を2泊3日で回るコース。

日の長い夏ならともかく、17:30には日が落ちてしまうこの時期にはかなり無謀なプランであった。それもまた一人旅の自由さかなー。

特に3日目、モニュメントバレーからブライスキャニオンに行くのは相当厳しいスケジュールで、元々はザイオンでお茶を濁そうと思っていたのだけど・・・ブライスキャニオンはすごくイイらしいという話を見かけて、うーん多少無理してでも行きたいと思って行くことにしたのだった。結局、2時間くらいしか滞在できなかったけど、それでも苦労した甲斐があった。

しかしその代償として、1日でトータル10時間くらい運転する羽目になってしまったのだった。疲れた。

グランドキャニオンからホースシューベンドに向かう途中、89号が通行止めになってて泣きながら引き返したところもしっかりログに載っていて笑える。89TのTって"Temporary"なのね!ちょろっと山道みたいなのが出てるだけで、こんなの分かるか!ってなった。事前にGoogle mapで調べてなきゃ路頭に迷うところだったわ。NeverLostとかマジで糞。

思い出のついでに当時使ったプレイリストを残しておこう。トータルで2、3周くらいしたのかな?

  • 控えめI love you ! - HKT48
  • ウラオモテ・フォーチュン - 小澤亜李
  • 青春セッションPARADISE(ニコ生音源) - A応P
  • 出る杭は撃たれる - 流星群少女
  • 何度目の青空か? - 乃木坂64
  • A.T.M.O.S.P.H.E.R.E - スフィア
  • Spring is here - スフィア
  • Third Planet - スフィア
  • 4 colors for you - スフィア

それにしてもアイドルソングとドライブの相性よすぎ。アリゾナの荒野を走りながらアイドルソング爆音で流すの最高。放課後胸キュンスターとかテンション上がる曲は運転のテンションも上がるので助かった。あと車の中だと、えーおーぴー!とか叫んでも外には聞こえないしな。なかなか楽しい。

Twitpicの画像を一括ダウンロードするスクリプトが動かなかったのでクイックハックした

ふーん。まぁ何だかんだ大丈夫やろって思って油断してたけど、本当に終了なのか。

自分のtwitpicに関しては正直なんの関心もないけど、日高里菜ちゃんのアカウントだけは守らねば。

twitterが乗っ取られてしまった現在、twitpicとかyfrogとかだけがインターネットに残された貴重な記録だったのになあ。

Twitpic / rina_hidaka

適当に "twitpic 保存" とかで検索すると当然ながら既存のモノがあるので使わせていただこう。

下のシェルスクリプトにしよう。

と思ったけど、何やらうまく動かないのでちょっといじった。他のは知らんが日高里菜ちゃんのアカウントは保存できたのでよしとしよう。

変更点は以下の2箇所。やっつけ感。

  1. 最終ページの取得方法を変更(たぶんtwitpicのデザイン変更による影響だと思うけど)
  2. curlが何故かエラー22だけを決め打ちでリトライしていたので正常終了以外は全部リトライ

100万年ぶりにgist使った。

https://gist.github.com/kkobayashi/e50b3869f4dc91cf725e

スクリプトだと自分でちょこちょこ直せるのがよいね。でも俺シェルスクリプト読むの嫌いだけどね。

$ diff twpicdl.sh twpicdl_dbg.sh
52,54c52,55
< LAST=`curl http://twitpic.com/photos/${TP_NAME} \
<   | grep "<a href=.*>Last<" \
<   | sed "s/.*\?page=\([0-9]*\).*/\1/"`
---
> #LAST=`curl http://twitpic.com/photos/${TP_NAME} \
> #  | grep "<a href=.*>Last<" \
> #  | sed "s/.*\?page=\([0-9]*\).*/\1/"`
> LAST=`curl http://twitpic.com/photos/${TP_NAME} | grep "<a href=.*?page=" | grep -v Next | sed "s/.*\?page=\([0-9]*\).*/\1/" | tail -1`
78c79,80
<     if [ $? -eq 22 -a $RETRY -le $MAXRETRY ]; then
---
> #    if [ $? -eq 22 -a $RETRY -le $MAXRETRY ]; then
>     if [ $? -ne 0 -a $RETRY -le $MAXRETRY ]; then
110c112,113
<       if [ $? -eq 22 ]; then
---
> #      if [ $? -eq 22 ]; then
>       if [ $? -ne 0 ]; then
133c136,137
<         if [ $? -eq 22 ]; then
---
> #        if [ $? -eq 22 ]; then
>         if [ $? -ne 0 ]; then

追記

などと言ってるうちにtwitpic自体が写真の公開を停止してるようで。午前中にクロールして、昼食後にそろそろ自分のアカウントもダウンロードしておくか・・・って思ったら写真が見られなくなってた。わろす。ギリギリのタイミングだったんだなー。

はてなのOAuthを使ってみる

パスワードによるWSSE認証が終了するらしい・・・というか、今日の時点ですでに終了してるのかな?
はてな各種APIでのパスワードによるWSSE認証を2014年3月5日に終了します(開発者向け) - Hatena Developer Blog
APIキーかOAuth、ということになるんだろうけど、試しにOAuthを使ってみることにした。

Consumer key を取得して OAuth 開発をはじめよう - Hatena Developer Center

・・・・・・。

正直よく分からん。「典型的なリクエスト・レスポンスは次のようになります。」って言われても。どんなパラメーターを送ると何が返ってくるか、の説明がないとコレだけ読んでも全然分からんのだけど・・・。

ちょろっとアプリ作りたいってだけの人に詳細はRFC読め、って不親切すぎじゃねーかって思うんだが・・・。

サンプルコードもサーバーサイドアプリのサンプルで読みにくいし・・・。

GoogleのOAuthを試したときは解説が分かりやすくて助かったのだけど、まあ、安定のはてなクオリティってことか。

あと、これはOAuthが2.0じゃなくて1.0aであることが原因のようですが、twitterとかgoogleに比べてメチャメチャめんどくさいな。いちいちoauth_signatureを作って送らなきゃいけないとか、認証用のURLを作るためにリクエストークンとか言うのを取ってこなきゃいけないとか。

練習のためにLWPで頑張ろうと思ったけど、さすがに面倒くさすぎてライブラリーに頼ることにした。OAuth2.0に移行すればいいのになー。

OAuth 1.0での開発を経験された方なら、おそらく誰しも署名と複雑な認証フローには苦しめられたのではないでしょうか?

この仕組みが複雑なため、OAuthクライアントを作成するためにはOAuthのライブラリが必須でした。しかし複雑なため、ライブラリにバグが存在することもしばしばありました。ライブラリが存在しない言語でOAuthを使用したい開発者は諦めざるを得ないような状況でした。

OAuth 2.0でWebサービスの利用方法はどう変わるか(1/3)- @IT

ですよねー


OAuth1.0の仕様に関してはYahooのページが参考になりました。さすがやふー。

http://developer.yahoo.co.jp/other/oauth/endpoint.html

サンプル

愚痴はともかく。

まずはトークンをゲットするところから。

use strict;
use warnings;
use utf8;

use OAuth::Lite::Consumer;
use OAuth::Lite::Token;

my $consumer = OAuth::Lite::Consumer->new(
  consumer_key       => '[Your consumer key]',
  consumer_secret    => '[Your consumer secret]',
  site               => q{https://www.hatena.com},
  request_token_path => q{/oauth/initiate},
  access_token_path  => q{/oauth/token},
  authorize_path     => q{https://www.hatena.ne.jp/oauth/authorize},
);

my $request_token = $consumer->get_request_token(
  callback_url => 'oob',
  scope        => 'read_public,write_public,read_private,write_private',
);

printf("Authorize this app and enter the PIN# from:\n%s\n\nPIN = ", $consumer->url_to_authorize(token=>$request_token));
my $pin = <STDIN>; # wait for input
$pin =~ tr/\x0A\x0D//d;

my $access_token = $consumer->get_access_token(
  token    => $request_token,
  verifier => $pin,
);

printf("oauth_token  : %s\n", $access_token->token);
printf("oauth_secret : %s\n", $access_token->secret);
Authorize this app and enter the PIN# from:
https://www.hatena.ne.jp/oauth/authorize?oauth_token=[Request token]

PIN = [verification code]

oauth_token  : [Your OAuth token]
oauth_secret : [Your OAuth secret]

実際にどんなデータがやり取りされるのか?については、はてな様の説明の「典型的なリクエスト・レスポンス」とやらを見るとよいのではないかと。

で、ゲットしたトークンの使い方は

use strict;
use warnings;
use utf8;

use OAuth::Lite::Consumer;
use OAuth::Lite::Token;

my $consumer = OAuth::Lite::Consumer->new(
  consumer_key       => '[Your consumer key]',
  consumer_secret    => '[Your consumer secret]',
);

my $access_token    = OAuth::Lite::Token->new(
  token  => '[Your OAuth token]',
  secret => '[Your OAuth secret]',
);

my $res = $consumer->request(
  method => 'GET',
  url    => q{http://n.hatena.com/applications/my.json},
  token  => $access_token,
);

print $res->decoded_content;
$ perl sample.pl
{"profile_image_url":"http://cdn1.www.st-hatena.com/users/kk/kkobayashi/profile.gif?1383898898","url_name":"kkobayashi","display_name":"kkobayashi"}

こんな感じか。さすがにライブラリー使うと簡単にできるな。

ちなみにアクセストークンを取らずにURLにアクセスすると

$ curl -o - http://n.hatena.com/applications/my.json
{"display_name":"ゲスト"}

OAuth::Lite::Consume::requestが返すのは HTTP::Response オブジェクトらしい。

あと、自動的に"Accept-Encoding: gzip"ヘッダーを付けてリクエストするので、中身を取り出すときはdecoded_contentを使うとよい。


・・・うーん、とはいえ、いちいち OAuth::Lite::Consumer を通してリクエストするのがかったるいな。APIキーでいいような気がしてきた。OAuthに比べて何か制限ってあるんだっけ?

Yahoo! Pipesの話

今さらながらpipesがマイブームなのでメモしておく。

そもそも何で今さら?という話ですが、"乃木坂 RSS"で検索すると出てくる胡散臭いサイトがあって、一体どういうものが出てくるのだ?と興味本位で登録してみたらpipesかよ!ということがありまして。

で、改めて調べてみると、XPathが使えてscraperチックなことができそう、ということが分かって、ちょっといじってみるかーとなったわけです。

例題

とりあえずサンプルとして、WUGのニュースからRSSを作成してみることにしましょう。

Wake Up, Girls! 1期シリーズ公式サイト

1. Xpathでタイトルとリンクを取得

Extract using XPath: に "//div[@id='contents']/table/tr" を指定。"id('contents')" という形式は使えないっぽい。

この時点でデバッガウインドウを見てみると、こんな感じに。

"Emit items as string" をチェックしないとDOMツリーが出てくる。チェックするとHTMLテキストが出てくる。

ここではDOMを使いたいのでチェックしない。

2. title と link にマッピング

このページのパクリ。簡単。

Tumblr


3. 各記事の本文を取得

まあ無くてもいいんだけど、ニュースの本文がdescriptionに出てきたほうが見やすいので。さきほどマッピングしたitem.linkの各URLから本文のテキストをXPath "//div[@id='contents']" で取ってくる。

Loopモジュールと組み合わせるのがコツといえばコツかな?たいしたコツではない。

あとでいじるのでDOMではなくHTMLテキストとして出力させる。

4. 微調整

ニュース本文から余分なヘッダーやらフッターやらを除く。この辺泥臭いので、もう少しDOMを柔軟にいじるモジュールがあってもいいかなあ。あるのかも知れんが。

デバッガーでソースが見られるので、適宜整形。

<div id="contents">
      <h2>2/27(木)bayfm78:K・WEST ENTAME GENERATION、出演決定!</h2>
      <div>
        <p>2014/02/24</p>
      </div>
      <br/>
      <div>
        <strong>bayfm78:K・WEST ENTAME GENERATIONへのゲスト出演が決定!</strong>
      </div>
      <div>
        <p> </p>
      </div>
      <div>
        <p>【日時】2月27日(木)19時〜</p>
      </div>
      <div>
        <p>【番組名】
        <strong>
          <a rel="nofollow" target="_blank" href="http://homepage2.nifty.com/marua-S/kashifm.htm">bayfm78:K・WEST ENTAME GENERATION</a>
        </strong></p>
      </div>
      <div>
        <p>【出演者】田中美海、高木美佑
        <br/>
        <br/>
         お楽しみに!</p>
      </div>
      <br/>
      <br/> 
      <div class="back">
        <a rel="nofollow" target="_blank" href="http://wakeupgirls.jp/news.php">< 戻る</a>
      </div></div>

これが

<div id="contents"><br/>
      <div>
        <strong>bayfm78:K・WEST ENTAME GENERATIONへのゲスト出演が決定!</strong>
      </div>
      <div>
        <p> </p>
      </div>
      <div>
        <p>【日時】2月27日(木)19時〜</p>
      </div>
      <div>
        <p>【番組名】
        <strong>
          <a rel="nofollow" target="_blank" href="http://homepage2.nifty.com/marua-S/kashifm.htm">bayfm78:K・WEST ENTAME GENERATION</a>
        </strong></p>
      </div>
      <div>
        <p>【出演者】田中美海、高木美佑
        <br/>
        <br/>
         お楽しみに!</p>
      </div>
      <br/>
      <br/> 
      </div>

こんな感じ。Regexのオプションは説明が無いのでよく分からんけど、多分sがsingle line(<div>.+</div>とかで改行を含む文字列にマッチする)で、mがmultiple lines(^とか$が各行にマッチするようになる)という感じなのかな。mはPerlと同じ感覚でよいが、sはこういう解釈でいいいのだろうか。sとmを同時に指定したらどうなるんだろう・・・?

あと、内部のXML Parserを通しているせいか、"emit items as string"をチェックしても元データのHTMLとは違うものが出てくるので注意が必要。

まあ、とりあえず動けばよいか。

5. 完成

大体のサイトはXPathで抜き出し→マッピング→整形 の流れでRSS化できそう。IFTTTとかのRSSと連動するWebサービスと組み合わせれば、もっと色々なことができるんじゃないかと思う。

http://pipes.yahoo.com/pipes/pipe.info?_id=508c47e7c3fa362fc6687b251b71a8f0

追記

動くは動くんだけど、遅すぎてRSSリーダーによってはタイムアウトすることがあるっぽい。loop + fetch page系は注意せねば。

Perlでgmailからメール送る 補足

とりあえずメールは送れたが、中身が何やってるか全然分からんので、もう少し調べてみた。

その1 Conent-Typeなどの属性情報

Email::Simpleだとheaderとbodyしか使われず、attributesに指定したContent-Typeなどのパラメーターは無視されてしまうようです。

my $email = Email::Simple->create(
  header     => [
    From    => 'kobayashi01234@gmail.com',
    To      => 'kobayashi01234@gmail.com',
    Subject => encode("MIME-Header-ISO_2022_JP", "テストメール"),
  ],
    attributes => {
      content_type => 'text/plain',
      charset      => 'ISO-2022-JP',
      encoding     => '7bit',
    },
    body => encode('iso-2022-jp', "てすと"),
);

print $email->as_string;
From: kobayashi01234@gmail.com
To: kobayashi01234@gmail.com
Subject: =?ISO-2022-JP?B?GyRCJUYlOSVIJWEhPCVrGyhC?=
Date: Thu, 20 Feb 2014 13:24:50 +0900

てすと

MIME形式でメールを作成すれば(当然だけど)ちゃんと付く。

my $email = Email::MIME->create(
  header     => [
    From    => 'kobayashi01234@gmail.com',
    To      => 'kobayashi01234@gmail.com',
    Subject => encode("MIME-Header-ISO_2022_JP", "テストメール"),
  ],
    attributes => {
      content_type => 'text/plain',
      charset      => 'ISO-2022-JP',
      encoding     => '7bit',
    },
    body => encode('iso-2022-jp', "てすと"),
);

print $email->as_string;
From: kobayashi01234@gmail.com
To: kobayashi01234@gmail.com
Subject: =?ISO-2022-JP?B?GyRCJUYlOSVIJWEhPCVrGyhC?=
Date: Thu, 20 Feb 2014 13:26:55 +0900
MIME-Version: 1.0
Content-Type: text/plain; charset="ISO-2022-JP"
Content-Transfer-Encoding: 7bit

てすと

マルチパートじゃないメールでも明示的にContent-Typeを指定しておいたほうがよいのだろうか。とりあえずGmailからGmailに送る分にはEmail::Simpleのままで文字化けも起こっていないようだし、OKということにしよう。

その2 EMAIL_SENDER_TRANSPORT 環境変数

このあたりのサイトに色々と説明がされているんだけど、よく分からん。

調べてみた結果、環境変数 EMAIL_SENDER_TRANSPORT=XXX を指定すると、メールの送信に "Email::Sender::Transport::XXX" モジュールが使われる、ということらしい。で、XXXモジュールは何してるのかというと、send_emailを呼び出してるだけのようです(だけ、ということも無いんだろうけど)。

# Email/Sender/Role/CommonSending.pm:

sub send {
  my ($self, $message, $env, @rest) = @_;
  my $email    = $self->prepare_email($message);
  my $envelope = $self->prepare_envelope($env);

  try {
    return $self->send_email($email, $envelope, @rest);
  } catch {
    Carp::confess('unknown error') unless my $err = $_;

    if (
      try { $err->isa('Email::Sender::Failure') }
      and ! (my @tmp = $err->recipients)
    ) {
      $err->_set_recipients([ @{ $envelope->{to} } ]);
    }

    die $err;
  }
}

EMAIL_SENDER_TRANSPORT = Print を指定するとメールの中身をprintしてくれる。

# Email/Sender/Transport/Print.pm:

sub send_email {
  my ($self, $email, $env) = @_;

  my $fh = $self->fh;

  $fh->printf("ENVELOPE TO  : %s\n", join(q{, }, @{ $env->{to} }) || '-');
  $fh->printf("ENVELOPE FROM: %s\n", defined $env->{from} ? $env->{from} : '-');
  $fh->print(q{-} x 10 . " begin message\n");

  $fh->print( $email->as_string );

  $fh->print(q{-} x 10 . " end message\n");

  return $self->success;
}
$ EMAIL_SENDER_TRANSPORT=Print perl sm.pl
ENVELOPE TO  : kobayashi01234@gmail.com
ENVELOPE FROM: kobayashi01234@gmail.com
---------- begin message
From: kobayashi01234@gmail.com
To: kobayashi01234@gmail.com
Subject: =?ISO-2022-JP?B?GyRCJUYlOSVIJWEhPCVrGyhC?=
Date: Thu, 20 Feb 2014 13:46:21 +0900

てすと
---------- end message

EMAIL_SENDER_TRANSPORT = Test の使い方がサッパリ分からんかったけど、どうやら擬似的にエラーを発生させたりするものらしい。delivery_failure と recipient_failure を上書きすると送信を失敗させられるよ!超簡単だね!みたいなこと書いてあるが、別に簡単でもないような・・・。

BEGIN {
  use Email::Sender::Transport::Test;
  $ENV{EMAIL_SENDER_TRANSPORT} = 'Test';
  no warnings 'redefine';
  *Email::Sender::Transport::Test::delivery_failure  = sub {Email::Sender::Failure::->new("Error1")};
}

my $email = Email::Simple->create(
  header     => [
    From    => 'kobayashi01234@gmail.com',
    To      => 'kobayashi01234@gmail.com',
    Subject => encode("MIME-Header-ISO_2022_JP", "テストメール"),
  ],
  body => encode('iso-2022-jp', "てすと"),
);

sendmail($email);
$ perl sm.pl
Error1

Trace begun at /usr/lib/perl5/site_perl/5.14/Email/Sender/Transport/Test.pm line 86
Email::Sender::Transport::Test::send_email('Email::Sender::Transport::Test=HASH(0x60106b6a0)', 'Email::Abstract=ARRAY(0x60105e230)', 'HASH(0x60105e1e8)') called at /usr/lib/perl5/site_perl/5.14/Email/Sender/Role/CommonSending.pm line 47
Email::Sender::Role::CommonSending::try {...}  at /usr/lib/perl5/site_perl/5.14/Try/Tiny.pm line 81
eval {...} at /usr/lib/perl5/site_perl/5.14/Try/Tiny.pm line 72
Try::Tiny::try('CODE(0x601189678)', 'Try::Tiny::Catch=REF(0x601018838)') called at /usr/lib/perl5/site_perl/5.14/Email/Sender/Role/CommonSending.pm line 60
Email::Sender::Role::CommonSending::send('Email::Sender::Transport::Test=HASH(0x60106b6a0)', 'Email::Abstract=ARRAY(0x60105e230)', 'HASH(0x6011897c8)') called at /usr/lib/perl5/site_perl/5.14/Email/Sender/Simple.pm line 121
Email::Sender::Simple::send_email('Email::Sender::Simple', 'Email::Abstract=ARRAY(0x60105e230)', 'HASH(0x60105e140)') called at /usr/lib/perl5/site_perl/5.14/Email/Sender/Role/CommonSending.pm line 47
Email::Sender::Role::CommonSending::try {...}  at /usr/lib/perl5/site_perl/5.14/Try/Tiny.pm line 81
eval {...} at /usr/lib/perl5/site_perl/5.14/Try/Tiny.pm line 72
Try::Tiny::try('CODE(0x60105e200)', 'Try::Tiny::Catch=REF(0x600baf8d0)') called at /usr/lib/perl5/site_perl/5.14/Email/Sender/Role/CommonSending.pm line 60
Email::Sender::Role::CommonSending::send('Email::Sender::Simple', 'Email::Simple=HASH(0x60105e128)') called at /usr/lib/perl5/site_perl/5.14/Sub/Exporter/Util.pm line 18
Sub::Exporter::Util::__ANON__('Email::Simple=HASH(0x60105e128)') called at sm.pl line 36

こんな使い方でいいのかなあ・・・?よく分からん。

その3 Email::Sender::Transport::SMTP::TLS

POD見るとEmail::Sender::Transport::SMTPSの方がオススメだからそっち使え、と書いてあるのでそうした方がよいかもしれない。

Perlでgmailからメール送る

最近はEmail::Senderを使うのがおすすめらしい。

use strict;
use warnings;
use utf8;

use Email::Sender::Simple qw(sendmail);
use Email::Sender::Transport::SMTP::TLS;
use Encode;

my $sender = Email::Sender::Transport::SMTP::TLS->new(
  host     => 'smtp.gmail.com',
  port     => 587,
  username => $USERID,
  password => $PASSWORD,
);

my $email = Email::Simple->create(
  header => [
    From    => 'kobayashi01234@gmail.com',
    To      => 'kobayashi01234@gmail.com',
    Subject => encode('MIME-Header-ISO_2022_JP', "テストメール"),
  ],
  attributes => {
    content_type => 'text/plain',
    charset      => 'ISO-2022-JP',
    encoding     => '7bit',
  },
  body => encode('iso-2022-jp', "てすと"),
);

sendmail($email, {transport => $sender});

SMTPは派生が多いからtrasnportで色々な送信方法に対応してるのかな。

XML::Atom::Entryにカテゴリーを追加する

はてなブログAtomPub - Hatena Developer Center

サンプルコードにカテゴリーをセットする方法が書いてなかったので。

単純に、XML::Atom::Category を作って XML::Atom::Entry->category で足します。

#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use XML::Atom::Entry;

my $entry = XML::Atom::Entry->new;
$entry->title('title');
$entry->content('content');

my @categories = map { my $c = XML::Atom::Category->new; $c->term($_); $c } qw(aaa bbb ccc);
$entry->category(@categories);

print $entry->as_xml;
<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns="http://purl.org/atom/ns#">
  <title>title</title>
  <content mode="xml">
    <div xmlns="http://www.w3.org/1999/xhtml">content</div>
  </content>
  <category term="aaa"/>
  <category term="bbb"/>
  <category term="ccc"/>
</entry>

コードのXML::Atom::Categoryを作るところ、もっと簡単に

map { XML::Atom::Category->new(term => $_) } qw(aaa bbb ccc);

とか

map { XML::Atom::Category->new->term($_) } qw(aaa bbb ccc);

とか書きたかったんだけど、newで引数を指定してもattributeの設定は無視されるし、termメソッドは$selfを返さないし、一発で簡単に書くのはムリっぽい。面倒くさいなあ。

あと、XML::Atom::Entryのpodみても全然categoryに関する説明が書いてないし、ソースコードは抽象化されすぎてて何やってるか全然分からんし難儀したのでメモしておく。

まず、XML::Atom::EntryはXML::Atom::Thingのサブクラス、XML::Atom::ThingはXML::Atom::Baseのサブクラスです。

で、XML::Atom::Thingの中でmk_object_list_accessorを使ってcategoryメソッドを作成しているようです。

XML::Atom::Thing:

# common multiple elements
__PACKAGE__->mk_object_list_accessor('link' => 'XML::Atom::Link', 'links');
__PACKAGE__->mk_object_list_accessor('category' => 'XML::Atom::Category', 'categories');
__PACKAGE__->mk_object_list_accessor('author' => 'XML::Atom::Person', 'authors');
__PACKAGE__->mk_object_list_accessor('contributor' => 'XML::Atom::Person', 'contributors');

XML::Atom::Base:

sub mk_object_list_accessor {
    my $class = shift;
    my($name, $ext_class, $moniker) = @_;

    no strict 'refs';

    *{"$class\::$name"} = sub {
        my $obj = shift;

        my $ns_uri = $ext_class->element_ns || $obj->ns;
        if (@_) {
            # setter: clear existent elements first
            my @elem = childlist($obj->elem, $ns_uri, $name);
            for my $el (@elem) {
                $obj->elem->removeChild($el);
            }

            # add the new elements for each
            my $adder = "add_$name";
            for my $add_elem (@_) {
                $obj->$adder($add_elem);
            }
        } else {
            # getter: just call get_object which is a context aware
            return $obj->get_object($ns_uri, $name, $ext_class);
        }
    };
..

ここで、"return $obj;" とか入れてあれば楽なのに、と思うんだけど、どうなんだろうなあ。