Fork me on GitHub

2010/09/16


このエントリーをはてなブックマークに追加
サイボウズLiveにAPIが追加されましたね。
機能追加のお知らせ(API公開、Googleカレンダーとの同期、デザイン変更) | サイボウズLiveマガジン
9月15日のサイトメンテナンスにより、新機能を追加しました。
■APIの公開
外部アプリケーションからサイボウズLiveの情報を取得・操作できるAPIを公開しました。通信方式はREST、認証方式はOAuthです。現在は最新情報の取得、スケジュール情報の取得に限定していますが、順次APIで取得・操作可能な情報を拡充していきます。
APIの公開にあわせて、サイボウズLive Developer Centerを公開しました。
http://magazine.cybozulive.com/2010/09/system_update10.html
API好きとしては、これを放っておく訳にはいきません。
もちろんVimで行きます!

APIはドキュメントによるとAtomを使います。また認証にはTwitterと同じくOAuthが採用されています。
Vimscriptでやるには少し敷居が高いんじゃないの?と思っている貴方!出来るんです!やるんです!

まず先日作った、vim-oauthを使って認証を行いました。
"https://api.cybozulive.com/oauth/initiate" に対してリクエストトークンを要求し、そのリクエストトークンを元に "https://api.cybozulive.com/oauth/authorize" でブラウザ認証を行います。すると数文字のベリファイアが貰えるので、"https://api.cybozulive.com/oauth/token" へアクセストークンを要求します。
これで認証必要な物は揃いました。ここまでをコードにすると以下の様な感じ。
set rtp+=webapi-vim

let config = {}
let configfile = expand('~/.cybozulive')
if filereadable(configfile)
  let config = eval(join(readfile(configfile)""))
else
  let config.consumer_key = input("consumer_key:")
  let config.consumer_secret = input("consumer_secret:")
  
  let request_token_url = "https://api.cybozulive.com/oauth/initiate"
  let auth_url =  "https://api.cybozulive.com/oauth/authorize"
  let access_token_url = "https://api.cybozulive.com/oauth/token"
  
  let [request_token, request_token_secret] = oauth#requestToken(request_token_url, config.consumer_key, config.consumer_secret)
  if has("win32") || has("win64")
    exe "!start rundll32 url.dll,FileProtocolHandler ".auth_url."?oauth_token=".request_token
  else
    call system("xdg-open '".auth_url."?oauth_token=".request_token."'")
  endif
  let verifier = input("PIN:")
  let [access_token, access_token_secret] = oauth#accessToken(access_token_url, config.consumer_key, config.consumer_secret, request_token, request_token_secret, {"oauth_verifier": verifier})
  let config.access_token = access_token
  let config.access_token_secret = access_token_secret
  call writefile([string(config)], configfile)
endif
これで、アクセストークンとアクセストークンシークレットが貰えます。簡単ですね!
さて、肝心のAtomです。このブログでは紹介した事なかったと思いますが、VimでのXMLパーサを実は以前書いた暖めておりました。
mattn's xmlparser-vim at master - GitHub

XML Parser for Vim

http://github.com/mattn/xmlparser-vim
これを使ってパースしました。パースするとDOMが返ります。
let notification_url = "https://api.cybozulive.com/api/notification/V2"
let ret = oauth#get(notification_url, config.consumer_key, config.consumer_secret, config.access_token, config.access_token_secret, {})
let dom = xml#parse(ret.content)
for elem in dom.findAll("entry")
  echo elem.find("updated").value() . " " .  elem.find("title").value()
  echo "  " . elem.find("author").find("name").value()
  let summary = elem.find("summary")
  if !empty(summary)
    echo "  " . substitute(summary.value(), "\n""\n  ""g")
  endif
  echo "\n"
endfor
こんな感じにXMLを処理出来ます。これ、全てPure Vimscriptです。信じれない人や変態な人は、ソース覗いて下さい。
動く物を置いておきます。
mattn's cybozulive-vim at master - GitHub

vim interface to cybozulive

http://github.com/mattn/cybozulive-vim
http 関連、XML関連を纏めて webapi-vim というリポジトリに入れ、cybozulive-vim からは submodule としています。
mattn's webapi-vim at master - GitHub

vim interface to Web API

http://github.com/mattn/webapi-vim
# git submodule init
# git submodule update
として下さい。
以下、cybozulive.vim の全体のソースです。
set rtp+=webapi-vim

