Mae向きなブログ

Mae向きな日記のブログ版。ようやくこちらに移行してきました。

Hpricotでの疑問点

以下を実行した際、「Ruby home page」と出力されることを期待していたのですが、画面には、「ruby home page」と表示されます。

# hpricot_prac.rb
require 'rubygems'
require 'hpricot'

doc = Hpricot('<a href="http://ruby-lang.org/">ruby home page</a>')
(doc/:a).each do |ele|
  ele.inner_html.gsub!(/ruby/, "Ruby")
  p ele.inner_html
end

何故だろうと思い、hpricot-0.6/lib/hpricot/elements.rbを見てみたところ、inner_htmlメソッドがあり、そこでは、

    def inner_html(*string)
      if string.empty?
        map { |x| x.inner_html }.join
      else
        x = self.inner_html = string.pop || x
      end
    end

というような配列に対する処理をやっているので、gsub!できないんだなと理解したつもりなのですが、このinner_htmlは、Elements#inner_htmlです。
以下のように、(doc/:a)は、Hpricot::Elementsなので、(doc/:a)に対してinner_htmlメソッドを呼び出して実行できるのは納得できるのですが、

p (doc/:a).class        #=> Hpricot::Elements
p (doc/:a).inner_html   #=> "ruby home page"

以下のように、eleは、Hpricot::Elemなので、ele.inner_htmlが実行できるのが不思議な感じがします。(*)

  p ele.class           #=> Hpricot::Elem
  p ele.inner_html

オブジェクト指向は奥が深い…。

追記(2008-08-01)

(*)は不思議ではないです。納得しました。id:rubikitch さんに教えていただきました。
Hpricot::Elem#inner_htmlの実体はHpricot::Traverse#inner_htmlで、hpricot/modules.rb よりこんな定義になっているからです。

module Hpricot
  module Traverse end
  module Container::Trav; include Traverse end
  class Elem;      module Trav; include Container::Trav end; include Trav end
end

で、hpricot/traverse.rbを見てみると、こうなっています。

module Hpricot
  module Traverse
    def html(inner = nil, &blk)
      if inner or blk
        altered!
        case inner
        when Array
          self.children = inner
        else
          self.children = Hpricot.make(inner, &blk)
        end
        reparent self.children
      else
        # ここを通る
        if respond_to? :children
          children.map { |x| x.output("") }.join
        end
      end
    end
    alias_method :inner_html, :html
    alias_method :innerHTML, :inner_html
  end
end

あと,最初に書いた,hpricot_prac.rbは,以下のようにHpricot::Elem#inner_html=を使えばうまくいきます。

# hpricot_prac.rb
require 'rubygems'
require 'hpricot'

doc = Hpricot('<a href="http://ruby-lang.org/">ruby home page</a>')
(doc/:a).each do |ele|
  ele.inner_html = ele.inner_html.gsub(/ruby/, "Ruby")
  pp ele.inner_html
end