2016 02 03

XMLの2

エレベーターに乗って、ドアが閉まって、動き出すかと思ったらドアが開いて人が乗ってくるときがある。 急いでいる時は、これが微妙にイラッとする。 なので優しい俺は、目の前でエレベーターのドアが閉まった時は、少し待ってからボタンを押すようにしている。

今日もそんな優しさを発揮して、ちょっと待ってから上のボタンを押したのに、さっき閉まったドアが開いてしまった。 で、開くドアの隙間から、エレベーターの奥の鏡に向かってファイティングポーズを取っているのが見えてしまった。 しかも鏡越しにちょっと目が合ってしまった。

エレベーターに乗ってまずは行き先階のボタンを押すのだが、押したつもりがちゃんと押せてなくて、いつまでたっても動き出さない時がたまにある。 今日の彼もそうだったんだろう。

俺の場合、ぼーっとしてるところにドアが開いて誰かが乗ってきて気不味い思いをしたのだが、ファイティングポーズを見られるのはどうなんだろう。 あんまり考えたくないな。

XML

昨日の続き。 今日はXMLファイルを読んで表示するところまで。

XMLファイルを読むには、MSXMLを使うのが簡単。 オブジェクトを生成して、XMLファイルを指定すると、XMLファイルからDOMを構築して返してくれる。 このDOMの要素を、HTMLのdivに置き換えて表示する。

という方針で、昨日の xml.hta では空っぽだった doLoad() を埋める。

