Hatena::ブログ(Diary)

window.closed RSSフィード

2009-05-31 RSS Library ソースコード読解終了

RSS Parser の Listener について

| 23:05 | RSS Parser の Listener についてを含むブックマーク

前回 REXML::Document.parse_stream を調べたことから、tag_start、tag_end などが解析時に主な処理をしていることが分かりました。

そのため今回は tag_start から Rss オブジェクトが作られるところを解析したいと思います。

Rss オブジェクトはどのように作られるか?

parser.rb にある ListenerMixin の tag_start メソッドを見てみます。かなり端折ると下記のようになります。

Rss オブジェクトが作られる前に注目するため最初の if 節で何か行われると思われます。ここでしていることは、initial_start_#{local} というメソッドを呼び出しているのですが、ここで local は要素名を指しています。そのため rss 要素であれば、initial_start_rss メソッドが呼び出されるということを意味しています。ということは feed 要素であれば initial_start_feed メソッドが呼ばれることになります。そのためコード上は RSS2.0 なのか、Atom なのかを気にしなくてもよくなっています。Ruby のメタプログラミング恐るべし。。。

    def tag_start(name, attributes)
      ...
      prefix, local = split_name(name)
        ...
        if @rss.nil? and respond_to?("initial_start_#{local}", true)
          __send__("initial_start_#{local}", local, prefix, attrs, ns.dup)
        elsif respond_to?("start_#{local}", true)
          __send__("start_#{local}", local, prefix, attrs, ns.dup)
        else
          start_else_element(local, prefix, attrs, ns.dup)
        end
        ...
    end

では、initial_start_rss はどこで定義されているかというと、下記の通り 0.9.rb にあります。ここで Rss オブジェクトが生成されていることが分かります。

    def initial_start_rss(tag_name, prefix, attrs, ns)
      ...
      @rss = Rss.new(attrs['version'], @version, @encoding, @standalone)
      ...
      @last_element = @rss
      ...
    end

簡単ではありますが何となく、Rss オブジェクト作成過程までは理解できました。

では次にその他の要素はどのように生成されるのか理解したいと思います。。。

うーん、わからん。。。start_else_element ってのが怪しいとは思うんですが、一体何をしているのやら。どれが重要なコードなのかも分からない状態です。

古いバージョンがヒント。

あまりにも分からないので、恐らく重要な部分だけ書かれていたであろう初期のバージョンのコードを見てみることにします。

下記サイトより RSS Parser Lib の 0.0.4 を取得してきます。

http://www.cozmixng.org/~kou/download/

ソースコード量も少ないし、目論見通り Ruby をあまり理解していない私でも何とか理解できそうな感じです。

ソースコード読解時には古いコードというのも役に立つということが分かりました(もちろんこの限りではないですが)。

Channel や他の子要素はどこで作られるのか?

0.0.4 をベースに見てみたところ、やはり大きな流れは 0.2.3 でも変わらないようです。

まず基点は tag_start メソッド。これは敢えて言う必要も無いですね。

次に start_else_element メソッドへ処理がわたります。ここでは @last_element に格納されているオブジェクトの Class から、要素名に合致する Class を取得しています(const_get の部分)。例えば Channel 要素の tag_start であれば、@last_element には Rss オブジェクトが格納されているため、Rss Class より const_get することにより要素名に合致する Channel クラスを取得することができます。これが entry 要素であった場合は、Rss Class には Entry クラスが定義されていないためエラーとなります。クラス構造がそのままパースするときの正当性確認に使われているようです。すごい。

ここで const_get により取得された Class は、start_have_something_element メソッドへと渡されます。その後 setup_next_element メソッドにて klass.new されることでオブジェクトが生成されるようになっています。

その他の要素についても、上記で説明したようにクラス構造がそのまま解析のための情報となっているため、クラス構造を辿ることでオブジェクトの生成がされていくことになります。そのため start_else_element 中には Rss や Channel などに関する記載は全く無くとも Rss オブジェクトは無事生成されることとなります。これは Atom でも同様となります(atom.rb を参照)。かなり頭の良いやり方だと思います、すばらしいです。

