XMLドキュメント・トラバースを支援するライブラリつくった
作成中のツールで設定情報を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
はその名前から受ける印象とちがってList
もIterable
も実装していません。Iterator
実装を返すメソッドも存在しません。
NodeList#getLength()
とNodeList#item(int)
という見慣れないインターフェースを利用して、昔ながらのカウンタ変数をともなうfor
ループを行う必要があります。
doogwood-xml
を利用する場合、Nodes#children(Node, Matcher)
やNodes#descendents(Node, Matcher)
もしくはそのヴァリアント/オーバーロードを利用します。返される結果はList
やOptional
です。
例えばタグ名を使って検索する場合は以下のようになります:
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
を実装していません。Iterable
やIterator
の実装を返すメソッドも存在しません。
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"));
プロジェクトのページはこちらです。