倭マン's BLOG

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

org.dom4j.Element を構築するビルダー

groovy.util.NodeBuilder の練習のため、dom4jXML ドキュメントオブジェクトを構築するビルダーを作ってみます(一覧)。 簡単のため、以下の条件を課すことにします:

  • 名前空間はサポートしない
  • 構築後に返されるオブジェクトは org.dom4j.Element オブジェクト

サンプル・コード


まずはサンプルコード。 ビルダーのインスタンスを生成する以外は、通常のビルダーと同じ方法で XML 文書を構築します。

def builder = new SimpleDom4jBuilder()
def doc = builder.table(customer:'c', invoice:'i'){
    join(c:'invoice_id', i:'id')
    and{
        greater('i.total':1000)
        like('c:name':'%Bill%', 'child')
    }
}
  
assert doc instanceof org.dom4j.Element
def writer = new XMLWriter(
        new OutputStreamWriter(System.out),
        OutputFormat.createPrettyPrint())
writer.write(doc)
writer.flush()

これを実行すると、以下の XML ドキュメント(要素)が表示されます。

<table customer="c" invoice="i">
  <join c="invoice_id" i="id"/>
  <and>
    <greater i.total="1000"/>
    <like c:name="%Bill%">child</like>
  </and>
</table>

Builder クラスの実装


実装するクラスの名前は SimpleDom4jBuilder とします(名前空間をサポートしないという意味で Simple)。 このビルダークラスは BuilderSupport クラスを継承して作成します。 実装する必要のあるメソッドは以下の5つです:

  • protected void setParent(parent, child)
  • protected createNode(name)
  • protected createNode(name, text)
  • protected createNode(name, Map atts)
  • protected createNode(name, Map atts, Otext)

クラス定義、フィールド、コンストラクタ

まずはクラス定義、フィールド、コンストラクタを。 フィールドとしては org.dom4j.DocumentFactory オブジェクトを保持するようにします。

import org.dom4j.DocumentFactory

class SimpleDom4jBuilder extends BuilderSupport{

    /** 読み取り可能 */
    final DocumentFactory factory

    /** デフォルトコンストラクタ */
    SimpleDom4jBuilder(){
        this(new DocumentFactory());
    }

    /** DocumentFactory を受け取るコンストラクタ */
    SimpleDom4jBuilder(DocumentFactory factory){
        this.factory = factory
    }
}

親を設定する

次は setParent() メソッドの実装。 Element#add() メソッドで要素を付加します。

class SimpleDom4jBuilder extends BuilderSupport{

    // ...

    @Override
    protected void setParent(parent, child) {
        parent.add(child)
    }
}

createNode() メソッドを委譲する

全ての createNode() をそのまま実装するのは面倒なので、一番コアになる createNode(Object, Map, Object) メソッドを(後で)キチンと実装することにして、他の createNode() メソッドは適当なパラメータでこのメソッドを呼び出すようにします。

class SimpleDom4jBuilder extends BuilderSupport{

    // ...

    /** {@link SimpleDom4jBuilder#createNode(Object, Map, Object) } へ処理を委譲 */
    @Override
    protected createNode(name) {
        return createNode(name, [:], null)
    }

    /** {@link SimpleDom4jBuilder#createNode(Object, Map, Object) } へ処理を委譲 */
    @Override
    protected createNode(name, text) {
        return createNode(name, [:], text)
    }

    /** {@link SimpleDom4jBuilder#createNode(Object, Map, Object) } へ処理を委譲 */
    @Override
    protected createNode(name, Map atts) {
        return createNode(name, atts, null);
    }
}

新しい要素を生成する

最後はコアになる createNode(name, Map atts, text) メソッドの実装です。

import org.dom4j.Element

class SimpleDom4jBuilder extends BuilderSupport{

    // ...

    @Override
    protected createNode(name, Map atts, text) {
        Element e = this.factory.createElement(name.toString());

        atts.each{ attName, value ->
            e.addAttribute(attName.toString(), value.toString())
        }

        if(text != null){
            e.addText(text.toString());
        }

        return e;
    }
  • 属性に対応する Map が空の場合も問題なく動作する
  • 子要素に対応する text が null かどうかを判定しなければならない
  • Map の値、text は String オブジェクトとは限らないので、toString() メソッドを呼び出す必要がある(上の実装では、念のため要素の名前、属性のキーに対しても toString() メソッドを呼び出しています。)

制限はいくつか課しましたが、意外と簡単に Builder のサブクラスを作成できた気がしますが、どうでしょう?

Groovyイン・アクション

Groovyイン・アクション