最初に結論

XMLで結果を吐き出すREST APIをRubyで用いたいなら、かなり役立つんじゃないかと思います。

sax-machineって?

XMLを、例えば普通にNokogiriなんかでパースすると、

1
2
api = Nokogiri::XML.parse(open(URI.encode "http://ws.audioscrobbler.com/2.0/?method=track.search&track=夏&api_key=#{API_KEY}&limit=5"))
puts api.search("/lfm/results/trackmatches/track[1]/name").inner_text

みたいなすごいことになるので、せめて

1
puts api.tracks[0].name

みたいには書けないか? そんな夢を簡単に叶えるのが sax-machine ライブラリです。

準備

gemを入れる。Nokogiriに依存するので、環境によってはまずlibxml2-dev(el)とlibxslt-dev(el)も入れないといけません。

1
2
sudo aptitude install libxml2-dev libxslt-dev # e.g. Ubuntu/Debian
sudo gem install sax-machine

今回はLast.fm APIで試すので、Last.fmにユーザ登録し、API KEYを取得する。API Accountへ。

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
require 'rubygems'
require 'sax-machine'
require 'open-uri'
 
module Lastfm
  class Track
    include SAXMachine
    element :name
    element :artist
    element :url
    element :streamable
    element :listeners
    ["small", "medium", "large", "extralarge"].each do |size|
      element :image, :as => "image_#{size}", :with => {:size => size}
    end
  end
 
  class TrackInfo
    include SAXMachine
    element "opensearch:Query", :value => :searchTerms, :as => :query
    element "opensearch:Query", :value => :role, :as => :query
    element "opensearch:totalResults", :as => :total_results
    element "opensearch:startIndex", :as => :start
    element "opensearch:itemsPerPage", :as => :per_page
    elements :track, :as => :tracks, :class => Track
 
    class << self
      API_KEY  = "YOUR_API_KEY"
      PER_PAGE = 5
      def search(query, options={}) # 検索インタフェース
        page = (options[:page] || 1).to_i
        per_page = (options[:per_page] || PER_PAGE).to_i
        url = URI.encode( "http://ws.audioscrobbler.com/2.0/?" +
          "method=track.search&" + 
          "track=#{query}&" + 
          "api_key=#{API_KEY}&" +
          "limit=#{per_page}&" +
          "page=#{page}" )
        parse(open(url))
      end
    end
  end
end
 
lastfm = Lastfm::TrackInfo.search("夏")
#=> #<Lastfm::TrackInfo:0x7f8d664d97e8>
lastfm.total_results
#=> 380
lastfm.tracks.count
#=> 5
puts lastfm.tracks[0].name
# 夏・コイ
puts lastfm.tracks[0].artist
# いきものがかり

Last.fmのAPI解説ページからたどれる実際のXMLと、比較するといいと思います。

sax-machineでやれること

  • 要素の中身を取得する。
  • 要素の中身を別名をつけて取得する。
  • 要素の属性を取得する。それも、別々に取れる。
  • 特定の属性を持った要素の中身を取得する。
  • 複数の要素を取得する。
  • 入れ子された要素については、新たな、SAXMachineをincludeしたクラスに変換できる。
  • 不要な要素は無視できる。

なお、複数要素取得については、

1
2
3
4
5
6
7
<xml>
  <hoge />
  <fuga />
  <item>......</item>
  <item>......</item>
  <item>......</item>
</xml>

も、

1
2
3
4
5
6
7
8
9
<xml>
  <hoge />
  <fuga />
  <list>
    <item>......</item>
    <item>......</item>
    <item>......</item>
  </list>
</xml>

も、同じように elements :item, :as => :items で取得可能です。SAXだから階層の深さが関係ない(、ということのはず)です。

あと、要素の中身をIntegerにするとかDateTimeにするとかはできません。。

かなり使いでがいいです。まだRuby向けライブラリができてないような、REST/XMLベースのAPIを使う際には、試してみてはいかがでしょう?