今回はノードを走査する方法を見ていきましょう(一覧)。 Quick Start Guide, cookbook 参照。 ノードを走査する方法としては、主に以下のものがあります:
- コレクション・フレームワークを用いる
- 高速ルーピング
- Visitor パターン
♪コレクション・フレームワークを用いる♪
java.util.List もしくは java.util.Iterator を返すメソッドを用いて子要素などを列挙します。 Java SE 5 以降では拡張 for 文を使えるので、java.util.Iterator を返すメソッドはあまり使う必要はないでしょう。
★子要素を列挙する
使用するメソッドは Element#elements(), Element#elementIterator() です。
public void iterateElements(final Element e){ // 拡張 for 文を使った例 for (final Object childObj: e.elements()) { Element child = (Element)childObj; // do something } // java.util.Iterator を使った例 for (final Iterator ite = e.elementIterator(); ite.hasNext();) { Element element = (Element)ite.next(); // do something } }
★特定の名前の要素を列挙する
特定の名前をもつ子要素のみを列挙することも出来ます。 使用するメソッドは Element#elements(String), Element#elementIterator(String) です。 引数の文字列が取得したい要素の名前です。 以下の例では "a" という名前の子要素を列挙しています:
public void iterateSpecificNameElements(final Element e){ // 拡張 for 文を使った例 for (final Object childObj: e.elements("a")) { Element element = (Element)childObj; // do something } // java.util.Iterator を使った例 for (final Iterator ite = e.elementIterator("a"); ite.hasNext();) { Element element = (Element)ite.next(); // do something } }
文字列を渡すメソッドの他に、org.dom4j.QName を渡して要素名を指定するメソッドElement#elements(QName), Element#elementIterator(QName) も使用できます。
★属性を列挙する
使用するメソッドは Element#attributes(), Element#attributeIterator() です。
public void iterateAttributes(final Element e){ // 拡張 for 文を使った例 for (final Object attObj: e.attributes()) { Attribute att = (Attribute)attObj; // do something } // java.util.Iterator を使った例 for (final Iterator ite = e.elementIterator(); ite.hasNext();) { Element att = (Attribute) ite.next(); // do something } }
♪高速ルーピング♪
ここでは、Branch に定義されている子ノードを取得するメソッド Branch#node(int)(Branch#content()でもよい)を用いて、子ノードを走査する方法を見てみます:
public void treeWalk(final Element element) { for (int i = 0, size = element.nodeCount(); i < size; i++){ final Node node = element.node(i); if (node.getNodeType() == Node.ELEMENT_NODE) { // node が要素のインスタンスの場合 treeWalk((Element)node ); }else { // do something.... } } }
node が要素かどうかを確かめるためには、Node#getNodeType() を使う以外にも、instanceof キーワードを使って
if(node instanceof Element){ // ... }
とすることも出来ます。 どちらの方法でも、要素以外のノードを察知して処理を行うようにすることが出来ます。 ただし、前回書いたように、Branch#node(i) では属性ノードは返されないので注意して下さい。
♪Visitor パターン♪
dom4j では、各種のノードに対して処理を行う際に、GoF 本にある「Visitor パターン」を使用することが出来ます。 使い方は
- org.dom4j.Visitor インターフェースを実装(もしくは org.dom4j.VisitorSupport を継承)したクラス(以下、Visitor クラス)を作成する
- Visitor クラスをインスタンス化し、Node#accept(Visitor) を呼び出す
です。
1. Visitor クラスを作成する
まず、Visitor クラスを作成します。 org.dom4j.Visitor には、以下のような各種ノードを処理するためのメソッドが定義されています:
- visit(Element)
- visit(Namespace)
- visit(Attribute)
- visit(Text)
- visit(CDATA)
- visit(Entity)
- visit(ProcessingInstruction)
- visit(Comment)
- visit(Document)
- visit(DocumentType)
これら全てのメソッドを実装するのは面倒なので、org.dom4j.VisitorSupport を用いて、処理が必要なメソッドのみをオーバーライドすると良いでしょう。
例として、cookbook に載ってたものを挙げておきます:
public class MyVisitor extends VisitorSupport{ @Override public void visit(Element element) { System.out.println( "Entity name: " + element.getName() + "text " + element.getText(); ); } }
ちなみに、Element や Document は visit() メソッド内の処理を行った後、属性(Attribute)や文書型(DocumentType)、content() で返される子ノードに対して自動的に visit() メソッドを呼び出します。 したがって、visit() メソッド内で子ノードなどに対して accept() メソッドを呼び出す必要はありません*1。
2. Visitor オブジェクトを使う
次に、Visitor クラスをインスタンス化して、処理を行いたい Node オブジェクトに渡します:
public void useVisitor(final Node node){ final Visitor visitor = new MyVisitor(); node.accept(visitor); }
*1:Element に対しては、visit() メソッドを呼び出す順番が「visit(Element)」→「その要素の属性に対して visit(Attribute)」→「 その要素の子ノードに対して visit(・・) 」と決まってしまっているので、正直、使い方に制限を感じることがあるかもしれません。 Document に対しても同様。