ザネリは列車を見送った

ブログという名の備忘録

Commons Digester で XML to Java バインディング

ちょっとした必要に迫られて XMLJava クラスにバインディングしてくれるツールについて調べた事の覚え書き。

JAXB

何かのライブラリとかツール群の中に同梱されているのは何度か見かけたけど、
自分で使ってみたのは初めてだったので手順をメモっとこう。

  1. 扱いたい XML の構造が分かっている場合、まず XML Schema を作成する。
  2. XML Schema から Java のクラスを生成する。
  3. 扱いたい XML をアンマーシャルして Java クラスにバインディング

1. は勿論手で書いてもいいけど、ちょっと複雑になってくるとかなりしんどいのでツールに任せた。
trangrelaxer が使える。

java -jar trang.jar <元のxml> <生成するxsd>
relaxer -xsd <元のxml>

2. は Java 標準の xjc を使用する。

xjc <1.で生成したxsd>

同ディレクトリにワラワラっと Java クラスが生成される。
3. はこの辺のサンプルを参考に。

一応やりたいことはできたけど、xsd の作成に一手間かかる上に
生成されるクラスが無駄に多め(これは xsd の書き方が悪いのかもしれないけど)だったり
XML の構造が変わったらまた1. ~3. をやり直さないといけなかったりして、
もうちょっとお手軽にできないものかと他の方法を探してみた。

Commons Digester

JAXB ほど事前にやる事は多くないけど、 XMLJava のみが可能で、その逆はできない。
XML の構造をパターンとして記述し、そのパターン出現時に行う処理を Digester に定義する。
例えばルート要素が root の場合に Root クラスにバインディングしたい場合、

digester.addObjectCreate("root", Root.class);

こんな感じで Root クラスを作るようにしている。 <root><name>なまえ</name></root> の
「なまえ」を Root クラスの name 変数にセットする場合、

digester.addBeanPropertySetter("root/name");

とする。 XML の要素名と Java 変数名が異なる場合は

digester.addBeanPropertySetter("root/name", "namae");

とプロパティ名を指定する。

属性を扱う場合、

digester.addSetProperties("root/name");

とすれば、<name>要素の全属性を Root クラスの同名変数に設定してくれる。
特定の属性のみ取得したい場合、

digester.addSetProperties("root/name", "id", "id");

のように、第二引数で属性名を、第三引数で Java 変数名を指定する。

名前空間の扱いはちょっと注意が必要。
デフォルトではプレフィックスを指定できる。
<a:root xmlns:a="http://xxx" xmlns:b="http://yyy"><b:name>なまえ</b:name></a:root> では、

digester.addObjectCreate("a:root", Root.class);
digester.addBeanPropertySetter("a:root/b:name", "name");

でちゃんと動作してしまう。
しかしこれではイケてないので、ちゃんと名前空間 URI との一致で解釈させるようにしたい。

digester.setNamespaceAware(true);
digester.setRuleNamespaceURI("http://xxx");
digester.addObjectCreate("root", Root.class);
digester.setRuleNamespaceURI("http://yyy");
digester.addBeanPropertySetter("root/name", "name");

このように URI を設定し直しつつ定義していかないといけない模様…。
後述するけど名前空間の扱いはいまいち不安定さを感じる。

まとめとして、mixi アプリの Person & Friends API レスポンスサンプル(application/atom+xml 形式)
Java クラスにバインディングする Digester 定義を書いてみた。

色々調べてみたけど、属性の名前空間対応はできていないのかも?
やり方を知らないだけかもしれないけど、どうも以下の id 属性は、どのような定義をしても
「最後に定義されている要素」(c:id)の値を取ってきているようだ。
全ての id を正確に URI とマッチさせつつ値を取得する方法を定義できなかった…。

<a:root xmlns:a="http://xxx" xmlns:b="http://yyy" xmlns:c="http://zzz">
<b:name a:id="a" b:id="b" c:id="c">なまえ</b:name>
</a:root>