倭マン's BLOG

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

NamespaceContext の実装を考える (3) : メソッド getPrefixes()

今回は、指定された名前空間 URI から、それに対応する接頭辞の Iterator を返すメソッド NamespaceContext#getPrefixes(String) を考えます(一覧)。

NamespaceContext#getPrefixes(String) の返り値


Java SE 6 の API ドキュメントを見ると、返り値は以下のように定義されています:

引数の名前空間 URI 返り値
http://www.w3.org/XML/1998/namespace xml のみを含む Iterator
http://www.w3.org/2000/xmlns/ xmlns のみを含む Iterator
null IllegalArgumentException が投げられる
対応する名前空間宣言がある場合(空文字列を含む*1 対応する接頭辞を全て含む Iterator
対応する名前空間宣言がない場合 要素を持たない Iterator

少し注意が必要なのは、対応する名前空間宣言がある場合でしょうか。 例えば、以下のような XML 文書があったとしましょう:

<a xmlns="xyz" xmlns:q="xyz">
  <b xmlns:p="xyz" xmlns:q="abc"/>
</a>

この <b> 要素の NamespaceContext オブジェクト context_b に対して、メソッド呼び出し

context_b.getPrefixes("xyz");

は、文字列のリスト ["", "p"] (順番は不明)が返されます。 要素 <b> 上では、接頭辞 "q" は名前空間 "abc" に割り当てられているので、これは除く必要があります。

接頭辞の解決手順


接頭辞の解決手順は以下の通り:

  1. 引数の名前空間 URI が null, "http://www.w3.org/XML/1998/namespace", "http://www.w3.org/2000/xmlns/" の場合、対応する接頭辞の Iterator を返す(もしくは例外を投げる)
  2. 親ノードから対象となる接頭辞のリストを取得する
  3. 対象ノード上で、別の名前空間 URI に宣言された接頭辞を除去する
  4. 対象ノード上で、新たに対象の名前空間 URI に宣言された接頭辞を付加する
  5. 得られた接頭辞のリストの Iterator を返す

親ノードの接頭辞リストから対象ノードの接頭辞リストを取得する際、新たに宣言された接頭辞を付加すると共に、他の名前空間に宣言し直された接頭辞を除去するのをお忘れ無きよう。

実装


実装は概ね以下の通り:

/** @author waman */
public abstract class AbstractNamespaceContext implements NamespaceContext{

    ...

    protected abstract NamespaceContext getParentContext();
    protected abstract getNamespaceURIonCurrentNode(String prefix);
    protected abstract getPrefixesOnCurrentNode(String uri);

    public Iterator<String> getPrefixes(String uri) {
        if(uri == null)
            throw new IllegalArgumentException("Namespace URI cannot be null !");

        if("http://www.w3.org/XML/1998/namespace".equals(uri))
            return Collections.singletonList("xml").iterator();

        if("http://www.w3.org/2000/xmlns/".equals(uri))
            return Collections.singletonList("xmlns").iterator();

        return getPrefixList(uri).iterator();
    }

    /**
     * 引数の名前空間 URI に対応する接頭辞のリストを返します。
     */
    private List<String> getPrefixList(String uri){

        // 親ノードから対象となる接頭辞のリストを取得する
        List<String> parentPrefixes = getParentPrefixList(uri);
        
        // 対象ノード上で、別の名前空間 URI に宣言された接頭辞を除去する
        List<String> prefixes = new LinkedList<String>();
        for(String prefix: parentPrefixes){
            String u = getNamespaceURIonCurrentNode(prefix);
            if(u == null || u.equals(uri))
                prefixes.add(prefix);
        }
            
        // 対象ノード上で、新たに対象の名前空間 URI に宣言された接頭辞を付加する
        prefixes.addAll(getPrefixesOnCurrentNode(uri));
        
        return prefixes;
    }

    /**
     * 親ノードの NamespaceContext に対して、
     * 引数の名前空間 URI に対応する接頭辞のリストを返します。
     * つまり、NamespaceContext#getPrefixes(String) で返される
     * Iterator の要素をリストで返します。
     */
    private List<String> getParentPrefixList(String uri){
        List<String> prefixes = new LinkedList<String>();
        
        NamespaceContext parent = getParentContext();
        if(parent != null){
            Iterator<?> ite = parent.getPrefixes(uri);
            while(ite.hasNext())
                prefixes.add((String)ite.next());
        }
        
        return prefixes;
    }
}

ほとんどのロジックを getPrefixList() メソッド内に実装していますが、これは getPrefix(String) メソッド内で処理を使い回すためです。 詳しくは次回以降。

*1:これはデフォルト名前空間を宣言します。