JavaScriptでXMLを扱う方法
現状でJavaScriptからXMLにアクセスするには、APIとしてW3C-DOMを用いることが唯一の現実的な選択肢となります(除くJScript.NET)。そのためには以下のようなプロセスを踏む必要があります。
- 新規Documentオブジェクトを取得する
- 任意のXMLファイルをロードする
- DOM APIを使ってXMLを操作する
このうち1はDOM2 Coreで標準化されており、2はDOM3 Load and Saveで標準化されるべき分野に属します。しかし勧告になるまで時間が掛かったため、その間に(いつものように)ブラウザごとの独自拡張による方法が蔓延した結果、現在ではMSXMLの独自拡張からGeckoやKHTMLにも採用されたXMLHttpRequestを用いるのが一般化しているようです。
XMLHttpRequestを使った方法については「XMLHttpRequestについて」に(仮にですが)まとめてありますのでそちらを参照してください。このページではMSIE(Windows版)とGeckoベースのブラウザの独自拡張による方法を解説します。
IEの場合
Windows上のIEの場合、Microsoftが提供するXMLパーサー”MSXML”を使ってXMLを操作することになります。バージョン5.0以降のIEには必ずこのMSXMLが付属しているので、IE5.0以降では無条件でXMLを扱えることになります。なお、MSXMLそのものは完全にWebブラウザから独立したコンポーネントです。IEとは別にバージョンを上げることができるので、同じバージョンのIEを使っていても使用できるMSXMLのバージョンが異なる(すなわち使用できる機能も異なる)ということは有り得ます。またブラウザと無関係である以上、例えばWSHなどからもアクセスできます。
JavaScriptからMSXMLにアクセスするためにはActiveXObjectを利用します。
var domDocument = new ActiveXObject("Microsoft.XMLDOM");
ActiveXObjectのコンストラクタで Microsoft.XMLDOM を指定することで、新規Documentオブジェクトが取得できます。このときの引数(ProgID)と、使用されるMSXMLのバージョンとの関係はやたらとややこしいので、とりあえず常に Microsoft.XMLDOM を指定することとして解説を続けます。
ProgIDとMSXMLのバージョンについて、現時点で調べが付いている部分のメモ。
Microsoft.XMLDOM … 使用されるバージョン≦3.0。IE 6.0以降がインストールされている場合は無条件で3.0。IE 5.Xを使用している場合、MSXML 3.0をxmlinst.exeを使用して置換モードでインストールした場合には3.0。それ以外の場合には同梱されているMSXMLのバージョン(2.X)。
MSXML.DOMDocument … 基本的にMicrosoft.XMLDOMと同じ。
Msxml2.DOMDocument … MSXML 3.0がインストールされている場合のみ使用可能。使用されるのは必ず3.0。
Msxml2.DOMDocument.3.0 … 必ず3.0。
Msxml2.DOMDocument.4.0 … 必ず4.0。
基本的に、バージョンを明示しないProgIDでは、バージョン3.0までしか使用されない(参考、参考2)。
XMLファイルを読み込むにはloadメソッドを用います。引数にはURLを指定しますが、基本的に同じドメイン内のファイルしか読み込めないことに注意してください(ローカル上で実行する場合は少し事情が異なります。詳しくはMSXML Client Securityを参照のこと)。
var domDocument = new ActiveXObject("Microsoft.XMLDOM");
domDocument.load('path/to/xml');
// ... 以後、DOM APIを使った処理
ただしデフォルトでは非同期的に読み込まれるので、読み込み完了前にアクセスが行われるとエラーが発生します。完全に読み込まれてから処理を行うには2つの方法がありますが、簡単なのは async プロパティを false に設定して同期読み込みを行う方法です。
var domDocument = new ActiveXObject("Microsoft.XMLDOM");
domDocument.async = false;
domDocument.load('path/to/xml');
// ... 以後、DOM APIを使った処理
同期的に読み込みを行った場合、loadメソッドは読み込みの成否に応じて真偽値を返すので、parseErrorプロパティからIXMLDOMParseError オブジェクトにアクセスしてエラー情報を取り出すことができます。エラー処理を含めて書くと次のようになります。
var domDocument = new ActiveXObject("Microsoft.XMLDOM");
domDocument.async = false;
if(domDocument.load('path/to/xml')) {
// ... DOM APIを使った処理
} else {
var err = domDocument.parseError;
alert("エラーが発生しました :"+ err.reason +
"\nURL :"+ err.reason +
"\n行 :"+ err.line +", 列 :"+ err.linepos);
}
もう一つの方法は onreadystatechange イベントの中で readyState の値を調べる方法です。
var domDocument = new ActiveXObject("Microsoft.XMLDOM");
domDocument.onreadystatechange = loadHandler;
domDocument.load('path/to/xml');
function loadHandler() {
if(domDocument.readyState==4) {
if(domDocument.parseError.errorCode!=0) {
var err = domDocument.parseError;
alert("エラーが発生しました :"+ err.reason +
"\nURL :"+ err.reason +
"\n行 :"+ err.line +", 列 :"+ err.linepos);
} else {
// ... DOM APIを使った処理
}
}
}
loadメソッドは読み込みの成否に関わらず即座に処理を終えますので、エラーが発生したかどうかはイベントハンドラ内で調べる必要があります。こちらの方法は複数のドキュメントを並列的に読み込む場合に向いています。
読み込みが完了したDocumentオブジェクトに対してはDOM APIを使ってアクセスできます。このとき使用できるプロパティ/メソッドについてはMSXMLのリファレンスを参照してください。”window.document”に対して用いられる、所謂DHTMLのためのものとは異なりますので注意してください。
Geckoベースのブラウザの場合
Geckoベースのブラウザで新規Documentオブジェクトを取得するには、DOM2 Coreで定義された DOMImplementation の createDocumentメソッドを用います。
var domDocument = document.implementation.createDocument("", "", null);
引数は次の通りです。
- namespaceURI … 作成されるDocumentのルートエレメントのNamespace URI
- qualifiedName … 作成されるDocumentのルートエレメントのQualifiedName(修飾名)
- doctype … 作成されるDocumentの文書型を表すDocumentTypeオブジェクト
外部から読み込む場合、上記した例のように、いずれも特に設定する必要はありません。
XMLファイルを読み込むにはloadメソッドを用います。引数にはURLを指定しますが、同じドメイン内のファイルしか読み込めません。IEとは異なり、ローカル上で実行する場合も例外でない(つまりローカル上のファイルしか読み込めない)ことに注意してください。
var domDocument = document.implementation.createDocument("", "", null);
domDocument.load('path/to/xml');
// ... 以後、DOM APIを使った処理
やはりこのままだと非同期で読み込まれてしまい、エラーになる可能性があります。回避策はやはり2つあって、やはり簡単なのは async プロパティを false に設定することです。
var domDocument = document.implementation.createDocument("", "", null);
domDocument.async = false;
domDocument.load('path/to/xml');
// ... 以後、DOM APIを使った処理
ただしこの方法は古いバージョンのGeckoエンジンでは使用できません。私は最新のソースを追いかけているわけではないので正確には分かりませんが、Mozilla's DOM Sample Projectを参考にする限り、Mozilla 1.3.1 の段階ではasyncプロパティは存在しません。またNetscape 7.0では使用不可能なのを確認しています。
確実に使用できるのは onload イベントの中で処理を行う方法です。
var domDocument = document.implementation.createDocument("", "", null);
domDocument.onload = loadHandler;
domDocument.load('path/to/xml');
function loadHandler() {
// ... DOM APIを使った処理
}
さて、続いてエラー処理についてです。Geckoベースのブラウザの場合、エラー処理は少々複雑です。
まず読み込みそのものに(ファイルが見つからないなどの理由で)失敗した場合と、XMLのパースに失敗した場合とでエラー時の動作が異なります。更にその双方が、少なくとも一度仕様変更されています(もしかしたらもっと多いかも知れません)。先述したとおり私は細かくソースを追ってはいませんので、ここでは参考までに、Netscape 7.0での動作を旧仕様として、Mozilla Firefox 1.0PRの動作を新仕様として紹介しておきます。
まず旧仕様においては、読み込みそのものに失敗しても一切エラーは起こりませんので、documentElement が null であることから判断するしかありません。
//上記の例の続き
function loadHandler() {
if(domDocument.documentElement==null) {
alert('XMLファイルの読み込みに失敗しました');
return ;
} else {
// ... DOM APIを使った処理
}
}
次に、XMLのパースに失敗した場合、以下のようなXMLが読み込まれたものとして扱われます。エラーらしい動作は一切起こりません。
<parsererror xmlns="http://www.mozilla.org/newlayout/xml/parsererror.xml">
XML パース エラー:形式が不正です
場所:file:///path/to/xml
行番号 1、列 10:
<sourcetext>
エラーが発生したXMLの該当行
----------^
</sourcetext>
</parsererror>
現実的にはルート要素の名前がparsererrorで、namespaceURIがhttp://www.mozilla.org/newlayout/xml/parsererror.xmlだったらエラーと判断することになるでしょう。ちなみにこの名前空間にどんな根拠があるのかは不明ですが、ソース中にも直書きしてあったりするので、ある程度は信頼できるものと思われます。
一方新仕様の場合、ファイルの読み込みそのものに失敗すると例外がthrowされるので、loadメソッドはtryブロックの中で実行する必要があります。
var domDocument = document.implementation.createDocument("", "", null);
domDocument.onload = loadHandler;
try {
domDocument.load('path/to/xml');
}
catch (e) {
alert(e.toString());
}
function loadHandler() {
// ... DOM APIを使った処理
}
次に、XMLのパースに失敗した場合、以下のようなXMLが読み込まれたとして扱われます。やはりエラーらしい動作は起こりません。
<parsererror xmlns="http://www.mozilla.org/newlayout/xml/parsererror.xml">
XML パースエラー: 整形式になっていません
URL: file:///path/to/xml
行番号: 1, 列番号: 10:
<sourcetext>
エラーが発生したXMLの該当行
----------^
</sourcetext>
</parsererror>
要素名や名前空間は旧仕様と同じですが、よく見比べると微妙に書式が異なっています。この違いにどの程度の意味があるのかは分かりませんので、あくまで参考に留めておいてください。
まとめると、新旧両方で正しくエラー処理を行うには、以下のようにする必要があります。
var domDocument = document.implementation.createDocument("", "", null);
domDocument.onload = loadHandler;
try {
domDocument.load('path/to/xml');
}
catch (e) {
alert(e.toString());
}
function loadHandler() {
var root = domDocument.documentElement;
if(root==null) {
alert('XMLファイルの読み込みに失敗しました');
return ;
} else if(root.tagName=='parsererror'
&& root.namespaceURI=='http://www.mozilla.org/newlayout/xml/parsererror.xml') {
alert(root.firstChild.nodeValue);
return ;
} else {
// ... DOM APIを使った処理
}
}
クロスブラウザ・XmlLoader
以上の内容をもとに、クロスブラウザで使用できるXmlLoaderクラス(非同期の読み込みにのみサポート)をサンプルとして挙げておきます。
xmlloader.js ソース表示 (文字コード・UTF-8、BSDライセンス)
使用例:
//読み込み完了時に呼び出される関数
function loadHandler(doc) {
alert("読み込みました: "+ doc.documentElement.tagName);
}
//エラー発生時に呼び出される関数
function errorHandler(err) {
alert("読み込みに失敗しました:\n"+ err.toString());
}
//一番簡単な使い方(0.0.3互換)
XmlLoader.load('sample.xml', loadHandler);
//エラー処理を追加
XmlLoader.load('sample.xml', loadHandler, errorHandler);
//複数のファイルを読み込む場合などはインスタンス化してから
var loader= new XmlLoader(loadHandler, errorHandler);
var files = new Array('sample1.xml', 'sample2.xml', 'sample3.xml');
for(var i=0; i<files.length; i++) {
loader.load(files[i]);
}
//イベントハンドラを変更
loader.onload = function(doc) { alert("名前空間URI: "+ doc.documentElement.namespaceURI); }
loader.onerror= function(err) { alert("読み込みに失敗したURL"+ err.url); }
loader.load('sample.xml');
簡易リファレンス:
クラス XmlLoader
名称 | 型 | 説明 |
onload | function | 読み込み完了時に呼び出される関数。 第一引数には DOMDocument が渡される |
onerror | function | エラー発生時に呼び出される関数。 第一引数には XmlLoaderError が渡される |
名称 | アクセス | 戻り値 | 引数と型 | 説明 | |||||||||
[コンストラクタ] | public | XmlLoader |
| - | |||||||||
load | public | bool |
| 指定されたURLからXMLを読み込む。 XMLの読み込み操作がサポートされていれば真を返す | |||||||||
load | public static | bool |
| 指定されたURLからXMLを読み込む。 XMLの読み込み操作がサポートされていれば真を返す |
クラス XmlLoaderError
名称 | 型 | 説明 |
reason | string | エラーの詳細 |
url | string | エラーが発生したXMLのURL |
line | int | エラー発生行 |
linepos | int | line中でのエラー発生位置 |
srcText | string | エラーを含む行全体 |