2016 02 08

XMLの7

昨日の続き。 今日はファイル出力を実装する。

まずは何をファイル出力するか。

画面表示しているものを、画面表示しているままに出力したい。

画面には、コメントを除外してインデントを整えた結果を表示している。 検索実行後なら、検索条件に該当する要素がリーフとなるように、要素ツリーとして表示している。 これをこのままで。

次に何でファイル出力するか。

とりあえずMSXMLは駄目。 あれで出力すると、インデントどころか改行すらされない。 まあそれがXMLの仕様だろうし、無駄なデータが減って計算機的にはいいのかもしれないが、人にはただ辛いだけだ。

なので自前でファイル出力を書く。 ファイルの読み込み同様、いろんなencodingを扱うために ADODB.Stream を使う。

当然、実際の書き込み前にencodingを指定しなきゃいけないのだが、読み込みの時と違うのは、どのencodingにすればいいのかがもう判っていること。 直前の読み込み時に取得した値をそのまま書き込み処理に渡せるように保持しておいてもいいし、現在表示している先頭行のXML宣言から取得してもいい。

更に、書き出し時にencodingを選択できるようにすれば、書き出しがencodingの変換も兼ねて便利かもと一瞬考えたが、これは一瞬でやめた。 きっと使わない。

出力するファイル名はどうするか。

パスは、とりあえず元のファイルがあった場所と同じでいいだろう。 そこに、ちょっと名前を変えて出力する。 どう変えるかは、出力時に決められるようにする。

そんな感じで doSave() を実装する。

function doSave() { try { var fileame = getFilename(); if ( fileame !== null ) { var strm = new ActiveXObject( "ADODB.Stream" ); strm.Charset = getEncoding(); strm.Open(); writeFromRoot( document.getElementById( "base" ) ); strm.SaveToFile( fileame, 2 ); strm.Close(); alert( "fin" ); } } catch ( e ) { alert( e ); } function getFilename() { var partName = prompt( ".xml を .入力値.xml に変えて出力する。", "1" ); return partName === null ? null : document.getElementById( "txtFile" ).value.replace( /\.xml$/, "." + partName + ".xml" ); } function getEncoding() { var enc = document.getElementById( "base" ).getElementsByTagName( "div" )[ 0 ].innerText.match( /\s+encoding="([^"]+)"/ ); return enc ? enc[ 1 ] : "Shift_JIS"; } function writeFromRoot( rootNode ) { Array.prototype.forEach.call( rootNode.childNodes, function ( node ) { if ( node.nodeName === "DIV" ) { dispatchWriter( node, "" ); } } ); } function dispatchWriter( div, indent ) { if ( div.currentStyle.display === "block" ) { strm.WriteText( indent, 0 ); if ( div.getElementsByTagName( "div" )[ 0 ] ) { writeTree( div, indent ); } else { writeLeaf( div, indent ); } strm.WriteText( "\r\n", 0 ); } } function writeLeaf( div, indent ) { Array.prototype.forEach.call( div.childNodes, function ( node ) { switch ( node.nodeName ) { case "SPAN": strm.WriteText( node.innerText, 0 ); break; case "#text": strm.WriteText( node.nodeValue, 0 ); break; default: throw "ERROR : unsupported node : " + node.nodeName; } } ); } function writeTree( div, indent ) { var isAfterDiv = false; Array.prototype.forEach.call( div.childNodes, function ( node ) { switch ( node.nodeName ) { case "DIV": if ( !isAfterDiv ) { strm.WriteText( "\r\n", 0 ); } dispatchWriter( node, indent + " " ) ; isAfterDiv = true; break; case "SPAN": if ( isAfterDiv ) { strm.WriteText( indent, 0 ); isAfterDiv = false; } strm.WriteText( node.innerText, 0 ); break; case "#text": if ( isAfterDiv ) { strm.WriteText( indent, 0 ); isAfterDiv = false; } strm.WriteText( node.nodeValue, 0 ); break; default: throw "ERROR : unsupported node : " + node.nodeName; } } ); } }

もうちょっと簡単に書けるかと思ったのだが、意外に面倒だった。 あと、同じような処理があっちこっちに出てきているのがさすがに気になってきたが、そっちは後で整理する。

getFilename

入力ダイアログを表示して、そこに入力された値を読み込んだファイル名の拡張子の前に入れることで保存用のファイル名とし、返す。

ファイル名として使える文字かどうかのチェックはしていないが、これは、駄目な文字ならファイル出力でどうせエラーになるだろうから。 プログラムの処理の中では遅いか早いかの違いはあるのだが、使う側からすればダメだとわかるタイミングは実質同じで、ダメという結果も同じだからね。

writeFromRoot

HTMLのDOMツリーをファイル出力する。

表示する/しないがdiv単位になので、ファイル出力もdiv毎に処理するのだが、その起点になるのがこいつ。 まあ、起点になっているだけで、実態は下請けに丸投げなのだが。

dispatchWriter

渡されたdivが画面表示しているかどうかを調べ、表示している場合は子要素にdivがあるかどうかを調べる。 で、子要素にdivがあるなら writeTree を、無ければ writeLeaf を呼び出す。

これもまた二次下請けを呼び出して丸投げ。

writeTree

末端でもあり中間でもある。

渡されたdivに直接ぶら下がっているspanとテキストノードは自分でファイル出力するが、divは dispatchWriter に任せる。

writeLeaf

こいつが真の末端の処理。

渡されたdivにぶら下がっているspanかテキストノードを、実際にファイル出力する。

最上位から次々と下請に丸投げを繰り返すとか、社会の縮図だな。 dispatchWriterと writeTreeは再帰的に相互に呼び出していてちょっと複雑だけど、これはこれでまた社会の縮図って気もするな。 お奉行様と越後屋のような。 って、いつの社会だよ。

そうそう、画面表示しているかどうかを判定するのに、最初は

div.style.display === "block"

としていたのだが、これだとうまくいかない。 ファイル読み込み後、一度でも検索すればちゃんとファイル出力されるのだが、検索しないままだとファイル出力を実行しても何も出力されないのだ。

その駄目な様を見て思い出したのだが、どういう訳かcssで設定しているはずの初期設定値がjavascriptで取得できないものがある。 このdisplayもその一つ。 javascriptから明示的に設定すれば、その後は設定した値をちゃんと読み取れるのだが。 一度検索すればいけるのはそのせい。

これがHTA(の実体であるIE)固有の問題なのか、正しい仕様解釈の結果で他のブラウザでも同様なのかは調べてないが、IEの場合は

div.currentStyle.display === "block"

とすることでこの問題を回避できる。 当然HTAでも。

これまでに何度もこの問題で同じ失敗を繰り返しているのに、なぜかすぐに忘れて、また同じところで躓くんだよな。 文字数が多くて嫌だとか、心が拒否しているのだろうか。

さて、ここまでで一応だが一通り基本機能がそろったことになる。 残っているのは、色と、操作可否の制御と、同じような処理記述の整理。

続きは明日。