REXML (Ruby 標準添付ライブラリ) のバグ

REXMLXPath の実装状況を調べようとしてちょっと XPath を使ってみたのですが、どうも仕様どおりの動きをしないことがあったのでメモしておきます。

#! /usr/bin/ruby1.9
# -*- coding: utf-8 -*-

begin

STDOUT.set_encoding( "UTF-8", "UTF-8" )

# REXML 使用
require "rexml/document"

# 以下の XML 文書を DOM にする
xml_string = <<EOF
<?xml version='1.0' encoding='UTF-8' ?>
<test>
<t1><text>test1</text>
<t2>test1-1</t2>
<t2>test1-2</t2>
<t2>test1-3</t2>
</t1>
<t1><text>test2</text>
<t2>test2-1<t2>test2-1-1</t2></t2>
<t2>test2-2</t2>
<t2>test2-3</t2>
</t1>
<t1><text>test3</text>
<t2>test3-1</t2>
<t2>test3-2</t2>
<t2>test3-3</t2>
</t1>
</test>
EOF

doc = REXML::Document.new( xml_string )
p REXML::XPath.match( doc, "//t1/t2[2]/text()" )
#=> ["test1-2"] ... NG: 本当は ["test1-2", "test2-2", "test3-2"] となるはず
p REXML::XPath.match( doc, "//t2[2]/text()" )
#=> ["test1-2", "test2-2", "test3-2"] ... これは期待通りの結果
p REXML::XPath.match( doc, "/descendant::*/text()" )
#=> [] ... NG: 本当は全てのテキストノードを取得するはず.
p REXML::XPath.match( doc, "/descendant::text()" )
#=> 全てのテキストノードが取得できた. よって descendant 軸に対応していないというわけではない
p REXML::XPath.match( doc, "/descendant::*" )
#=> 全ての要素ノードが取得できた. よって任意の要素ノードに一致する記法 "*" に対応していないわけではない

rescue => err
p err
p err.backtrace()
end

Ruby 1.9.1p0 で上のコードを実行したところ、コード中に記載されているような出力がなされたのですが、1 つ目の XPath "//t1/t2[2]/text()" による結果と 3 つ目の XPath "/descendant::*/text()" による結果が思うようなものではありませんでした。

XPath "//t1/t2[2]/text()"

これは、文書中の全てのノードの子要素 t1 について、(2 つ以上の) 子要素 t2 を持っているか調べて、もし持っている場合 2 つ目の t2 を取得し、その t2 の子テキストノードを得るというものです。 文書中に、2 つ以上の子要素 t2 を持つ要素 t1 は 3 つあり、該当する t2 は子テキストを 1 つずつ持っているので、結果として得られるテキストノードは 3 つあるはずです。

しかし、実際に実行したところ 1 つのテキストノードしか得られませんでした。

"//t1/t2" という XPath も "//t2[2]" という XPath も期待通りの結果を返すため、内部的にどういう問題が起こっているのか見当もつきませんが、とにかくバグがあるようです。

XPath "/descendant::*/text()"

これは文書中の全ての要素について、その子テキストを得るような XPath なのですが、実際に実行したところ何もノードが引っかかりませんでした。

descendant 軸に対応していないのかもと思いましたが、"/descendant::text()" という XPath も "/descendant::*" という XPath はちゃんと期待通りの動きをしました。 こちらも内部的にどういう問題が起こっているのかよくわかりませんが、バグがあるようです。

階層が深くなると問題あり?

1 階層だけならちゃんと動くけど、階層を深くすると問題が起こるのかなぁ、と思ったりしますが・・・まだ詳しく動きを追ってないのでよくわかりません。 また暇なときにでもライブラリの内部実装を見てみますか。