let config = {}
let configfile = expand('~/.cybozulive')
if filereadable(configfile)
  let config = eval(join(readfile(configfile)""))
else
  let config.consumer_key = input("consumer_key:")
  let config.consumer_secret = input("consumer_secret:")
  
  let request_token_url = "https://api.cybozulive.com/oauth/initiate"
  let auth_url =  "https://api.cybozulive.com/oauth/authorize"
  let access_token_url = "https://api.cybozulive.com/oauth/token"
  
  let [request_token, request_token_secret] = oauth#requestToken(request_token_url, config.consumer_key, config.consumer_secret)
  if has("win32") || has("win64")
    exe "!start rundll32 url.dll,FileProtocolHandler ".auth_url."?oauth_token=".request_token
  else
    call system("xdg-open '".auth_url."?oauth_token=".request_token."'")
  endif
  let verifier = input("PIN:")
  let [access_token, access_token_secret] = oauth#accessToken(access_token_url, config.consumer_key, config.consumer_secret, request_token, request_token_secret, {"oauth_verifier": verifier})
  let config.access_token = access_token
  let config.access_token_secret = access_token_secret
  call writefile([string(config)], configfile)
endif

let notification_url = "https://api.cybozulive.com/api/notification/V2"
let ret = oauth#get(notification_url, config.consumer_key, config.consumer_secret, config.access_token, config.access_token_secret, {})
let dom = xml#parse(ret.content)
for elem in dom.findAll("entry")
  echo elem.find("updated").value() . " " .  elem.find("title").value()
  echo "  " . elem.find("author").find("name").value()
  let summary = elem.find("summary")
  if !empty(summary)
    echo "  " . substitute(summary.value(), "\n""\n  ""g")
  endif
  echo "\n"
endfor
実行結果貼っておきますね。
2010-09-16T03:33:00Z サイボウズLiveアプリを作る!
  松本 泰弘
  [登録] 2010年9月16日(木)

2010-06-30T14:42:20Z tw.pptx
  竹迫 良範

2010-09-15T02:40:58Z mattnさんとオフする
  松本 泰弘
  大阪に出張に逝ったときとか

2010-06-18T01:27:21Z 竹迫 良範 さんが参加しました
  竹迫 良範

2010-02-26T03:59:58Z test
  hasegawa yosuke
  EF BB BF C0 AF 2F
  (添付ファイルがあります。)

mattn the pure vimscripter.
Posted at 15:54 in ソフトウェア::vim
Tagged as: API, Atom, OAuth, vim, xml
Bookmarks: add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip

2009/07/09


このエントリーをはてなブックマークに追加
XML::Simpleだと格納される結果が決まっており、例えば
<statuses>
    <status>
        <id>4773580</id>
        <text>kazuhoさんがやってくれました!</text>
        <user>
            <screen_name>mattn</screen_name>
        </user>
    </status>
    <status>
        <id>4773581</id>
        <text>今日のnickは○○提供です。</text>
        <user>
            <screen_name>kazuho</screen_name>
        </user>
    </status>
</statuses>
こんなXMLを以下の様な形にしたい場合がある場合に少し不便だったりします。
---
id: 4773580
text: kazuhoさんがやってくれました!
screen_name: mattn
---
id: 4773581
text: 今日のnickは○○提供です。
screen_name: kazuho
XML::Simpleを使うと、arrayノード一つにID要素があると勝手にノード扱いになったり、不必要なノードへのアクセスが必要になったりします。以下XML::Simpleのパース結果
---
status:
  4773580:
    text: kazuhoさんがやってくれました!
    user:
      screen_name: mattn
  4773581:
    text: 今日のnickは○○提供です。
    user:
      screen_name: kazuho
こんな場合にはXML::CuteQueriesを使うと便利です。
Paul Miller / XML-CuteQueries - search.cpan.org

A cute little query language for converting XML to Perl

http://search.cpan.org/dist/XML-CuteQueries/
上の例であれば以下のコードで望み通りの形式でパース出来てしまいます。
use strict;
use warnings;
use LWP::Simple;
use XML::CuteQueries;

