JavaScript/select-optionタグによるメニューの動的更新 でselectメニューをonMouseOverで動的に<option>のラベルを更新するサンプルを試したが、実際のお仕事では更新データはAjaxを用いてXMLをAPIから取得している。その後、JavaScriptのDOMを使って値を取り出すようにした。
すると同じ現場のベテランが、「JavaScriptのDOMをそのままXML解析に使うと嵌りやすいから止した方が良い。APIもうちが作ってるので、JSONで返すようなオプションを付けてeval()した方が良いよ。」と教えてくれた。
どういう事かと聞いてみると、ブラウザ間でホワイトスペースノードを始めとして非互換な挙動があるとのこと。以下のURLを教えてくれた。
今回使ったAPIは幸いにして改行やホワイトスペースノードが存在しないXMLを返してくれていたので、「偶々」クロスブラウザでも正常に動いていた。
実のところ今までAjaxで、特にJavaScriptでのXML操作を仕事で取り組んだ事が無かったので、こうした嵌りどころに対してまだまだ無知な状態である。教えてくれてありがとうございました。
・・・で終わるのも何だかスッキリしないので、ホントにホワイトスペースノードの挙動が違うのかどうか確認してみる事にしました。あと、仕事ではprototype.jsを使っているのだけれど(XML無しの)JavaScript操作としてはjQueryの方が使用回数が多い。そこで、jQueryでAjaxで取ってきたXMLを操作するのどうしてるんだろうとさっと検索してみたら・・・
→有り難い事に、Ajaxで取得したXML DocumentをラップしてHTML DOMと同様のselectorが使えるようになっている。
ふと思い出したが、Ajaxが流行始めの事、prototype.jsが日本でも騒がれ始めた初期に、JKL.ParseXMLのようなXML解析ライブラリが合わせて紹介された時期があった。
こうしたライブラリが出てきたのも、JavaScriptでのXML DOM Element操作のクロスブラウザ問題を考えれば納得が行く。
戯れ言はこの辺にして、実際にホワイトスペースノードの挙動を確認してみる。
HTML/JavaScriptなどは一通り次のzipファイルに収めてあるので、実際に動かしてみたい方はダウンロードして下さい。
xmldom_whitspace_crossbrowser.zip
まず、ホワイトスペースノードの有無比較用のXMLファイルを用意する。
xmldom_whitspace_crossbrowser.zip 中の以下のファイルが比較用XMLになる。
xmldom_whitspace_crossbrowser.zip 中の xmldom_by_prototype.html が、Ajax部分をprototype.jsで処理し、XMLHttpRequestのresponseXML経由で直接使ってみたサンプルコードになる。
"Load XML WITHOUT White-Space-Element" と "Load XML WITH White-Space-Element" の二つのボタンが表示される。"Load XML WITHOUT..."をクリックすると books_without_ws.xml が読み込まれ、"Load XML WITH..."をクリックすると books_with_ws.xml が読み込まれる。各XMLロード後は、下部のテキストエリアに"<book>"要素の直下の子要素の名前とnodeTypeを表示する。
もしもホワイトスペースノードの解釈で違いが出てくるなら、ホワイトスペースノードはTextNode、すなわち nodeType = 3 なので、その分が下部テキストエリアに表示されるはずである。
function load_xml(url) { new Ajax.Request(url, { method : 'get', onComplete : function(xhr) { var msg = ""; var xmldoc = xhr.responseXML; var bookNodeList = xmldoc.getElementsByTagName("book"); for (var i = 0; i < bookNodeList.length; i++) { var bookNode = bookNodeList[i]; msg = msg + bookNode.nodeName + "\n"; msg = msg + " (nodeName, nodeType)\n"; // "<book>"の直下の子要素リスト var childNodeList = bookNode.childNodes; for (var j = 0; j < childNodeList.length; j++) { var el = childNodeList[j]; // 子要素のnodeNameとnodeTypeを順に表示していく。 msg = msg + " " + el.nodeName + ", " + el.nodeType + "\n"; } } $("t1").value = msg; } }); }
ホワイトスペースノード無しのXMLの場合は全ブラウザで同じ表示だったが、ホワイトスペースノード有りのXMLの場合は、予想通りFirefox/Chrome系とIEとで結果が異なった。
Firefox3.x, Chrome3 :
book (nodeName, nodeType) #text, 3 title, 1 #text, 3 author, 1 #text, 3 price, 1 #text, 3
IE8 :
book (nodeName, nodeType) title, 1 author, 1 price, 1
IEの場合はホワイトスペースノードを無視しているが、Firefox/Chromeの場合はTextNodeとして子要素リストに含まれてしまっている事が分かる。
さらに、</author>と<price>タグの間には改行しか入れていないが、それだけでもホワイトスペースノードとしてTextNodeにカウントされてしまっている。
xmldom_whitspace_crossbrowser.zip 中の xmldom_by_jquery.html が、jQueryでAjax処理およびXML要素の取得を試みたサンプルコードになる。各ボタンと下部テキストエリアに表示される内容は xmldom_by_prototype.html と同じである("Load XML WITH White-Space-Element(2)"ボタンについては後述)。
function load_xml(url) { var msg = "(nodeName, nodeType) in book node\n"; $.ajax({ async: false, // sync for alert(msg) url: url, type: 'GET', dataType: 'xml', success: function(xml){ // XML Document をjQueryでラップし、"<book>"要素以下の子要素に対して // 順に無名関数を適用し、nodeNameとnodeTypeを取り出す。 $(xml).find("book").contents().each(function() { msg = msg + this.nodeName + ", " + this.nodeType + "\n"; }); } }); $("#t1").text(msg); }
XML DOM Element 直接操作の時と同様、Firefox/ChromeではホワイトスペースノードがTextNodeに解釈され、IEではホワイトスペースノードは無視された。
Firefox3.x, Chrome3 :
(nodeName, nodeType) in book node #text, 3 title, 1 #text, 3 author, 1 #text, 3 price, 1 #text, 3
IE8 :
(nodeName, nodeType) in book node title, 1 author, 1 price, 1
jQueryが上手くラップしてくれるとはいえ、それでもホワイトスペースノードの差異までは吸収されていない。
しかし、jQueryのselectorやDOM操作用の機能を上手く使えば、必要な要素だけを簡単に取り出す事が出来る。今回の例で言えば、"<book>"の下の子要素で実際に使いたいのは<author>や<title>など、nodeType = 1(Element)であり、nodeType = 3(TEXT NODE)は無視しても良い(位置的にどう考えてもホワイトスペースなので)。
従って、以下のようにfilter()を挟んで nodeTyep = 3(TEXT NODE) を除外したリストに対してeach()をかける手法が使える。
function load_xml2(url) { var msg = "(nodeName, nodeType) in book node\n"; $.ajax({ // ... success: function(xml){ $(xml).find("book").contents() .filter(function() { // nodeType = 3(TEXT NODE) の場合はfalse -> 除外 return this.nodeType != 3; }).each(function() { msg = msg + this.nodeName + ", " + this.nodeType + "\n"; }); } }); $("#t1").text(msg); }
"Load XML WITH White-Space-Element(2)" ボタンをクリックすると、上記のload_xml2()がホワイトスペースノード有りのXMLを読み込んで実行される。果たしてその結果は、Firefox/Chrome/IEで同じものとなった。
prototype.jsであろうとjQueryであろうと、ホワイトスペースノードの解釈についてはブラウザに依ってしまう。
ただしjQueryの場合について言えば、DOM要素の操作がかなり使いやすくなっている為、複雑なコードを書かなくともブラウザ間の差異を吸収することができる場合もある。
ちなみに最初はmsgをalert()表示させていたのだが、jQueryで何故かちゃんとsuccessの中で追加した文字列が表示されずに嵌った。
原因としては、最初は"async: false"を無しで読んでいた為、ajax処理が完了する「前に」alert()されたので、nodeName/nodeTypeが表示されていなかった。"async: false"を付ける事で、nodeName/nodeTypeの一覧が追加された後のmsgを取得できるようになった。
・・・が、"async"オプションが原因である事に気づくまでに2時間ほど嵌ってしまった・・・。
ホント、JavaScriptと付き合うの疲れるヨー。
コメント