JavaScriptでXMLをParseする

先の例では、超簡単な例で、YUIのレスポンスオブジェクトからresponseXMLを取り出してみた。
今回は以下のような、ちょっと複雑だが、マッシュアップするときなどにありがちなデータを取得してみる。

<?xml version="1.0" encoding="utf-8"?>
<testdata>
<company>
	<name>
		ほげほげ(株)
	</name>
	<place>
		ほげほげ県ほげほげ市1-1
	</place>
	<domain>
		電気
	</domain>
	<salesman>
		<sname>ほげ山ほげ夫</sname>
		<sphone>
			<snumber>090-0000-0000</snumber>
			<snumber>090-1111-1111</snumber>
		</sphone>
		<sname>ほげ田ほげ彦</sname>
		<sphone>
			<snumber>090-2222-2222</snumber>
		</sphone>
	</salesman>
</company>
<company>
	<name>
		あらら(株)
	</name>
	<place>
		あらら県あらら市1-1
	</place>
	<domain>
		機械
	</domain>
	<salesman>
		<sname>あら山あら夫</sname>
		<sphone>
			<snumber>090-3333-33333</snumber>
		</sphone>
		<sname>あら田あら彦</sname>
	</salesman>
</company>
</testdata>

XMLを解析(パース)するやり方には、DOMとSAXがあるのは今も昔も同じ。
Javascriptだとインタプリタなので、SAXということになるのだろう。
JavascriptでのXMLのパースについてググってみたが、地道にパースしてみたら、とか書いてあったりする。
なので、responseXMLを地道にパースしてみることにした。

前提は、整形式のXMLの構造が分かっていること(SAXでパースするので、妥当なXMLである必要はない)。
マッシュアップの際には、とりあえず、RESTサービスにGETでクエリーを投げて、現物を確認してからコーディングするのが間違いない。
なので、妥当な前提と考える。

実験は3種類。

実験0 responseXMLそのものがJavascriptとしてどう認識されるか、を知る。
実験1 responseXMLのテキストノードを、とりあえず取るようにコードする。例のXMLから、companyの名前と場所を取り出してみる。
実験2 W3CのDOM1の仕様書で定義されているインターフェイスをつかって、ノードをある程度判断しながらパースするコーディングをする。例のXMLから、salesmanの名前と電話番号に関するタグとテキストを取得してみる。

まずは、そのために以下のような画面を用意した。

以下が、上の実験を行ったコード。

<HTML>
<HEAD>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<META http-equiv="Content-Style-Type" content="text/css">
<TITLE>Ajax_Sampling</TITLE>
<style type="text/css">
.output {
    width: 500px;
    background-color: #6D739A;
    color: white;
    border: 1px solid black;
}

</style>

<!-- 読み込むjs --> 
<script type="text/javascript" src="scripts/yui/yahoo/yahoo-min.js" >
</script> 
<script type="text/javascript" src="scripts/yui/yahoo-dom-event/yahoo-dom-event.js" >
</script> 
<script type="text/javascript" src="scripts/yui/connection/connection-min.js" >
</script> 

<script type="text/javascript">
// namespaceの定義
YAHOO.namespace('EGP');

var ajaxHandlers=YAHOO.EGP.ajaxHandlers;
var ajaxCallback=YAHOO.EGP.ajaxCallback;
var btnHandlers=YAHOO.EGP.btnHandlers;