my $cq = XML::CuteQueries->new;
$cq->parse(get "http://api.wassr.jp/statuses/public_timeline.xml");
my @statuses   = $cq->cute_query("/statuses/*" => {'*' => '', 'user/*' => ''});
use YAML;
warn Dump @statuses;
PODを見ていただければ分かりますが、XPathでクエリ式を書きそれに対するデータシェイプを指定します。例であれば"/statuses/*"にあるノードすべて"*"はデータシェイプのルートに、また"/statuses/*"にある"user/*"(user内の全て)もデータシェイプ内のルートに置くという指定になります。
これ、XML版のWeb::Scraperって感じですかね。便利だわー。

モダンPerl入門 (CodeZine BOOKS) モダンPerl入門 (CodeZine BOOKS)
牧 大輔
翔泳社 / ¥ 2,940 (2009-02-10)
 
発送可能時間:在庫あり。

Posted at 10:34 in ソフトウェア::lang::perl
Tagged as: perl, xml, xpath
Bookmarks: add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip

2009/05/22


このエントリーをはてなブックマークに追加
YQLを使うと色んなネットワークリソースをさもAPIを扱うかの様に操作でき、幾らでも新しい可能性が生まれて来ます。YQLには初期の状態でYahoo!で扱える色んなテーブル(flickrやdelicious等)が用意されており
show tables
と入力することでそのテーブル一覧が表示されます。
yql-community-tables1
また右側のサイドバーにあるテーブル一覧で「Show Comminity Tables」をクリックするとユーザコミュニティが作成した便利なテーブルも扱う事が出来ます。
yql-community-tables2
これらのComminity Tablesはgithubで開発されており、日々新しいデータテーブルが作成されています。
実はこのユーザテーブルは、ネットワーク上にXMLを配置する事が出来る人ならば誰でも作れます。
今日はこのユーザテーブルを自作する手順をご紹介します。

ユーザテーブルはユーザテーブル群を纏めるenvファイルと、実際のクエリを記述するファイルとで構成され、YQLからenvパラメータを使って参照する事が出来ます。例えばユーザコミュニティのテーブルであればenvファイルのURLを指定して以下の様にアクセスされます。
http://developer.yahoo.com/yql/console/?env=http://datatables.org/alltables.env
このenvパラメータで指定されたURLにアクセスしてみて頂けると分かると思いますが形式は以下の様なuse文の羅列になっています。
use 'http://www.datatables.org/amazon/amazon.ecs.xml' as amazon.ecs;
use 'http://www.datatables.org/auth/auth.basic.xml' as auth.basic;
use 'http://www.datatables.org/bitly/bit.ly.shorten.xml' as bit.ly.shorten;
use 'http://www.datatables.org/data/data.html.cssselect.xml' as data.html.cssselect;
このenvファイルと、実際のXMLファイルを用意すれば自分専用のAPIを作る事が出来ます。
今日はサンプルとして、はてなのユーザプロフィールをスクレイピングしてXML(もしくはJSON)を返すAPIを作ってみたいと思います。
まず配置場所としては、Google Page Creatorを使用しました。Google Page CreatorならばGoogleのアカウントさえ作れば誰でも無料でXMLを配置する事が出来ます。
はてなプロフィールを扱うAPIなのでファイル名はhatena.profile.xmlとし、alltables.envファイル
use 'http://mattn.jp.googlepages.com/hatena.profile.xml' as hatena.profile;
としました。
次に実際のAPI部ですが、形式は以下の様になります。
<?xml version="1.0" encoding="UTF-8"?>
<table xmlns="http://query.yahooapis.com/v1/schema/table.xsd">
  <meta>
    <author>mattn</author>
  </meta>
  <bindings>
    <select produces="XML" itemPath="">
      <urls>
        <url>http://www.hatena.ne.jp/{id}/profile</url>
      </urls>
      <inputs>
        <key id="id" type="xs:string" paramType="variable" required="true"/>
      </inputs>
      <execute/>
    </select>
  </bindings>
