Ruby 1.9.3 リファレンスマニュアル > ライブラリ一覧 > rssライブラリ
RSS を扱うためのライブラリです。
RSS ParserはRSS 0.9x/1.0/2.0, Atom 1.0 をサポートしていますが,RSS 0.90 はサポートしてません.ごめんなさい.
RSS のモジュールはそれぞれ、
をサポートしています。 ただし,Content モジュールは content:encoded しかサポートしていません.
RSS をパースしたい場合は RSS::Parser クラスを使います。 RSS::Parser.parse は String の RSSを パースします(使用するXMLパー サによっては File や IO オブジェクトなどでもパース可能です)。
をそれぞれ返します。パースした String が well formed な XML で無い場合は, 例外 RSS::NotWellFormedError が発生します。well formed な XML だが,RSS 0.9x/1.0/2.0, Atom のいずれでもない場合は,nil が 返ります.
例えば、RSS 1.0 をバリデーション付きでパースするには以下のよ うにします。ここで、変数 rss_source には RSS 1.0 形式の文 字列が格納されているものとします。
require 'rss' rss = RSS::Parser.parse(rss_source, true)
RSS::Parser.parse の第二引数は省略すると true が指定されたもの とみなされるので、これは以下のようにも書けます。
require 'rss' rss = RSS::Parser.parse(rss_source)
最初はバリデーション付きでパースして,valid ではない RSS だった 場合はバリデーション無しでパースするには以下のように書きます。
require 'rss' rss = nil begin rss = RSS::Parser.parse(rss_source) rescue RSS::InvalidRSSError rss = RSS::Parser.parse(rss_source, false) end
これは rss_source が RSS 0.9x/1.0/2.0 のどれか分からない時 にも有効です。RSS 1.0としてバリデー ション付きでパースし、バリデーションに失敗すると RSS 0.9x/(壊 れた)1.0/2.0 のいずれかとしてパースします。
パーサはデフォルトでは知らない要素(仕様書に規定されていない 要素)を無視します。もし、知らない要素に遭遇した時に例外を発 生させたければ,RSS::Parser.parse の第三引数に false を指定して ください。こうすると、パース中に知らない要素に遭遇した時に RSS::UnknownTagError 例外が発生します。RSS::UnknownTagError クラスは RSS::InvalidError クラスのサブクラスです。
以下のようにすると,より厳密にパースできます。
RSS::Parser.parse(rss_source, true, false)
フィードをパースすると RSS::RDF, RSS::RDF::Channel, RSS::Rss, RSS::Atom::Feed 等のオブジェクトになります。 各オブジェクトで子要素オブジェクトにアクセスするために,要素名と 同じ名前のアクセサがあります。
rdf:RDF 要素の子要素である channel 要素を参照するには,以下のよう にします。
rss = RSS::Parser.parse(rss_source) rss.channel # => /rdf:RDF/channel要素; RSS::RDF::Channel
もし、要素が子要素も属性も持たない場合は String が返ってきます。 その要素が省略可能ならばnilが返って来るかもしれません。これ は要素が子要素または属性を持つ場合も同様です。
rss = RSS::Parser.parse(rss_source) rss.channel.description # => /rdf:RDF/channel/text(); String
属性にアクセスする時も同様です。channel要素のrdf:about属性に アクセスするには以下のようにします。属性の値はStringかnilで す。
rss = RSS::Parser.parse(rss_source) rss.channel.about # => /rdf:RDF/channel/@about属性; String または nil
同名の複数の子要素があるかもしれないときも同様です。ただし、 リーダに引数を指定しないと最初の子要素が返ってきます。例えば、 rdf:RDF要素の最初のitem要素にアクセスするには以下のようにし ます。
rss = RSS::Parser.parse(rss_source) rss.item # => /rdf:RDF/item[1]要素; RSS::RDF::Item
3番目のitem要素にアクセスするには以下のようにします。省略さ れなかったリーダの引数はArray#[]の引数と同じように扱われます。
rss = RSS::Parser.parse(rss_source) rss.item(2) # => /rdf:RDF/item[3]要素; RSS::RDF::Item
子要素すべてを取得したいときは要素名の複数形がリーダとなりま す。すべてのitem要素を取得するには以下のようにします。
rss = RSS::Parser.parse(rss_source) rss.items # => /rdf:RDF/item要素の配列; [RSS::RDF::Item, ...]
rdf:RDF要素の子要素であるchannel要素を設定するには以下のよう にします。RSS::RDF::Channel.newの第一引数にはrdf:about属性の 値を指定することもできます。
rss = RSS::Parser.parse(rss_source) rss.channel = RSS::RDF::Channel.new(rdf_about_value)
属性値を設定する場合も同様です。
rss = RSS::Parser.parse(rss_source) rss.channel.about = "http://cozmixng.www.cozmixng.org/"
同名の複数の子要素が存在する場合は少し異なります.要素名の複 数形でのメソッドで要素の配列を取得して,その配列に対して Array#<<やArray#[]=などを用いて要素を設定します.
rss = RSS::Parser.parse(rss_source) item = RSS::RDF::Item.new(rdf_about_value) rss.items << item rss.items.last == item # => true
注意: item=/set_itemなどはRubyっぽくないので使わないでくださ い.
RSS Parserといっているので誤解されがちですが,RSS/Atomを出力する こともできます.
to_sするとRSSまたはAtom形式の文字列を返します.
RSS/Atomを出力する流れは以下のようになります.
xml-stylesheetも出力することができます.
RSSのルート要素(RSS::RDFまたはRSS::Rss)オブジェ クトおよびAtomのルート要素(RSS::Atom::Feedまたは RSS::Atom::Entry)オブジェクトはxml_stylesheetsという 名前の配列を持っています.この配列にRSS::XMLStyleSheet オブジェクトを挿入することでRSS/Atomにxml-stylesheetを 関連づけることができます.
rss.xml_stylesheets << RSS::XMLStyleSheet.new(...)
RSS::XMLStyleSheet.newには以下のようなHashまたは 連想配列を渡します.作成されるRSS::XMLStyleSheetオブジェ クトは与えられた引数によって初期化されます.
Hash:
{ :href => "...", :type => "...", :title => "...", :media => "...", :charset => "...", :alternate => "...", }
連想配列:
[ [:href, "..."], [:type, "..."], [:title, "..."], [:media, "..."], [:charset, "..."], [:alternate, "..."], ]
全てのキーは省略可能です.
例えば,xml-stylesheetとしてsample.xslを指定する場合は以下の ようにします.
rss.xml_stylesheets << RSS::XMLStyleSheet.new({:href => "sample.xsl"})
本当は{:type => "text/xsl"}も指定しないといけないとこ ろですが,拡張子が.xslまたは,.cssの場合は適当に推測してくれ るので省略可能です.
既存のRSS/Atomをパースせずに,一から新しくRSS/Atomを作成するには RSS Makerが便利です.
以下のように使います.
require "rss" rss = RSS::Maker.make("バージョン") do |maker| maker.XXX = YYY ... end
例えば,
を生成するには以下のようにします.
require "rss" rss = RSS::Maker.make("1.0") do |maker| maker.channel.about = "http://example.com/index.rdf" maker.channel.title = "Example" maker.channel.description = "Example Site" maker.channel.link = "http://example.com/" end
もし,
エントリを含めたければ以下のようにします.
require "rss" rss = RSS::Maker.make("1.0") do |maker| maker.channel.about = "http://example.com/index.rdf" maker.channel.title = "Example" maker.channel.description = "Example Site" maker.channel.link = "http://example.com/" item = maker.items.new_item item.link = "http://example.com/article.html" item.title = "Sample Article" end
もし,先のエントリが
のものならこうします.
require "rss" rss = RSS::Maker.make("1.0") do |maker| maker.channel.about = "http://example.com/index.rdf" maker.channel.title = "Example" maker.channel.description = "Example Site" maker.channel.link = "http://example.com/" maker.items.new_item do |item| item.link = "http://example.com/article.html" item.title = "Sample Article" item.date = Time.parse("2004/11/1 10:10") end end
サンプル中の
item.date = ...
は
item.dc_date = ...
でも構いません.#dc_date=は#date=の単なる別名で す.
さらに,
エントリを持つなら以下のようにします.
require "rss" rss = RSS::Maker.make("1.0") do |maker| maker.channel.about = "http://example.com/index.rdf" maker.channel.title = "Example" maker.channel.description = "Example Site" maker.channel.link = "http://example.com/" maker.items.new_item do |item| item.link = "http://example.com/article.html" item.title = "Sample Article" item.date = Time.parse("2004/11/1 10:10") end maker.items.new_item do |item| item.link = "http://example.com/article2.html" item.title = "Sample Article2" item.date = Time.parse("2004/11/2 10:10") end end
もし,更新日が新しい順に並び替えたければ
maker.items.do_sort = true
を追加し,以下のようにします.
require "rss" rss = RSS::Maker.make("1.0") do |maker| maker.channel.about = "http://example.com/index.rdf" maker.channel.title = "Example" maker.channel.description = "Example Site" maker.channel.link = "http://example.com/" maker.items.do_sort = true maker.items.new_item do |item| item.link = "http://example.com/article.html" item.title = "Sample Article" item.date = Time.parse("2004/11/1 10:10") end maker.items.new_item do |item| item.link = "http://example.com/article2.html" item.title = "Sample Article2" item.date = Time.parse("2004/11/2 10:10") end end
もし,サイトに
がある場合は以下のようにします.
require "rss" rss = RSS::Maker.make("1.0") do |maker| maker.channel.about = "http://example.com/index.rdf" maker.channel.title = "Example" maker.channel.description = "Example Site" maker.channel.link = "http://example.com/" maker.items.do_sort = true maker.items.new_item do |item| item.link = "http://example.com/article.html" item.title = "Sample Article" item.date = Time.parse("2004/11/1 10:10") end maker.items.new_item do |item| item.link = "http://example.com/article2.html" item.title = "Sample Article2" item.date = Time.parse("2004/11/2 10:10") end maker.image.title = "Example Site" maker.image.url = "http://example.com/logo.png" end
もし,
検索用ページがあったら以下のようにします.
require "rss" rss = RSS::Maker.make("1.0") do |maker| maker.channel.about = "http://example.com/index.rdf" maker.channel.title = "Example" maker.channel.description = "Example Site" maker.channel.link = "http://example.com/" maker.items.do_sort = true maker.items.new_item do |item| item.link = "http://example.com/article.html" item.title = "Sample Article" item.date = Time.parse("2004/11/1 10:10") end maker.items.new_item do |item| item.link = "http://example.com/article2.html" item.title = "Sample Article2" item.date = Time.parse("2004/11/2 10:10") end maker.image.title = "Example Site" maker.image.url = "http://example.com/logo.png" maker.textinput.title = "Search Example Site" maker.textinput.description = "Search Example Site's all text" maker.textinput.name = "keyword" maker.textinput.link = "http://example.com/search.cgi" end
もし,
xml-stylesheetを追加したい場合は以下のようにします.
require "rss" rss = RSS::Maker.make("1.0") do |maker| xss = maker.xml_stylesheets.new_xml_stylesheet xss.href = "http://example.com/index.xsl" maker.channel.about = "http://example.com/index.rdf" maker.channel.title = "Example" maker.channel.description = "Example Site" maker.channel.link = "http://example.com/" maker.items.do_sort = true maker.items.new_item do |item| item.link = "http://example.com/article.html" item.title = "Sample Article" item.date = Time.parse("2004/11/1 10:10") end maker.items.new_item do |item| item.link = "http://example.com/article2.html" item.title = "Sample Article2" item.date = Time.parse("2004/11/2 10:10") end maker.image.title = "Example Site" maker.image.url = "http://example.com/logo.png" maker.textinput.title = "Search Example Site" maker.textinput.description = "Search Example Site's all text" maker.textinput.name = "keyword" maker.textinput.link = "http://example.com/search.cgi" end
もし,RSS 2.0を生成したい場合は以下のように, RSS::Maker.makeの第一引数を変更します.
require "rss" rss = RSS::Maker.make("2.0") do |maker| xss = maker.xml_stylesheets.new_xml_stylesheet xss.href = "http://example.com/index.xsl" maker.channel.about = "http://example.com/index.rdf" maker.channel.title = "Example" maker.channel.description = "Example Site" maker.channel.link = "http://example.com/" maker.items.do_sort = true maker.items.new_item do |item| item.link = "http://example.com/article.html" item.title = "Sample Article" item.date = Time.parse("2004/11/1 10:10") end maker.items.new_item do |item| item.link = "http://example.com/article2.html" item.title = "Sample Article2" item.date = Time.parse("2004/11/2 10:10") end maker.image.title = "Example Site" maker.image.url = "http://example.com/logo.png" maker.textinput.title = "Search Example Site" maker.textinput.description = "Search Example Site's all text" maker.textinput.name = "keyword" maker.textinput.link = "http://example.com/search.cgi" end
もし,RSS 0.91を生成したい場合は,RSS 2.0の場合と同様に RSS::Maker.make の第一引数を"0.91"に変更します.
ただし、RSS 0.91では言語指定が必須なので、言語を指定する必要 があります。ここでは日本語であると指定します。
rss = RSS::Maker.make("0.91") do |maker| maker.channel.language = "ja" ... end
RSS 1.0など、/rdf:RDF/channel/language要素がないフィードの場 合でも、単に無視したりdc:languageとして扱ったりと適切に処理し ます。そのため、以下のように「バージョンが"0.91"のとき だけ言語を指定する」というように書く必要はありません。 フィードのバージョンに関わらず言語を指定してください。
rss = RSS::Maker.make("0.91") do |maker| maker.channel.language = "ja" if maker.feed_version == "0.91" ... end
もし,Atom 1.0を生成したい場合は,RSS 0.91や2.0の場合と同様に RSS::Maker.makeの第一引数を"atom"に変更します.
rss = RSS::Maker.make("atom") do |maker| ... end
ただし、これだけでは十分ではありません。これは、Atom 1.0では RSS 1.0/2.0/0.91では必須ではなかった以下の情報が必要となるか らです。
よって、これまでのスクリプトをAtom 1.0を出力するようにするた めには以下のように変更する必要があります。
もし、
Atom 1.0なら以下のようになります。
require "rss" atom = RSS::Maker.make("atom") do |maker| xss = maker.xml_stylesheets.new_xml_stylesheet xss.href = "http://example.com/index.xsl" maker.channel.about = "http://example.com/atom.xml" maker.channel.title = "Example" maker.channel.description = "Example Site" maker.channel.link = "http://example.com/" maker.channel.author = "Bob" maker.channel.date = Time.now maker.items.do_sort = true maker.items.new_item do |item| item.link = "http://example.com/article.html" item.title = "Sample Article" item.date = Time.parse("2004/11/1 10:10") end maker.items.new_item do |item| item.link = "http://example.com/article2.html" item.title = "Sample Article2" item.date = Time.parse("2004/11/2 10:10") end maker.image.title = "Example Site" maker.image.url = "http://example.com/logo.png" maker.textinput.title = "Search Example Site" maker.textinput.description = "Search Example Site's all text" maker.textinput.name = "keyword" maker.textinput.link = "http://example.com/search.cgi" end
Atom 1.0用の情報を加えたこのスクリプトを最初のRSS 1.0を出 力するスクリプトに戻す場合は、RSS::Maker.makeの第一引 数を"1.0"に変えるだけです。Atom 1.0用に追加した情報を 削除する必要はありません。それらは単に無視されます。
フィードの種類がRSS 1.0/2.0でもAtomでもパースするためのAPIは 以下のように共通です。
feed = RSS::Parser.parse(feed_xml)
しかし、返ってくるオブジェクトはRSS 1.0オブジェクト (RSS::RDF)かもしれませんし、Atomオブジェクト (RSS::Atom::Feed)かもしれません。このため、パースした結果 を使う場合はフィードの種類を意識しなくてはならず、 使いづらくなります。
RSS Parserが提供する解決方法はユーザに好みのフィードの種類を 選んでもらうというものです。例えば、以下のようにしてRSS 1.0 をRSS 2.0に変換することができます。
require 'rss' rss10 = RSS::Parser.parse(rss10_xml) rss20 = rss10.to_feed("rss2.0")
種類がわからない複数のフィードを扱う場合は以下のようにし、す べてのフィードをRSS 2.0のように扱うことができます。
feeds.each do |xml| rss20 = RSS::Parser.parse(xml).to_feed("rss2.0") ... end
また、to_feedは以下のように書くことも出来ます。
feed.to_rss("1.0") # == feed.to_feed("rss1.0") feed.to_rss("2.0") # == feed.to_feed("rss2.0") feed.to_atom("1.0") # == feed.to_feed("atom1.0")
形式を変換したときに問題になるのは、変換元のオブジェクトが変 換後の形式に必須の情報を持っていない場合です。この場合は変換 に失敗します(RSS::Errorのサブクラスの例外が発生します)。そ のため、適宜、必要な情報を補う必要があります。たとえば、RSS 1.0では各item要素にタイトルが必須ですが、RSS 2.0では省略可能 です。そのような場合に対応するために、以下のようにブロックを 使用することが出来ます。
rss10 = feed.to_rss("1.0") do |maker| maker.items.each do |item| item.title.content ||= "No title" end end
to_feedのブロック内で出来ることを理解するためには、to_feedが どのように動作するかを理解するとよいです。パース結果のオブジェ クトはフィードの種類に関わらずsetup_makerというメソッドを持っ ています。これは、自分が持っている情報をRSS Makerに与えるメソッ ドです。to_feedはRSS::Maker.makeで作ったRSS Makerに対して setup_makerを行い、他の形式に変換しようとします。ブロックには setup_makerを行った後のRSS Makerが渡されます。つまり、 to_feedのブロック内で出来ることはRSS Makerに対して出来ること と同じです。
上記の方法でパース済みのオブジェクトを変換できるので、フィー ドを異なる形式のXMLへ変換することは簡単です。
feed = RSS::Parser.parse(feed_xml) new_feed_xml = feed.to_feed("atom1.0").to_s
これを行うための便利なメソッドto_xmlがあります。to_xmlを使う と以下のように書き直すことができます。
feed = RSS::Parser.parse(feed_xml) new_feed_xml = feed.to_xml("atom1.0")
。。。あまり変わりませんね。to_feedを用いた場合と同じように ブロックを指定してRSS Makerを操作することも出来ます。ますま す変わりませんね。
to_feed().to_sではなく、to_xmlを使うことには一長一短がありま す。to_xmlは変換元のフィードの種類と変換後のフィードの種類が 同じ場合は単にto_sを呼び出すだけです。これにより、同じ形式に 変換する場合の速度があがります(RSS Makerを作って変換、という ことを省略するので当然です)。しかし、ブロックを指定して変換 後の結果を調整することができません。例えば、以下のようにRSS 1.0からRSS 2.0に変換する場合はブロックが呼ばれます。
rss10.to_xml("rss2.0") do |maker| # makerを操作できる end
しかし、以下のようにRSS 1.0からRSS 1.0に変換しようとした場合 はブロックは呼び出されません。
rss10.to_xml("rss1.0") do |maker| # ブロックが呼び出されないのでmakerを操作できない。 end
このAPIに関しては、どうしたらよいのかまだ悩んでいます。もし、 なにかアイディアがあれば教えてください。
RSS Parser のサンプルスクリプトをいくつか紹介します.これらの スクリプトは sample/ 以下に入っています.
それでは、複数のフィードをパースしてitem要素を表示するスクリプト を書いてみましょう。
まず、RSS 0.9x/1.0/2.0, Atom 1.0をパースできるように以下のようにrequireします。
require 'rss'
パースするフィードはファイルに保存されていて引数で与えられるものとします。
ARGV.each do |fname| feed = nil begin feed = RSS::Parser.parse(File.read(fname), false) rescue RSS::Error end if feed.nil? puts "#{fname}はRSS 0.9x/1.0/2.0, Atom 1.0のいずれでもありません。" else print_items(feed) end end
あとはprint_itemsというメソッドを定義するだけです。
RSS::RDF/RSS::Rss/RSS::Atom::Feed/RSS::Atom::Entryには便利の ためにitemsというメソッドとimageというメソッドが定義されています。
itemsはそれぞれ以下を返します。
imageはそれぞれ以下を返します。
ここでは、itemsを使って各項目を表示します。
def print_items(feed) feed.items.each do |item| puts "#{item.title} : #{item.description}" end end
これは、RSSフィードに対してはうまく動きますが、Atomフィードに 対してはうまく動きません。それはAtomフィードにはdescription要 素がないからです。そこで、AtomフィードもRSSフィードに変えて 扱うことにします。
def print_items(feed) convert_to_rss10(feed).items.each do |item| puts "#{item.title} : #{item.description}" end end
convert_to_rss10は以下のようになります。
def convert_to_rss10(feed) feed.to_rss("1.0") do |maker| maker.channel.about ||= maker.channel.link maker.channel.description.content ||= "No description" maker.items.each do |item| item.title.content ||= "No title" item.link ||= "UNKNOWN" end end end
未設定の可能性がある要素にデフォルト値を設定しています。
出力する文字コードを変更するにはoutput_encoding=が使えます。 もし、変換できないエンコーディングを指定された場合は RSS::UnknownConversionMethodError例外が発生します。
先程のprint_itemsをEUC-JPで出力するように書き換えてみましょう。
def print_items(feed) rss10 = convert_to_rss10(feed) begin rss10.output_encoding = "EUC-JP" rescue RSS::UnknownConversionMethodError end rss10.items.each do |item| puts "#{item.title} : #{item.description}" end end
次はDublin Coreモジュールのdate属性を使って更新順にitemを表 示してみましょう。
最初に現れたDublin Coreモジュールの要素にアクセスするには 「dc_要素名」というアクセサが用意されています。全ての要素の 配列にアクセスするには「dc_要素の複数形」(dc_rightsは dc_rights_listになります)とします.
複数形でアクセスした場合は「要素の内容を表す文字列」ではなく, 「要素を表すオブジェクト」の配列が返ります.「要素を表すオブ ジェクト」から「要素の内容を表す文字列」を取得するには contentメソッドやその別名であるvalueメソッドを利 用します.「要素の内容を表す文字列」を設定するには content=メソッドやその別名であるvalue=メソッドを 利用します.
rss.channel.dc_title # => 「要素の内容を表す文字列」 # ("My site"など) rss.channel.dc_titles # => 「要素を表すオブジェクト」の配列 # ([DublinCoreTitleオブジェクト, ...]) rss.channel.dc_titles.collect {|title| title.value} # => 「要素の内容を表す文字列」の配列 # (["My site", ...]など) rss.channel.dc_titles.first.value == rss.channel.dc_title # => true # 厳密にはこう first_title = rss.channel.dc_titles.first first_title = first_title.value if first_title first_title == rss.channel.dc_title # => true
ちなみにSyndicationモジュールの要素にアクセスするには「sy_要 素名」というアクセサが,Contentモジュールの要素にアクセスす るには「content_要素名」というアクセサが用意されています。
サンプル1と同じようにパースするRSSはファイルに保存されていて 引数で与えられるものとします。
require 'rss' items = [] ARGV.each do |fname| rss_source = nil File.open(fname) do |f| rss_source = f.read end rss = nil begin rss = RSS::Parser.parse(rss_source, true) rescue RSS::Error end if rss.nil? puts "#{fname}はRSS 1.0ではありません。" else begin rss.output_encoding = "euc-jp" rescue RSS::UnknownConversionMethodError end rss.items.each do |item| items << item if item.dc_date end end end print_items(items)
あとはprint_itemsというメソッドを定義するだけです。
Item#dc_dateはTimeオブジェクトかnilを返します。引数の itemsにはdc_dateがnilではないものしか含まれていないは ずなので以下のようにソートできます。
def print_items(items) items.sort do |x, y| y.dc_date <=> x.dc_date end.each do |item| puts "#{item.dc_date.localtime.iso8601} : #{item.title} : #{item.description}" end end
RSS::Rss::Channel#generator
RSS::Rss::Channel#ttl
RSS::Rss::Channel::Item#author
RSS::Rss::Channel::Item#comments
RSS::Rss::Channel::Item#guid
RSS::Rss::Channel::Item#pubDate