倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

XML ノードを走査する

今回はノードを走査する方法を見ていきましょう(一覧)。 Quick Start Guide, cookbook 参照。 ノードを走査する方法としては、主に以下のものがあります:

♪コレクション・フレームワークを用いる♪


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 パターン」を使用することが出来ます。 使い方は

  1. org.dom4j.Visitor インターフェースを実装(もしくは org.dom4j.VisitorSupport を継承)したクラス(以下、Visitor クラス)を作成する
  2. 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 に対しても同様。