</table>
YQLではXMLの中に書かれたexecuteノードにjavascriptを記述する事ができ、response.objectというオブジェクト変数に値を格納する事で独自のフォーマットを作り出す事が出来ます。
またそこで使うことが出来るjavascriptにはE4X(ECMAScript for XML)が採用されており、XMLを扱いやすくなっています。
今回のはてなプロフィールAPIではhttp://www.hatena.ne.jp/mattn/で表示されるページを組み込み関数y.queryを使ってスクレイピングします。
以下javascript部のコード
var contents = y.query("select * from html where url=@url and xpath=@xpath", {
  url: "http://www.hatena.ne.jp/" + id + "/",
  xpath: "'//dl[@class=\"profile\"]/dd'"
});
var shortdesc = contents.results..div.(@['id']=="hatena-body")..dd[2];
var longdesc = contents.results..div.(@['id']=="hatena-body")..dd[3];
var profile =
<profile>
    <user>{contents.results..div.(@['id']=="hatena-body")..dd[0].p.text().toString()}</user>
    <nickname>{contents.results..div.(@['id']=="hatena-body")..dd[1].p.text().toString()}</nickname>
    <shortdesc>{shortdesc ? shortdesc.*.toString() : ''}</shortdesc>
    <longdesc>{longdesc ? longdesc.*.toString() : ''}</longdesc>
    <medals/>
    <addresses/>
    <services/>
</profile>;
for each(var n in contents.results..div.(@['class']=="medals").img) {
    profile.medals.medial += <medal>{n.@['title'].toString()}</medal>;
}
for each(var n in contents.results..table.(@['class']=="profile addresslist")..tr) {
    profile.addresses.address +=
    <address>
        <name>{n.th.*.text().toString()}</name>
        <value>{n.td.*.text().toString()}</value>
    </address>;
}
for each(var n in contents.results..ul.(@['class']=="hatena-fotolife floatlist")[0]..a) {
    profile.services.service +=
    <service>
        <name>{n.img.@['title'].toString()}</name>
        <url>{n.@['href'].toString()}</url>
    </service>
}
response.object = profile;
y.queryにはYQLから使うことが出来るselect文を使用する事ができ、xpathを使って絞り込む事も出来ます。ただ今回は一つのAPIで
  • アカウントID
  • ニックネーム
  • 短い紹介
  • 自己紹介
  • アドレス情報
  • 使っているサービス一覧
  • 市民メダル情報
を一度に扱うので、個別にxpathでクエリを投げるのでは無く、一部を除いたページ全体を取得しておいて、E4XのDOM操作でxpathライクな処理を書いています。
例えばE4Xでは、DOMオブジェクトの階層スキップや条件指定検索を
contents.results..div.(@['id']=="hatena-body")..dd[2];
等といった書き方をする事が出来ます。つまり無理にxpathで絞り込む必要は無いという事です。
おそらくサーバ側ではrhinoあたりを使っているのかもしれませんね。

XML全体のコードは以下の様になりました。
<?xml version="1.0" encoding="UTF-8"?>
<table xmlns="http://query.yahooapis.com/v1/schema/table.xsd">
  <meta>
    <author>mattn</author>
  </meta>
  <bindings>
    <select produces="XML" itemPath="">
      <urls>
        <url>http://www.hatena.ne.jp/{id}/profile</url>
      </urls>
      <inputs>
        <key id="id" type="xs:string" paramType="variable" required="true"/>
      </inputs>
      <execute><![CDATA[
      var contents = y.query("select * from html where url=@url and xpath=@xpath", {
        url: "http://www.hatena.ne.jp/" + id + "/",
        xpath: "'//dl[@class=\"profile\"]/dd'"
      });
      var shortdesc = contents.results..div.(@['id']=="hatena-body")..dd[2];
      var longdesc = contents.results..div.(@['id']=="hatena-body")..dd[3];
      var profile =
      <profile>
          <user>{contents.results..div.(@['id']=="hatena-body")..dd[0].p.text().toString()}</user>
          <nickname>{contents.results..div.(@['id']=="hatena-body")..dd[1].p.text().toString()}</nickname>
          <shortdesc>{shortdesc ? shortdesc.*.toString() : ''}</shortdesc>
          <longdesc>{longdesc ? longdesc.*.toString() : ''}</longdesc>
          <medals/>
          <addresses/>
          <services/>
      </profile>;
      for each(var n in contents.results..div.(@['class']=="medals").img) {
          profile.medals.medial += <medal>{n.@['title'].toString()}</medal>;
      }
      for each(var n in contents.results..table.(@['class']=="profile addresslist")..tr) {
          profile.addresses.address +=
          <address>
              <name>{n.th.*.text().toString()}</name>
              <value>{n.td.*.text().toString()}</value>
          </address>;
      }
      for each(var n in contents.results..ul.(@['class']=="hatena-fotolife floatlist")[0]..a) {
          profile.services.service +=
          <service>
              <name>{n.img.@['title'].toString()}</name>
              <url>{n.@['href'].toString()}</url>
          </service>
      }
      response.object = profile;
      ]]></execute>
    </select>
  </bindings>
