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なども考慮にいれなくてはならないので、もっとコード量は増える)。