倭マン's BLOG

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

NamespaceContext の実装を考える (7) : AbstractNamespaceContext の使用例

今回は前回までで考えた AbstractNamespaceContext の実際の使い方を考えてみます(一覧)。

ここでは dom4j に定義されている、XML ツリーの要素を表すインターフェース org.dom4j.Element から、NamespaceContext を取得できるようにしてみましょう*1

ただし、拙者は dom4j の開発者と縁もゆかりもないので、直接 dom4j の実装を変更することは出来ません。 したがって、(チョット手間ですが)AspectJ を用いて外部からこれらの実装を織り込んでやります。

AspectJ によるフィールド、メソッドの付加


AspectJ を使う」と言っても使う部分はほんの少しで、org.dom4j.Element インターフェースに NamespaceContext を返すメソッド getNamespaceContext() を定義するだけです*2

import javax.xml.namespace.NamespaceContext;
import org.dom4j.Element;

public aspect NamespaceContextAspect {
    
    /**
     * getNamespaceContext() メソッドで返す NamespaceContext オブジェクト。
     */
    private NamespaceContext Element.context;
    
    /**
     * NamespaceContext を返すメソッドを org.dom4j.Element に付加する。
     */
    public NamespaceContext Element.getNamespaceContext(){
        
        if(this.context == null)
            this.context = new Dom4jNamespaceContext(this);
        
        return this.context;
    }
}

Dom4jNamespaceContext クラス


準備が出来たので、メインのテーマ、NamespaceContext の実装クラスを作成しましょう。 dom4j に特化したクラスを Dom4jNamespaceContext として作成します。

Dom4jNamespaceContext クラスは前回までで作成した AbstractNamespaceContext クラスを拡張して作成します。 実装が必要なメソッドは

  • getParentContext(String) : NamespaceContext
  • getgetNamespaceURIonCurrentNode(String) : String
  • getPrefixesOnCurrentNode(String) : List

の3つでした:

import java.util.LinkedList;
import java.util.List;
import javax.xml.namespace.NamespaceContext;
import javax.xml.stream.events.Namespace;
import org.dom4j.Element;

public class Dom4jNamespaceContext extends AbstractNamespaceContext {

    private final Element element;
    
    public Dom4jNamespaceContext(Element element){
        this.element = element;
    }

    @Override
    protected NamespaceContext getParentContext() {
        Element parent = this.element.getParent();
        if(parent == null)
            return null;
        else
            return parent.getNamespaceContext();
    }
    
    @Override
    protected String getNamespaceURIonCurrentNode(String prefix) {
        
        for(Object obj: this.element.declaredNamespaces()){
            Namespace ns = (Namespace)obj;
            if(ns.getPrefix().equals(prefix))
                return ns.getNamespaceURI();
        }
        
        return null;
    }

    @Override
    protected List<String> getPrefixesOnCurrentNode(String uri) {

        List<String> prefixes = new LinkedList<String>();
        
        for(Object obj: this.element.declaredNamespaces()){
            Namespace ns = (Namespace)obj;
            if(ns.getNamespaceURI().equals(uri))
                prefixes.add(ns.getPrefix());
        }
        
        return prefixes;
    }
}

メソッド org.dom4j.Element#declaredNamespaces() は、その要素に宣言されている名前空間宣言のリストを返すメソッドです。

祖先要素を遡って名前空間宣言を辿る必要がないので、比較的簡単に実装ができました!

*1:実際には org.dom4j.Element インターフェースには、NamespaceContext インターフェースに定義されているメソッドと同様の挙動をするメソッド getNamespaceForURI(), getNamespacesForPrefix(), getNamespacesForURI() などが定義されています。 これらのメソッドの返り値は org.dom4j.Namespace オブジェクトもしくはそのリストです。

*2:アスペクトの実装自体はこれだけですが、これを実際に織り込むには、dom4j.jar, aspectjrt.jar などを CLASSPATH に加えて、inpathdom4j.jar を加える必要があります。