まとめ

というわけで何回かに渡って、RubyRSS Parser Library ソースコード読解をしてきましたが、一通りの流れを掴んだ所で読解については終わりにしたいと思います。一通り理解してみて Ruby に少し詳しくなった気がしますので、今後何かしらプログラミングへ役立てて行きたいと思います。

2009-05-12 Tool 紹介

Command Prompt Explorer Bar インストール

| 01:14 | Command Prompt Explorer Bar インストールを含むブックマーク

Command Prompt Explorer Bar というかなり便利そうな Tool があったのでインストールしてみました。

エクスプローラ上にコマンドプロンプトが表示されるという優れもの。きっかけは以下の本を読んだことです。

Amazon.co.jp: プロダクティブ・プログラマ -プログラマのための生産性向上術 (Theory in practice): Neal Ford, 島田 浩二 (監訳), 夏目 大: 本


インストールは簡単で下記サイトへ行き、Binary ファイルを落としてインストールするのみです。

Command Prompt Explorer Bar - CodeProject

# ただし codeproject へメンバー登録する必要があるようです。


これは便利です。普段今いるディレクトリコマンドプロンプト使いたいなと思うことは良くあります。

例えば Windows の検索ではなく grep 使いたいときとか。

そんなときにこれがあればエクスプローラ開きつつ Ctrl+M することで、コマンドプロンプトが現在のディレクトリとなってエクスプローラ下部に表示されます。すばらしい。

参考サイト

dfltweb1.onamae.com ? このドメインはお名前.comで取得されています。

2009-05-11 REXML Parser

REXML::StreamParser の使い方

| 01:40 |  REXML::StreamParser の使い方を含むブックマーク

REXML Parser について簡単に調べてみます。

lib/rss/rexmlparser.rb にて REXML は下記のように使われています。

        REXML::Document.parse_stream(@rss, @listener)

これはストリーム型と呼ばれるパース方法で、パース後に tree が作られるのではなく、逐次 Listener へ解析結果が通知されるような形となります(SAX 型もたぶん一緒)。

よって調べるべきは Listener の方で、Listener では REXML::StreamListener を include していますので、これを調べれば使い方はある程度わかりそうです。

ということで下記サイトで調べてみました(須藤功平さんに感謝)。

http://pub.cozmixng.org/~kou/rexml-doc-ja/

目立つところでは REXML::StreamListener クラスは tag_start や tag_end といった通知系メソッドが定義されています。これらはサイトに記載がありますように、前者は要素(タグ)が現れたときに呼ばれ、後者は終了要素(タグ)が現れると呼ばれます。これがストリーム方式と言われている部分です。

# それ以外にもメソッドがありますが、何となくどんなことをしているのか判別できればよいためここではこれ以上深入りはしません。

では、試しに使ってみたいと思います。ネタは例によってこのサイトの RSS データにします。Listener を作って、それを REXML::Document.parse_stream に渡すだけです。tag_start, tag_end をオーバーライドして rss, channel, item 要素だった場合に Print するようにしてみました。

#!ruby

require 'rexml/document'
require 'rexml/streamlistener'
require 'rss'

url = "http://d.hatena.ne.jp/bazz/rss2"
rss_cont = open(url) { |u| u.read }

class RSSListener
  include REXML::StreamListener
  def tag_start(tag, attrs)
    case tag
    when "rss"
      s = ""
    when "channel"
      s = "  "
    when "item"
      s = "    "
    else
      return
    end
    print s + tag + "\n"
  end

  def tag_end(tag)
    case tag
    when "rss"
      s = ""
    when "channel"
      s = "  "
    when "item"
      s = "    "
    else
      return
    end
    print s + "/" + tag + "\n"
  end
end

listener = RSSListener.new
REXML::Document.parse_stream(rss_cont, listener)

