XPathにおける
名前空間の扱い

XML文書と名前空間の指定

XML文書では、異なる仕様で定められた要素を混在させることができます。 例えば RSS 1.0 では、channel, item など RSS 1.0 の仕様で定められた要素のほかに、 RDF の仕様で定められた RDF (rdf:RDF), Dublin Core という仕様で定められた date (dc:date) などが混在しています。 これらの要素がどの仕様で定められたものなのかを識別するため、 要素名の前に rdf, dc といった名前空間接頭辞(prefix)がつけられています。 接頭辞と、仕様を識別するためのURIとの対応が、root要素の属性に記述されています。

    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns="http://purl.org/rss/1.0/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:syn="http://purl.org/rss/1.0/modules/syndication/"
    xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/"

上記は RSS 1.0 の例です。 2行目の xmlns=... の記述により、要素名に接頭辞のない要素は RSS 1.0 の要素であるとされています。

一方、RSS 2.0 では名前空間の指定がされていません。 要素名に接頭辞のない要素は、名前空間の指定のない要素となっています。

XPath と名前空間

XPath で要素名を指定する際、その要素名がどの名前空間のものであるかを指定する必要があります。 名前空間を明示しない場合、名前空間の指定がされていない名前、という意味になります (これは XPath 1.0 の仕様です)。

    /rss/channel/item

このように XPath を書いた場合、rss, channel, item は名前空間の指定がされていない名前と解釈されます。 これは RSS 2.0 の実態と一致するため、期待したとおりに要素が選択されます。

一方、RSS 1.0 向けに以下のように XPath を書いても、期待した動作をしません。

    /RDF/item

XPath としては、RDF, item は名前空間の指定がされていない名前と解釈されますが、 RSS 1.0 ではそれぞれが特定の名前空間に属した名前ですので、マッチしません。

名前空間を無視する方法

XPath を使いたいけれど、名前空間の区別は不要、という場合、 あらかじめ名前空間を扱わない DOM ツリーを構築しておくという方法があります。 DOM 構築を担当する LSParser のインスタンスに対し、以下の指示をします。

	DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
	DOMImplementationLS implementation = (DOMImplementationLS)registry.getDOMImplementation("XML 1.0");
	// 読み込み対象の用意
	LSInput input = implementation.createLSInput();
	input.setByteStream(inputStream);
	input.setEncoding(encoding);
	// 構文解析器(parser)の用意
	LSParser parser = implementation.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS, null);
	parser.getDomConfig().setParameter("namespaces", false);
	// DOMの構築
	document = parser.parse(input);

なお、DOM 3 に準拠した LSParser ではなく旧来の DocumentBuilder で Document を生成する場合、 デフォルトでは名前空間を扱わず、 逆に名前空間を扱う場合に DocumentBuilderFactory に対して setNamespaceAware(true) の指示が必要となります。

名前空間を無視することにより、例えば Atom の場合には以下のように書いてもマッチします。

    /feed/entry/title[contains(.,'You')]

名前空間を無視はしないが指定しない方法

XPath では、要素を指定するときに * を使うと、どんな名前空間の要素でもマッチします。 マッチさせた上で、名前空間は指定せずに要素名だけを指定します。

    /*[local-name()='RDF']/*[local-name()='item']

local-name() は、名前空間とは関係なく、ローカルな名前を返します。 ローカルな名前が 'RDF' である要素、という意味になります。

さきほどの Atom の例では、以下のようになります。

    /*[local-name()='feed']/*[local-name()='entry']/*[local-name()='title' and contains(.,'You')]

論理演算の and が使われています。

この方法では名前空間を見ずに要素名だけで要素を指定するため、 名前空間Aの name と名前空間Bの name を区別することはできません。

名前空間を指定する方法

XPath においても、要素名の名前空間を指定する際に名前空間接頭辞を使うことができます。 ただし、読み込む XML文書に記述されている接頭辞と仕様との対応を援用することはできず、 自分で接頭辞と仕様の対応を定める必要があります。

まず、XPath に現れる名前空間接頭辞を仕様のURIに変換する、 NamespaceContext インタフェースを実装したクラスを用意します。 以下では、RSS 1.0 で使われる 5つの仕様に対して、接頭辞を決めています。 XPath 1.0 では接頭辞なしの要素を名前空間に対応づけることができないため、 RSS 1.0 の要素に rss という接頭辞をつけるようにしています。 その他の仕様については読み込む文書と接頭辞を同じにしていますが、 異なる接頭辞にすることもできます。

class NamespaceContextRSS1 implements NamespaceContext {
	private HashMap<String, String> prefixToURI;
	public NamespaceContextRSS1() {
		prefixToURI = new HashMap<String, String>();
		prefixToURI.put("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
		prefixToURI.put("rss", "http://purl.org/rss/1.0/");
		prefixToURI.put("dc", "http://purl.org/dc/elements/1.1/");
		prefixToURI.put("syn", "http://purl.org/rss/1.0/modules/syndication/");
		prefixToURI.put("taxo", "http://purl.org/rss/1.0/modules/taxonomy/");
	}
	public String getNamespaceURI(String prefix) {	// 接頭辞からURIを求める
		return prefixToURI.get(prefix); 
	}
	public Iterator<String> getPrefixes(String uri) {
		ArrayList<String> list = new ArrayList<String>();
		for(Map.Entry<String, String> entry : prefixToURI.entrySet()) {
			if(entry.getValue().equals(uri)) {
				list.add(entry.getKey());
			}
		}
		return list.iterator();
	}
	public String getPrefix(String uri) {
		return getPrefixes(uri).next();
	}
}

このクラスのインスタンスを、XPath インスタンスに教えておきます。

	XPathFactory factory = XPathFactory.newInstance();
	XPath xPath = factory.newXPath();
	xPath.setNamespaceContext(new NamespaceContextRSS1());

こうしておくことにより、次のように XPath を書くことができるようになります。

    /rdf:RDF/rss:item/rss:title