REXMLで、RelaxNGを使ってValidationするにはどうすればいいのでしょうか?

わけあって、XMLスキーマを作ることになったのだけれど、イマドキXMLSchemaでもないよなそれは茨の道だ地獄への道は標準化が舗装する とか思ったのでRelaxNGを使ってみることにした。XMLやらスキーマやらRelaxやら言ってると、途端にじゃばじゃばいう擬音が聞こえてくるけども、こちとら自腹じゃそんな重たいものはいらぬ、というわけで、Rubyでライブラリをあさる。
軽くRAAを眺めてみると、Ruby標準のXMLライブラリであるREXMLにちらっとRelaxNGに対応とか書いてある。おお、じゃあそれでいいや。と、いろいろいじくってみたものの、壊滅的にドキュメントがない。一体どうやって使うんだこれ。
しょうがないので、ちらっとソースを眺めてみると、REXML::Validation::RelaxNG#validateの仮引数名がeventになってる。ははぁんこれは、ストリームパーサー系にくわすんだなとあたりをつけたまではいいが、ValidatorをREXML::Parsers::SAX2Parserのリスナーに登録して、parseを呼ぶだけでは駄目っぽい。
明らかなスキーマ違反にはちゃんと対応して反応してくれて、るんだけど、妥当に見えるXMLにまで不可思議な例外をあげて、くれる始末。まだまだ先は長いなあ。
どこかに、サンプルコードはないのか。ないのかああああ。

サンプルコード発見

REXMLにちゃんとRelaxNG用のテストコードがあったよ。debianでごそっとインストールしてるから気がつかなかった。RAAのgonzuiありがとう。テストと見比べてXMLのほうを修正したら通った。
結論としては、XMLのタグの間に改行がはいっていたせいらしい。改行を潰したらちゃんと通った。RelaxNGと妥当なXMLの組でvalidateしても例外が飛ばなくなった。んー、っと、でも改行のはいってないXMLファイルなんてどれだけあるんだ。これくらいライブラリ側のスイッチでなんとかなる気もするんだけど、どこをいじればいいのかな、と。

なんとかコード化

結局、いったん改行込みのXMLをREXML::Documentにオプション付きで取りこんで無意味な空白を全部つぶした文字列に変換してからRelaxNGでvalidate。いかにも二度手間だがこれで良しとする。ふぅ…。
以下サンプルコード。parseした時点で例外が飛ばなければOK。RelaxNGとXMLの組は http://www.kohsuke.org/relaxng/tutorial.ja.html のものをコピーした。
うーんしかし、これはどういうことなんだろう。RelaxNGがちゃんと書けてないのか、REXMLのバグなのか。RelaxNGをちゃんと勉強しなきゃいけないのかなー。面倒だなー。

require 'rexml/document'
require 'rexml/parsers/treeparser'
require 'rexml/validation/relaxng'

ng = <<EOS
<element name="addressBook" xmlns="http://relaxng.org/ns/structure/0.9">
  <zeroOrMore>
    <element name="card">
      <element name="name">
        <text/>
      </element>
      <element name="email">
        <text/>
      </element>
    </element>
  </zeroOrMore>
</element>
EOS

xml = <<EOX
<addressBook>
  <card>
    <name>John Smith</name>
    <email>js@example.com</email>
  </card>
  <card>
    <name>Fred Bloggs</name>
    <email>fb@example.net</email>
  </card>
</addressBook>
EOX

validator = REXML::Validation::RelaxNG.new(ng)
nospace_xml = REXML::Document.new(xml,{:ignore_whitespace_nodes => :all}).to_s
doc = REXML::Parsers::TreeParser.new(nospace_xml)
doc.add_listener(validator.reset)
doc.parse