</table>
あとはこれをYQLから指定してやれば良い事になります。
実際にはここで試す事が出来ます。

さてこれでAPIが出来上がったので、APIを使ったサンプルを書いてみましょう。
$(function() {
    $('#hatena-profile').submit(function() {
        $('#hatena-profile-ajaxloader').attr('src', 'http://mattn.kaoriya.net/images/ajax-loader.gif').show();
        var query = "select * from hatena.profile where id = '" + $('#hatena-id').val() + "'";
        var envurl = "http://mattn.jp.googlepages.com/alltables.env";
        $.getJSON('http://query.yahooapis.com/v1/public/yql?q=' + encodeURIComponent(query) + '&format=json&env=' + encodeURIComponent(envurl) + '&callback=?', function(data) {
            $('#hatena-profile-ajaxloader').hide();
            $('#hatena-profile-user').text(data.query.results.profile.user);
            $('#hatena-profile-nickname').text(data.query.results.profile.nickname);
            $('#hatena-profile-medals').html(data.query.results.profile.medals.medal.join('<br />'));
            $.each(data.query.results.profile.addresses.address, function() {
                var html = $('#hatena-profile-addresses').html();
                $('#hatena-profile-addresses').html(html + this.name + ':' + this.value + '<br />');
            });
            $.each(data.query.results.profile.services.service, function() {
                var html = $('#hatena-profile-services').html();
                $('#hatena-profile-services').html(html + this.name + ':' + this.url + '<br />');
            });
            $('#hatena-profile-info').show('slow');
        });
        return false;
    });
});
簡単にプロフィール情報を表示するだけの物です。
以下実行例です。

続きを読む...

Posted at 00:08 in web
Tagged as: xml, yql
Bookmarks: add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip

2008/10/27


このエントリーをはてなブックマークに追加
以前、「はてなブックマークをPlaggerで同期する際の注意点」という記事で書いたXML::Feed::Atomが複数のsubjectを返せない件で、送ったパッチが取り込まれました。厳密にはちょっと変えられてますが。
はてなブックマークのRSSフィードにはblockquote引用文が含まれており、Atomを使った方が良いという記事でしたが難点としてPlaggerで使用しているXML::Feed::Atomには複数のsubjectつまりタグを返せる仕組みがなかった為、別のソーシャルブックマークへポストした際にタグが削られてしまう事になっていました。
簡単な例で言うと
use strict;
use warnings;
use XML::Feed;
my $feed = XML::Feed->parse(URI->new('http://b.hatena.ne.jp/mattn/atomfeed'))
    or die XML::Feed->errstr;