結果は以下のようになります。

rss
  channel
    item
    /item
    item
    /item
    item
    /item
    item
    /item
    item
    /item
    item
    /item
    item
    /item
    item
    /item
  /channel
/rss

私でも、ものの数分でできてしまいました。Ruby 恐るべし。。。

では、REXML::StreamParser の使い方がわかったところで今日はおしまいにします。

RSS Lib でも、REXML::StreamParser を継承することで、rss 要素が始まったときや、channel 要素が始まったときなどに何らかの処理をすることで RSS 解析結果を蓄積していると考えられます。

参考サイト

REXML API ドキュメントの日本語版

http://pub.cozmixng.org/~kou/rexml-doc-ja/

2009-05-10 RSS Lib Parser 部分

RSS Lib の Parser について

| 00:48 | RSS Lib の Parser についてを含むブックマーク

内部で実際に使っている Parser は以下のどれかとなります。

以下 lib/rss/parser.rb より抜粋。

    AVAILABLE_PARSER_LIBRARIES = [
      ["rss/xmlparser", :XMLParserParser],
      ["rss/xmlscanner", :XMLScanParser],
      ["rss/rexmlparser", :REXMLParser],
    ]

require によりロードできたものを使用できるパーサとして登録していきます。ちなみに私の環境では irb で試してみたところ、REXML だけロードできるようです。これは REXML が標準の Ruby に含まれているためと思われます。他のものは Ruby 標準としては採用されていないようです(xmlparser および xmlscanner)。

それ以外のパーサを使う場合はインスタンス作成時に引数として ParserClass を渡す必要があります。

私の環境で動く REXML について引き続きコードを読んで行こうと思います。

前回の記事にありますように、RSS::Parser クラスメソッドの parse にて Parser クラスに応じたインスタンスが作成されます。REXML がロードされる私の環境では REXMLParser インスタンスが作成される。作成されたインスタンスの parse メソッドを呼び出すことでパースされ、RSSの情報が取得できるインスタンスが生成されるという構造となっているようです。

後者の RSS 情報を持っているインスタンスについては、後々見ていくとして今回はパース部分を見てみたいと思います。

さて parse メソッドなのですが、REXMLParser クラスを見てみても _parse というメソッドがあるのみで他には見当たりません。ということは他で定義されていることになるのですが、REXMLParser クラスのスーパークラスに当たる BaseParser クラスが怪しそうです。BaseParser クラスを見てみると案の定 parse メソッドがありますが、中では _parse メソッドを呼び出していますが _parse メソッドは見当たりません。これはサブクラスで _parse メソッドを実装する必要があることを意味していると考えられます。Parser クラスを変更することによって独自のパースを行うことができるということだと思います。速さを求めるためのカスタマイズ用とか、XML パーサが乱立していたとかあるんだと思う、たぶん。。。

というわけで、また REXMLParser に戻ってきて _parse を見てみると、中では REXML::Document.parse_stream を呼び出していることがわかります。

  class REXMLParser < BaseParser

    class << self
      def listener
        REXMLListener
      end
    end
 
    private
    def _parse
      begin
        REXML::Document.parse_stream(@rss, @listener)
      rescue RuntimeError => e
        raise NotWellFormedError.new{e.message}
      rescue REXML::ParseException => e
        context = e.context
        line = context[0] if context
        raise NotWellFormedError.new(line){e.message}
      end
    end
    ~省略~
  end

REXML は Ruby 標準として含まれている機能ですので、使い方を調べる必要があります。見た感じでは Web から取得した RSS コンテンツ(@rss)と @listener というものを渡して、その後は何もしていないことからここで RSS 情報が作成完了するということになります。名前からはおそらく @listener へタグ情報が通知され、何らかの形で RSS 情報が作成されるのだと推測されます。

今日はここら辺にして、次は @listener が何者か見ていこうと思います。

ここまでのクラス関係を絵にしてみるとこんな感じですね▽

f:id:bazz:20090511004537p:image