//Ajaxハンドラー
ajaxHandlers = {
	
	// 受信成功時の処理
	responseSuccess:function(oj){
		// 取得したデータの表示
		var xml0	=	oj.responseXML;
		// 実験結果0; xml0.getElementsByTagNameの正体
		var node0 	=	xml0.getElementsByTagName("testdata");
		YAHOO.util.Dom.get('output_0').innerHTML += node0;


		// 実験結果1; companyごとの処理
		var wkResult='';
		// companyを取得(childrenは全てHTML Ocject): 2つ取れる。
		var node1 =	xml0.getElementsByTagName("company");
		var nameArray =	new Array();
		var placeArray = new Array();
		var resultArray = new Array();
		for(i=0; i<node1.length; i++){
			//node1[i].getElementsByTagName("name")はHTML Collection.
			//なので、item(index)でエレメント(object Node)が取り出せる。
			wkName = node1[i].getElementsByTagName("name").item(0);
			wkPlace = node1[i].getElementsByTagName("place").item(0);
			//エレメント(ノード)の取り出し。
			nameArray[i] = wkName.firstChild.nodeValue;
			placeArray[i]= wkPlace.firstChild.nodeValue;
		}
		
		//実験1の表示; 
		for(i=0; i<node1.length; i++){
			wk = 'Conpany: ' + nameArray[i] + ', Place: ' + placeArray[i];
			YAHOO.util.Dom.get('output_1').innerHTML += wk;
		}

		// 実験結果2; できるだけNodeのインターフェイスを使う。
		for(i=0; i<node1.length; i++){
			// salesmanタグの取り出し。
			// node2はHTML Collectionになる。
			node2 =	node1[i].getElementsByTagName("salesman");
			// セールスマンごとの処理
			for(j=0; j<node2.length; j++){
				// HTML CollectionからNodeを取り出す。
				wkSname = node2.item(j);
				// セールスマンの名前を取り出す。
				// 取り出したNode<salesman>からchid(NodeList)の取り出し。
				wkChildren = wkSname.childNodes;
				// wkChildrenにはTextノードはいない。
				for(k=0; k < wkChildren.length; k++){
					//wkChildrenにはTextノードはいないので、以下ではELEMENTノードしかとれない。
					
					//nodeType=3(TEXT_NODE)の時は、テキストを取り出す。
					if(wkChildren.item(k).nodeType == 3){
						YAHOO.util.Dom.get('output_2').innerHTML += wkChildren.item(k).nodeValue+ ' ';
					}else{
						// nodeType=1(ELEMENT_NODE)の時はタグ名を取り出す。
						// sname, sphoneがとりだせるはず。
						if(wkChildren.item(k).nodeType == 1){
							YAHOO.util.Dom.get('output_2').innerHTML += wkChildren.item(k).nodeName+ ' ';
							// ELEMENT_NODEならもう一度、子要素を調べる。
							// snameの子要素にはTEXTノードがあるので取れるが、
							// sphoneの子要素にはTEXTノードがない。			
							wkGrandChildren = wkChildren.item(k).childNodes;				
							for(l=0; l < wkGrandChildren.length; l++){
								// snameについては、名前がとれる。
								if(wkGrandChildren.item(l).nodeType == 3){
									YAHOO.util.Dom.get('output_2').innerHTML += wkGrandChildren.item(l).nodeValue+ ' ';
								}else{
									// sphoneについては、タグ名(snumber)がとれる。
									if(wkChildren.item(k).nodeType == 1){
										YAHOO.util.Dom.get('output_2').innerHTML += wkChildren.item(k).nodeName+ ' ';
									}									
								}
							}
						}
					}
				}
			YAHOO.util.Dom.get('output_2').innerHTML += '<br/>';
			}
		}
	},

	// 受信失敗時の処理
	responseFailure:function(oj){
		// backgroudを赤にする
		YAHOO.util.Dom.setStyle('output_0',
				'background-color','red');
		var sFailure='ステータス: ' + oj.status +
		 				'\n' + 'ステータステキスト: ' +
						oj.statusText +
						'\n' +'読み込みに失敗しました。';
		// 
		// 非表示
		YAHOO.util.Dom.setStyle('output_1',
				'visibility','hidden');
		YAHOO.util.Dom.setStyle('output_2',
				'visibility','hidden');
	},

	// テキストを読み込む
	startRequest :function(url){
		YAHOO.util.Connect.asyncRequest('GET',url,
				ajaxCallback, null);
	} 		
};

// コールバック成功/失敗時の振り分け
ajaxCallback =
{
	success:ajaxHandlers.responseSuccess,
	failure:ajaxHandlers.responseFailure,
	// cache: falseのしないと、HttpServerがajax_test2.xml
	// の内容をcacheしてしまう。
	cache: false,
	scope: ajaxHandlers
};

// Buttonの処理
// ボタンハンドラを定義
btnHandlers =
{
	click:function(){
		ajaxHandlers.startRequest("data/ajax_test2.xml")
	}
}
//ボタンにイベントを仕掛ける。btnがAvailableになってからだよ。
YAHOO.util.Event.onAvailable('btn',
	function() {
		YAHOO.util.Event.on('btn','click',
				btnHandlers.click);
	}
);

</script>
</HEAD>
<BODY>
<p>
実行ボタンを押してください。<br/>
<ul>
<li>Serverのdata/ajax_test2.xmlを取得して、内容を表示します。
<li>取得の成功、失敗に関わらず、レスポンスオブジェクトのプロパティーを表示します。
</ul>
</p>
<div>
<input id="btn" type="button" value="実行" />

</div>
<p>
<div id="output_0" class="output">
実験結果0<br/>
</div>
</p>
<p>
<div id="output_1" class="output">
実験結果1<br/>
</div>
</p>
<p>
<div id="output_2" class="output">
実験結果2<br/>
</div>
</p>
</BODY>
</HTML>

以下に結果の画面を示す。

YUIから得られるresponseXMLは、HTMLCollectionとして認識されることが分かる。
なので、実験1では、それを前提にNodeオブジェクトを取り出している。

特に、実験2のように、中途半端にDOM1インターフェイスを使ってコードすると面倒なことになることが分かる。

なので、マッシュアップする際には

  • 何が取りたいのか

をはっきり決めて、ピンポイントでとりにいくのがよい。

中途半端にパースしようとすると、上の実験2のようなコードになって、リカーシブにコーディングしないと(完全には)解決できなくなってしまう。
(実際には、Attribute_Nodeなども考慮にいれなくてはならないので、もっとコード量は増える)。