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 を参照)。かなり頭の良いやり方だと思います、すばらしいです。