for my $entry ($feed->entries) {
  print $entry->link."\n";
  print "[$_]" for $entry->category;
  print $entry->summary->{body};
  print "\n";
}
というコードに対して、以前までのXML::Feed::Atomだと
http://b.hatena.ne.jp/mattn/20081024#bookmark-10541285
[coderepos]
http://b.hatena.ne.jp/mattn/20081024#bookmark-10541123
[coderepos]
http://b.hatena.ne.jp/mattn/20081024#bookmark-10538485
[wsgi]
http://b.hatena.ne.jp/mattn/20081024#bookmark-10534904
[googleappengine]
http://b.hatena.ne.jp/mattn/20081024#bookmark-10531400
[]てかRSS意識しすぎw そんなに気になるか。
http://b.hatena.ne.jp/mattn/20081024#bookmark-7152814
[webfont]
http://b.hatena.ne.jp/mattn/20081024#bookmark-10530774
[vimperator]Include this patch f*ck でok
http://b.hatena.ne.jp/mattn/20081023#bookmark-10527748
[perl]
http://b.hatena.ne.jp/mattn/20081023#bookmark-10529111
[chrome]
http://b.hatena.ne.jp/mattn/20081023#bookmark-10522215
[catalyst]
http://b.hatena.ne.jp/mattn/20081023#bookmark-10520297
[dojo]
http://b.hatena.ne.jp/mattn/20081023#bookmark-10516769
[igoogle]
http://b.hatena.ne.jp/mattn/20081023#bookmark-10513534
[ruby]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10512893
[merb]merbでscaffold
http://b.hatena.ne.jp/mattn/20081022#bookmark-10512804
[merb]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10512802
[merb]
http://b.hatena.ne.jp/mattn/20081022#bookmark-9671938
[merb]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10512759
[merb]
http://b.hatena.ne.jp/mattn/20081022#bookmark-9696628
[merb]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10512388
[perl]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10498444
[lighttpd]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10504059
[]prototype.jsかぁ…。
http://b.hatena.ne.jp/mattn/20081022#bookmark-10502783
[]
http://b.hatena.ne.jp/mattn/20081021#bookmark-10489204
[vimperator]rcが先す
http://b.hatena.ne.jp/mattn/20081021#bookmark-10488856
[softbank]
http://b.hatena.ne.jp/mattn/20081021#bookmark-10484808
[blosxom]
http://b.hatena.ne.jp/mattn/20081021#bookmark-10488095
[]
http://b.hatena.ne.jp/mattn/20081020#bookmark-7686515
[テスト]TeSt
http://b.hatena.ne.jp/mattn/20081019#bookmark-10465795
[]
http://b.hatena.ne.jp/mattn/20081019#bookmark-10460236
[perl]
こんな形にタグが1つ以上出力されず、しかも空の配列として得られたりしていましたが、XML::Feed 0.22以降だと
http://b.hatena.ne.jp/mattn/20081024#bookmark-10541285
[coderepos][oops]
http://b.hatena.ne.jp/mattn/20081024#bookmark-10541123
[coderepos]
http://b.hatena.ne.jp/mattn/20081024#bookmark-10538485
[wsgi]
http://b.hatena.ne.jp/mattn/20081024#bookmark-10534904
[googleappengine][appengine]
http://b.hatena.ne.jp/mattn/20081024#bookmark-10531400
てかRSS意識しすぎw そんなに気になるか。
http://b.hatena.ne.jp/mattn/20081024#bookmark-7152814
[webfont]
http://b.hatena.ne.jp/mattn/20081024#bookmark-10530774
[vimperator]Include this patch f*ck でok
http://b.hatena.ne.jp/mattn/20081023#bookmark-10527748
[perl]
http://b.hatena.ne.jp/mattn/20081023#bookmark-10529111
[chrome]
http://b.hatena.ne.jp/mattn/20081023#bookmark-10522215
[catalyst]
http://b.hatena.ne.jp/mattn/20081023#bookmark-10520297
[dojo]
http://b.hatena.ne.jp/mattn/20081023#bookmark-10516769
[igoogle][google]
http://b.hatena.ne.jp/mattn/20081023#bookmark-10513534
[ruby][rails]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10512893
[merb]merbでscaffold
http://b.hatena.ne.jp/mattn/20081022#bookmark-10512804
[merb]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10512802
[merb]
http://b.hatena.ne.jp/mattn/20081022#bookmark-9671938
[merb]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10512759
[merb]
http://b.hatena.ne.jp/mattn/20081022#bookmark-9696628
[merb]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10512388
[perl][twitter][wassr]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10498444
[lighttpd]
http://b.hatena.ne.jp/mattn/20081022#bookmark-10504059
prototype.jsかぁ…。
http://b.hatena.ne.jp/mattn/20081022#bookmark-10502783

http://b.hatena.ne.jp/mattn/20081021#bookmark-10489204
[vimperator]rcが先す
http://b.hatena.ne.jp/mattn/20081021#bookmark-10488856
[softbank]
http://b.hatena.ne.jp/mattn/20081021#bookmark-10484808
[blosxom]
http://b.hatena.ne.jp/mattn/20081021#bookmark-10488095

http://b.hatena.ne.jp/mattn/20081020#bookmark-7686515
[テスト]TeSt
http://b.hatena.ne.jp/mattn/20081019#bookmark-10465795

http://b.hatena.ne.jp/mattn/20081019#bookmark-10460236
[perl]
こんな感じの出力になります。 今回の修正により手放しで、はてなブックマークAtomフィードをdelicios等にポスト出来る様になります。

めでたしめでたし
Posted at 09:46 in ソフトウェア::lang::perl
Tagged as: atom, perl, xml
Bookmarks: add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip