gemでlibxml-ruby(0.5.4)をインストールして使っていた。
いつからか、以下のようなメッセージでクラッシュしたりしなかったり、という事象が出始めた。
[BUG] object allocation during garbage collection phase
・・・という問題に悩んでいたのだけど、そのものズバリの答えが。
[ruby-dev:35157] Re: error: Ruby 1.8.7 object allocation during garbage collection phase
クラッシュといっては心外な話しか。
新しいrubyだと問題を検出して[BUG]を出す、ということだそう。

原因はわかったが、とりあえず手元のlibxml-ruby前提で書いたツールをどうするか。

libxml-rubyのパッチを書く?
とコードを眺めたものの早々にあきらめた。これはすぐにどうこう出来そうもない。。

REXMLで書き直す?
そもそもREXMLが遅いので、libxml-rubyにしただけにこれは避けたい。

perlのLibXMLで書き直す?
んー、時間もかかるし同じものをもう一回書くのもな。

で、jruby
REXMLを使うとスタート地点に戻ってしまうので、javaのDOM・XPath APIを使うことに。

とりあえず、libxml-rubyで使ってるインタフェースだけラッパーを用意。
ツール側のコードはほぼ変更がなかった。
libxml2の軽快さには遠いがjrubyありがとうって感じ。

jruby:1.1.2
Sun Java: 1.6.0_05
OS:CentOS 5.1

  $KCODE='u'
require 'java'

class WrapNode
attr_reader :node

def initialize(e)
@node = e
@xpath = nil
end

def to_s
@node.nodeValue.to_s
end

def wrap(e)
if e == nil
return nil
end
WrapNode.new(e)
end

def next
self.wrap(@node.nextSibling)
end

def text?
@node.nodeType == org.w3c.dom.Node::TEXT_NODE
end

def [](name)
@node.getAttribute(name)
end

def parent
self.wrap @node.parentNode
end

def child
self.wrap @node.firstChild
end

def xpath
if !@xpath
factory = javax.xml.xpath.XPathFactory.newInstance()
@xpath = factory.newXPath()
end

@xpath
end

def find(expr)
nodelist = self.xpath.evaluate(expr, @node, javax.xml.xpath.XPathConstants::NODESET)

ret = []
for i in 0 .. (nodelist.length - 1)
ret.push(WrapNode.new(nodelist.item(i)))
end
ret
end
end

class WrapDocument < WrapNode
def initialize(e)
super(e)
end

def root
self.wrap(@node.documentElement)
end
end

# 指定されたファイルをopenしてXML文書を返す
#
# fn_in::
# ファイル名。nilの場合、stdinを適用
def build_doc(fn_in)
st = java.lang.System.in
if fn_in
st = java.io.FileInputStream.new(fn_in)
end

factory = javax.xml.parsers.DocumentBuilderFactory.newInstance()
builder = factory.newDocumentBuilder()
WrapDocument.new(builder.parse(st))
end

doc = build_doc("foo.xml")
doc.find("//bar").each { |e|
puts e["name"]
}