function doLoad() { try { var base = document.getElementById( "base" ); base.innerHTML = ""; domToHtml( loadXml( getFilename() ), base ); } catch ( e ) { alert( e ); } function getFilename() { var filename = document.getElementById( "txtFile" ).value; if ( !filename ) { throw "ERROR : filename"; } return filename; } function loadXml( filename ) { var dom = new ActiveXObject( "MSXML2.DOMDocument" ); if ( !dom.load( filename ) ) { throw "ERROR : xml load"; } return dom; } function domToHtml( node, root ) { switch ( node.nodeType ) { case 1: // element elementToHtml( node, root ); break; case 3: // text textToHtml( node, root ); break; case 4: // cdata cDataToHtml( node, root ); break; case 7: // processing instruction processingInstructionToHtml( node, root ); break; case 9: // document documentToHtml( node, root ); break; case 10: // doctype doctypeToHtml( node, root ); break; case 5: // entity reference case 6: // entity case 8: // comment case 11: // document fragment case 12: // notation break; default: throw "ERROR : unsupported nodeType : " + node.nodeType; } return root; } function elementToHtml( node, root ) { var div = document.createElement( "div" ); var attributes = ""; if ( node.attributes ) { attributes = attributesToString( node.attributes ); } if ( node.hasChildNodes() ) { div.appendChild( document.createTextNode( "<" + node.nodeName + attributes + ">" ) ); Array.prototype.forEach.call( node.childNodes, function ( childNode ) { domToHtml( childNode, div ); } ); div.appendChild(document.createTextNode( "</" + node.nodeName + ">" ) ); } else { div.appendChild(document.createTextNode( "<" + node.nodeName + attributes + "/>" ) ); } root.appendChild( div ); } function textToHtml( node, root ) { root.appendChild( document.createTextNode( node.nodeValue ) ); } function cDataToHtml( node, root ) { var divInner = document.createElement( "div" ); divInner.appendChild( document.createTextNode( node.nodeValue ) ); var divOuter = document.createElement( "div" ); divOuter.appendChild( document.createTextNode( "<![CDATA[" ) ); divOuter.appendChild( divInner ); divOuter.appendChild( document.createTextNode( "]]>" ) ); root.appendChild( divOuter ); } function processingInstructionToHtml( node, root ) { var div = document.createElement( "div" ); div.appendChild( document.createTextNode( "<?" + node.nodeName + " " + node.nodeValue + "?>" ) ); root.appendChild( div ); } function documentToHtml( node, root ) { if ( node.hasChildNodes() ) { Array.prototype.forEach.call( node.childNodes, function ( childNode ) { domToHtml( childNode, root ); } ); } } function doctypeToHtml( node, root ) { var div = document.createElement( "div" ); div.appendChild( document.createTextNode( "<!DOCTYPE " + node.nodeValue + ">" ) ); root.appendChild( div ); } function attributesToString( attributes ) { return Array.prototype.reduce.call( attributes, function ( a, b ) { return a + " " + b.name + "=\"" + b.value + "\""; }, "" ); } }

属性名や値の色を帰るのは後回しにして、まずは最低限のファイルを読んで内容を画面表示するところまでとしている。

下請け関数を全部 doLoad() の中に入れてしまったが、これは同じレベルにたくさんfunctionが並んでいると、どこで使っているのか判り難いと思ったから。 この後に作るdoSearchやdoSaveも同様にして、共通に使えるものがあれば一つ階層を上げる。

せっかくなので、主な下請け関数の簡単な説明。

loadXml

MSXML2.DOMDocument のオブジェクトを生成し、引数で指定されたファイル名のXMLファイルを読んで、構築したDOMを返す。

domToHtml

XMLのDOMの要素をHTMLのDOMの要素に変換する。

変換先は全部divの中のテキスト要素なのだが、変換元であるXMLの要素はいろんな種類があるので、その種類を判別して専用の下請け変換処理を呼び出している。 その下請け処理の中からまた自分自身を呼び出したりしているのは、XMLが再帰的な構造になっているから。

俺的に対応不要と思われる種別や積極的に捨てたいコメントなどの種別は、下請けの変換処理を呼ばないことで捨てている。

attributesToString

属性情報の擬似配列を、 属性名="属性値" の繰り返しの文字列に変換する。

MSXML2.DOMDocument は、実質2行でファイルの文字コードまで良きに計らってくれる。 便利な世の中になったものだなぁ… なんて爺いのようなことを思いながら動作確認。

まずは一通りの要素を網羅した小さなXMLファイルを作って読ませてみた。 特に問題なく動く。 要素の開始/終了、入れ子要素のインデント、子供を持たない空要素の表示、各要素の属性の名前と値の表示、全て問題なし。 捨てたい要素は正しく表示されない。

もうちょっと実用的なところで、古い雑誌のサンプルコードとして付いていたstrutsやspringのXMLファイルを読ませてみた。 これも特に問題無し。

次に大きなファイルで、と思ったのだが、手元に大きなXMLファイルが無い。 何か良いものがないかとしばらく考えて、郵便番号のデータをXML化することにした。 郵便局のサイトで全国の郵便番号データがCSV形式で公開されているので、これをダウンロードしてきてXML化する。

ダウンロードした全国の郵便番号CSVデータはこんな感じ。

01101,"060 ","0600000","ホッカイドウ","サッポロシチュウオウク","イカニケイサイガナイバアイ","北海道","札幌市中央区","以下に掲載がない場合",0,0,0,0,0,0 01101,"064 ","0640941","ホッカイドウ","サッポロシチュウオウク","アサヒガオカ","北海道","札幌市中央区","旭ヶ丘",0,0,1,0,0,0 ...

これを、各レコードの後ろのどうでもよさそうな数字は無視し、XML的に問題となりそうな文字を適当に変換して、最終的に次のように変換した。

<?xml version="1.0" encoding="UTF-8"?> <全国> <郵便番号情報> <全国地方公共団体コード>01101</全国地方公共団体コード> <旧郵便番号>060 </旧郵便番号> <郵便番号>0600000</郵便番号> <トドウフケンメイ>ホッカイドウ</トドウフケンメイ> <シクチョウソンメイ>サッポロシチュウオウク</シクチョウソンメイ> <チョウイキメイ>イカニケイサイガナイバアイ</チョウイキメイ> <都道府県名>北海道</都道府県名> <市区町村名>札幌市中央区</市区町村名> <町域名>以下に掲載がない場合</町域名> </郵便番号情報> ... </全国>

要素名が長いせいで、CSVの時は12MBぐらいだったファイルが、XMLにしたら70MBぐらいになった。 さすがにここまで大きいのはいらないので、北の方に地域を限定して20MBぐらいのファイルにした。 で、このファイルを読ませてみたのだが…

帰ってこない。

何か頑張ってはいるようなので、そのまましばらく待っているとこんな警告が表示された。

このスクリプトの実行を中止しますか?

このページのスクリプトが、webブラウザーの実行速度を遅くしています。
このスクリプトを実行し続けると、コンピューターが応答しなくなる可能性があります。

中止と続行のボタンが表示されているので、続行を選択。

またしばらく待つと同じ警告が表示される。 続行。 警告。 続行。 警告。 続行。 諦めずに続行を選択し続けていると、ついにPCの方が音を上げた。

Error: この操作を完了するための十分な記憶域がありません。

いつ終わるのか不安なままに続行を選択し続けていたので、このダイアログが表示された時は 「勝った!」 なんて思ったのだが、冷静に考えれば負けだよな。 というか誰も勝ってない。

記憶域が不足って、実メモリだけじゃなく仮想記憶も足りないってことか。 まあ確かにテストで使っている surface pro 2 / Windows10 はメモリもディスクもちょっと少ない。 なのでそれらが潤沢にあるはずの Windows7 マシンで試してみたのだが、こちらも、上限が上がっている雰囲気はあるものの結果は同じだった。 うーん…

タスクマネージャーでメモリ使用量の推移を見ると、処理開始以降緩やかに、しかし直線的に着実にメモリの使用量が増えていく。 そして、だいたい1GBぐらい使用量が増えたところで終了。 20MB程度のデータを表示するのに使うメモリが1GBって…

途中で処理を止めたりしながら少しだけ細かく観察した結果、判ったのは、メモリを食っているのがMSXMLによるXMLの読み込みではなくて表示用のHTMLのDOM構築らしいこと。 さて、どうするかな。