Hatena::ブログ(Diary)

vivid memo このページをアンテナに追加 RSSフィード

vivid code というサイトのメモ代わりに記事を書いていました。
現在ははてなブログに移行し、「ひだまりソケットは壊れない」 というブログで記事を書いています。 はてな id も id:nobuoka に変更しました。

2010-01-03

DOM 3 XPath において名前空間に属する Node を指定する方法

Document Object Model (DOM) Level 3 には XPath に関する仕様も含まれています。

最近のブラウザである Firefox 3.5 や Safari 4、Opera 10 などの JavaScript 実装は DOM Level 3 に対応しており、XPath 機能も使う事ができます。 これらのブラウザであれば、XPath 機能を使った JavaScript を含む HTML を解釈してくれるため、要素の選択などを XPath を使って簡単に行う事ができます。

// XPath 式の実行
// 第 1 引数が XPath 式, 第 2 引数が context node, 第 3 引数は XPathResolver オブジェクト (HTML であれば null でよい),
// 第 4 引数は結果の型, 第 5 引数は (必要であれば) 結果を格納するオブジェクト (普通は null でよい)
var xpath_result = document.evaluate( "string(//title)", document, null, XPathResult.STRING_TYPE, null );
// 結果の XPathResult オブジェクトから実際の値を取得
var str = xpath_result.stringValue;

上記コードを実行すると変数 str には、文書中の title 要素 (複数ある場合は最初に現れるもの) の文字列値が格納されます。 実際に動くサンプルは下記ページにあります。

XHTML 中で XPath を使うためには名前空間 URI の解決を行わなければならない

上で述べたように HTML で XPath を使う事ができるわけですが、同じようにして XHTML (MIME タイプが "application/xhtml+xml" のもの) において XPath を使おうとすると、問題が起きます。 HTML の要素は名前空間に属していない *1 のに対して、XHTML の要素は名前空間 URI "http://www.w3.org/1999/xhtml" に属しているという違いがあるためです。

XPath の仕様には、以下のように書いています。

A QName in the node test is expanded into an expanded-name using the namespace declarations from the expression context. This is the same way expansion is done for element type names in start and end-tags except that the default namespace declared with xmlns is not used: if the QName does not have a prefix, then the namespace URI is null (this is the same way attribute names are expanded). It is an error if the QName has a prefix for which there is no namespace declaration in the expression context.
(XML Path Language - 2.3 節 より引用)

つまり、XPath 式中の node test において、名前空間に属する node を指定したい場合は、prefix を付けなければならない、ということです。 また、XPath 式中の prefix と namespace URI を結びつける具体的な方法については特に書かれていません。

XPathNSResolver を使用して名前空間の解決を行う

さて、XPath の仕様には prefix と namespace URI を結びつける具体的な方法が書かれていませんが、DOM 3 XPath 仕様にその方法が書かれています。

インターフェイス XPathNSResolver を実装したオブジェクトを使い、名前空間の解決を行います。 XPathNSResolverの生成には、XPathEvaluator#createNSResolver メソッド を使用します。 XPath 式中で利用する prefix を宣言している Node を引数に渡して XPathEvaluator#createNSResolver メソッド を呼び出すと、期待している XPathNSResolver が得られます。

一般的には XPathEvaluator インターフェイスは Document インターフェイスを実装しているオブジェクトに実装されています。 JavaScript であれば window.document オブジェクトという事になります。

// まずは名前空間の宣言を行う要素を生成 (要素名などは適当でよい)
var nsr_elem = document.createElementNS(null, "ns-resolver");
// 次に, その要素に名前空間の宣言のための属性を付与
// (今は prefix "h" を namespace URI "http://www.w3.org/1999/xhtml" に結びつける)
// (必要に応じて複数の名前空間の宣言をしてよい)
nsr_elem.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:h", "http://www.w3.org/1999/xhtml");
// XPathNSResolver の生成
var nsr = document.createNSResolver( nsr_elem );

この XPathNSResolver があれば、XPath 式中に、namespace URI "http://www.w3.org/1999/xhtml" に結び付けられた prefix "h" を使う事ができるようになります。

// 第 3 引数に, 先ほど生成した XPathNSResolver オブジェクトを指定して XPath 式の実行
var xpath_result = document.evaluate( "string(//h:title)", document, nsr, XPathResult.STRING_TYPE, null );
// 結果の XPathResult オブジェクトから実際の値を取得
var str = xpath_result.stringValue;

実際に動くサンプルは下記ページにあります。

HTML 5 と XPath の関係

まだ草案の段階ですが、HTML 5 においては XPath を使う時の事情がちょっと変わりそうなのでメモ程度に書いておきます。

HTML 5 の草案において、XPath との相互作用について以下の節に書いてあります。

ここに書いてある通り、XPath 式中の QName が prefix を持っていなかった場合、名前空間が null として扱われるのではなく、条件によって変化するようです。

  1. If the context node is from an HTML DOM, the default element namespace is "http://www.w3.org/1999/xhtml".
  2. Otherwise, the default element namespace URI is null.

HTML 5 においては HTML 要素は全て名前空間 "http://www.w3.org/1999/xhtml" に属するわけですが、わざわざ XPath 式中に prefix を書かずとも良い ―― つまり、今までどおり使う事ができる、ということですね。

*1:HTML 5 の草案では、HTML 要素も名前空間 URI "http://www.w3.org/1999/xhtml" に属することとなっているが、HTML 4.01 以前では HTML 要素は名前空間に属していない