読者です 読者をやめる 読者になる 読者になる

M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

XMLドキュメント・トラバースを支援するライブラリつくった

Java

作成中のツールで設定情報をXMLから読み取るロジックを書こうとしたところ、Java標準APIのDOM実装──org.w3c.domパッケージに含まれる各種API──がとてつもなく使い勝手のわるいものであることに気が付きました。使い勝手がわるいのはW3Cの標準に忠実にあろうとした結果のようですが、JavaScriptのそれでも結構「微妙」なものだったのが、Javaになるとちょっと「しんどい」レベルになります。

というわけで、XMLドキュメント・トラバースを支援するライブラリをつくってみました

XMLドキュメントをパースする

標準APIを直接利用する場合、ドキュメントのパースは以下のように行うことになります:

try {
	Document doc = DocumentBuilderFactory
			.newInstance()
			.newDocumentBuilder()
			.parse(file);
} catch (SAXException e) {
	// ...
} catch (IOException e) {
	// ...
} catch (ParserConfigurationException e) {
	// ...
}

doogwood-xmlを利用する場合、同じことを以下のように行うことができます:

import static org.doogwood.xml.Nodes.*;

try {
	Document doc = fromFile(file);
} catch (XmlException e) {
  // ...
}

特定のタグを検索する

標準APIを利用する場合、Node#getChildNodes()Element#getElementsByTagName(String)などを呼び出して、
これらのメソッドが返すNodeListなるコンテナからNodeを取り出し、そこに種々の条件判定を行って・・・という手続きを踏みます。

ちなみにNodeListはその名前から受ける印象とちがってListIterableも実装していません。Iterator実装を返すメソッドも存在しません。
NodeList#getLength()NodeList#item(int)という見慣れないインターフェースを利用して、昔ながらのカウンタ変数をともなうforループを行う必要があります。

doogwood-xmlを利用する場合、Nodes#children(Node, Matcher)Nodes#descendents(Node, Matcher)もしくはそのヴァリアント/オーバーロードを利用します。返される結果はListOptionalです。

例えばタグ名を使って検索する場合は以下のようになります:

import static org.doogwood.xml.Nodes.*;
import static org.doogwood.xml.Matchers.*;

Document doc = ...;
List<Node> list = descendents(doc, tagNameIs("foo"));
Optional<Node> one = firstDescendent(doc, tagNameIs("foo"));

検索条件は複数組み合わせて利用することができます:

List<Node> fooOrBar = descendents(doc, tagNameIs("foo").or(tagNameIs("bar")));
List<Node> fooHasAttrBaz = descendents(doc, tagNameIs("foo").and(attrExists("baz")));

タグの属性にアクセスする

標準APIを利用する場合Node#getAttribute(String)Node#getAttributes()を呼び出すことになります。
前者は属性が存在しなくても空文字列を返すため、属性存在有無の判定をnullチェックにより実施することができません。
後者はNamedNodeMapなるコンテナを返しますが例によってこれはMapを実装していません。IterableIteratorの実装を返すメソッドも存在しません。

doogwood-xmlを利用する場合、Nodes#iterableAttrMap(Node)Nodes#iterableAttrList(Node)Nodes#attributes(Node, Matcher)やそのヴァリアントを利用します。

前者2つは名前から察せさられる通り、当該のXMLノードから属性情報を読み取りListもしくはMapに格納して返します。後者は前掲のNodes#descendents(Node, Matcher)と同様に、条件にマッチする属性のみを返します。

Node foo = ...
Map<String, Attr> attrMap = iterableAttrMap(foo);
List<Attr> attrList = iterableAttrList(foo);
List<Attr> bazAttrList = attributes(foo, nameIs("baz"));


プロジェクトのページはこちらです。