# -*- rd -*-

= Tutorial.ja

$Id: Tutorial.ja 316 2004-02-13 10:10:04Z kou $

== require

RSS 1.0を扱う場合は

  require 'rss/1.0'

を記述してください。

RSS 0.9xを扱う場合は

  require 'rss/0.9'

を記述してください。

RSS 2.0を扱う場合は

  require 'rss/2.0'

を記述して下さい．

これらは複数記述する事ができます。つまり，RSS 0.9x/1.0を扱いたい場合は

  require 'rss/0.9'
  require 'rss/1.0'

として下さい．RSS 0.9x/1.0/2.0を扱いたい場合は

  require 'rss/1.0'
  require 'rss/2.0'

として下さい．RSS 2.0はRSS 0.9xを含んでいるので

  require 'rss/0.9'

を記述する必要はありません．

また、必要に応じて

  require 'rss/dublincore'

や

  require 'rss/syndication'

や

  require 'rss/content'

や

  require 'rss/trackback'

も記述してください。これらは、それぞれ((<Dublin
Core|URL:http://web.resource.org/rss/1.0/modules/dc/>))モジュー
ル、
((<Syndication|URL:http://web.resource.org/rss/1.0/modules/syndication/>))
モジュール，
((<Content|URL:http://web.resource.org/rss/1.0/modules/content/>))
モジュール，
(((<TrackBack|URL:http://madskills.com/public/xml/rss/module/trackback/>))
モジュールを使用可能にします。

ただし，Contentモジュールはcontent:encodedしかサポートしていません．

== パース

RSSをパースしたい場合はRSS::Parserクラスを使います。
RSS::Parser.parseはStringのRSSをパースします。RSS 1.0をパー
スした場合はRSS::RDFオブジェクトを、RSS 0.9x/2.0をパースした
場合はRSS::Rssオブジェクトを返します。パースしたStringがRSS
0.9x/1.0/2.0のいずれでもない場合はnilまたはRSS::Errorのサブ
クラスの例外が発生します。

例えば、RSS 1.0をバリデーション付きでパースするには以下のよ
うにします。ここで、変数(({rss_source}))にはRSS 1.0形式の文
字列が格納されているものとします。

  require 'rss/1.0'
  rss = RSS::Parser.parse(rss_source, true)

RSS::Parser.parseの第二引数は省略するとtrueが指定されたもの
とみなされるので、これは以下のようにも書けます。

  require 'rss/1.0'
  rss = RSS::Parser.parse(rss_source)

最初はバリデーション付きでパースしてvalidではないRSSだった場
合はバリデーション無しでパースするには以下のように書きます。

  require 'rss/1.0'
  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のいずれかとしてパースします。

  require 'rss/1.0'
  require 'rss/2.0'
  rss = nil
  begin
    rss = RSS::Parser.parse(rss_source)
  rescue RSS::InvalidRSSError
    rss = RSS::Parser.parse(rss_source, false)
  end


Dublin Core，Syndication，Content，TrackBackモジュールの要素
もパースしたい場合はRSS::Parser.parseの前にrss/dublincore,
rss/syndication, rss/content, rss/trackbackをrequireしてくだ
さい。

  require 'rss/1.0'
  require 'rss/2.0'
  require 'rss/dublincore'
  require 'rss/syndication'
  require 'rss/content'
  require 'rss/trackback'
  rss = nil
  begin
    rss = RSS::Parser.parse(rss_source)
  rescue RSS::InvalidRSSError
    rss = RSS::Parser.parse(rss_source, false)
  end

=== 知らない要素の扱い

パーサはデフォルトでは知らない要素(仕様書に規定されていない
要素)を無視します。もし、知らない要素に遭遇したら例外を発生
させたければRSS::Parser.parseの第三引数にfalseを指定してくだ
さい。こうすると、パース中に知らない要素に遭遇した時に
RSS::UnknownTagError例外が発生します。RSS::UnknownTagErrorク
ラスはRSS::InvalidErrorクラスのサブクラスです。

以下のようにするとより厳密にRSS 1.0をパースできます。

  RSS::Parser.parse(rss_source, true, false)

== パースされたRSS

RSSはパースされるとStringオブジェクトからRSS::RDF,
RSS::RDF::Channel, RSS::Rss等のオブジェクトになります。各オ
ブジェクトで子要素オブジェクトにアクセスするために要素名と同
じ名前のアクセサがあります。

=== リーダ(reader)

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.channel.item # => /rdf:RDF/item[1]要素; RSS::RDF::Channel::Item

3番目のitem要素にアクセスするには以下のようにします。省略さ
れなかったリーダの引数はArray#[]の引数と同じように扱われます。

  rss = RSS::Parser.parse(rss_source)
  rss.channel.item(2) # => /rdf:RDF/item[3]要素; RSS::RDF::Channel::Item

子要素すべてを取得したいときは要素名の複数形がリーダとなりま
す。すべてのitem要素を取得するには以下のようにします。

  rss = RSS::Parser.parse(rss_source)
  rss.channel.items # => /rdf:RDF/item要素の配列; [RSS::RDF::Channel::Item, ...]

=== ライタ(writer)

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/"

同名の複数の子要素が存在する場合も同様です。ただし、引数が1
つだけだと、追加されます。

  rss = RSS::Parser.parse(rss_source)
  item = RSS::RDF::Item.new(rdf_about_value)
  rss.item = item
  rss.item(-1) == item # => true

引数が2つ以上あるとArray#[]=と同じように処理されます。

  rss = RSS::Parser.parse(rss_source)
  item = RSS::RDF::Item.new(rdf_about_value)
  rss.send(:item=, 0, item)
  rss.item == item # => true

別名として「set_要素名」も用意されているのでObject#sendを使
わずにそちらを使ってもいいでしょう。

  rss = RSS::Parser.parse(rss_source)
  item = RSS::RDF::Item.new(rdf_about_value)
  rss.set_item(0, item)
  rss.item == item # => true

== サンプル1 - 項目一覧

それでは、複数のRSSをパースしてitem要素を表示するスクリプト
を書いてみましょう。

まず、RSS 0.9x/1.0/2.0をパースできるように以下のようにrequireします。

  require 'rss/1.0'
  require 'rss/2.0'

パースするRSSはファイルに保存されていて引数で与えられるものとします。

  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, false)
    rescue RSS::Error
    end

    if rss.nil?
      puts "#{fname}はRSS 0.9x/1.0/2.0のいずれでもありません。"
    else
      print_items(rss)
    end
  end

あとはprint_itemsというメソッドを定義するだけです。

RSS::RDFとRSS::Rssには便利のためにitemsというメソッドとimage
というメソッドが定義されています。

itemsはRSS::RDFの場合は全ての/rdf:RDF/item要素を配列で返しま
す。RSS::Rssの場合は全ての/rss/channel/item要素を配列で返し
ます。

imageはRSS::RDFの場合は/rdf:RDF/image要素を返します。
RSS::Rssの場合は/rss/channel/image要素を返します。

  def print_items(rss)
    rss.items.each do |item|
      puts "#{item.title} : #{item.description}"
    end
  end

出力する文字コードを変更するにはRSS::RDF#output_encoding=ま
たはRSS::Rss#output_encoding=が使えます。もし、変換できない
エンコーディングを指定された場合は
RSS::UnknownConversionMethodError例外が発生します。

先程のprint_itemsをEUC-JPで出力するように書き換えてみましょう。

  def print_items(rss)
    begin
      rss.output_encoding = "EUC-JP"
    rescue RSS::UnknownConversionMethodError
    end
    rss.items.each do |item|
      puts "#{item.title} : #{item.description}"
    end
  end



== サンプル2 - 更新順表示

次はDublin Coreモジュールのdate属性を使って更新順にitemを表
示してみましょう。

Dublin Coreモジュールの要素にアクセスするには「dc_要素名」と
いうアクセサが用意されています。ちなみにSyndicationモジュー
ルの要素にアクセスするには「sy_要素名」というアクセサが，
Contentモジュールの要素にアクセスするには「content_要素名」
というアクセサが用意されています。

requireするのは以下のものです。

  require 'rss/1.0'
  require 'rss/dublincore'

サンプル1と同じようにパースする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
