仕事中、忙しいときは気分転換に、暇なときは暇潰しに、だいたい1時間に1回ぐらいは飲み物を買いに席を立ったりする。 で、自販機の前でコーヒーが出来上がるのをぼんやり待っていて、ふと 「はまだしょうこ」 が濁ると 「はまだしょうご」 になることに気がついた。 濁点一つでこの違い。 ちょっと凄い。 字面とは逆に内面は浜田翔子の方が濁ってそうだけど。
そして自席への帰り道。 カップのコーヒーを持って歩きながら 「全部濁ったら山瀬まみ」 なんて浮かんできて、自分のおっさん振りに愕然とするのだった。 そういえば、NHKの試してガッテン以外で見たことがないな、山瀬まみって。
昨日の続き。
XMLのDOMからHTMLに変換するときは、DOMの全要素に順番に、そして各要素に1回だけアクセスする。 過去の経験からWSHで手軽に使えることが判っているDOMを選択したのだが、今回のような場合はSAXの方が向いているんだよな。 俺の脳内アントワネットも 「DOMが駄目ならSAXを使えばいいじゃない」 なんて言ってるし、じゃあSAXでと思ったのだが。
結論から言うと、WSHではSAXは使えない。
特定のクラスを継承したクラスを作る必要があり、これがJavaScriptではうまくいかない。 VBScriptでもだめ。 MS OfficeのVBAならできるようだが、そんなものを使う気は無いし。
でもここで終わるのはなんだか負けた気分なので、自分でSAX相当の処理を実装してみた。
function doLoad() {
try {
document.getElementById( "base" ).innerHTML = xmlToHtml( loadXml( getFilename() ) ).join( "" );
} catch ( e ) {
alert( e );
}
function getFilename() {
var filename = document.getElementById( "txtFile" ).value;
if ( !filename ) {
throw "ERROR : filename";
}
return filename;
}
function loadXml( filename ) {
var iStrm = new ActiveXObject( "ADODB.Stream" );
var enc = loadFromFile( "Shift_JIS", -2 ).match( /\s+encoding="([^"]+)"/ );
return loadFromFile( enc ? enc[ 1 ] : "Shift_JIS", -1 );
function loadFromFile( encoding, mode ) {
iStrm.Charset = encoding;
iStrm.Open();
iStrm.LoadFromFile( filename );
var buf = iStrm.ReadText( mode );
iStrm.Close();
return buf;
}
}
function xmlToHtml(xml) {
var rInstruction = new RegExp( /^<\?(.+?)\?>/ ); // 1:宣言内部
var rDoctype = new RegExp( /^<!DOCTYPE\s+([^\s].+?)>/i ); // 1:宣言内部
var rComment = new RegExp( /^<!--([\s\S]*?)-->/ ); // 1:コメント
var rCData = new RegExp( /^<!\[CDATA\[([\s\S]+?)\]\]>/i ); // 1:CDATA内部
var rText = new RegExp( /^(([^<]|[*s])+)/ ); // 1:テキスト全体
var rTag = new RegExp( /^<([^\?!][^>]*(\s*("[^"]*")*\s*)*?)>/ ); // 1:<>内部
var rNotBlank = new RegExp( /[^\s]/m );
var rAttributes = new RegExp( /\s+([^\s="]+)\s*=\s*"([^"]+)"/g ); // 1:属性名, 2:属性値
var htmlAry = [];
var buf = xml;
while ( buf !== "" ) {
var len = buf.length;
buf = buf.replace( rInstruction, instructionHandler )
.replace( rDoctype, doctypeHandler )
.replace( rComment, ignore )
.replace( rCData, cDataHandler )
.replace( rTag, tagHandler )
.replace( rText, textHandler );
if( len === buf.length ) {
throw "ERROR : unsupported element";
}
}
return htmlAry;
function ignore( s, s1 ) {
return "";
}
function instructionHandler( s, s1 ) {
htmlAry.push( "<div><?" + s1 + "?></div>" );
return "";
}
function doctypeHandler( s, s1 ) {
htmlAry.push( "<div><!DOCTYPE " + s1 + "></div>" );
return "";
}
function cDataHandler( s, s1 ) {
htmlAry.push( "<div><![CDATA[<div>" + s1.replace( /</g, "<" ).replace( />/g, ">" ) + "</div>]]></div>" );
return "";
}
function textHandler(s, s1) {
if (rNotBlank.test(s1)) {
htmlAry.push(s1.replace( /</g, "<" ).replace( />/g, ">" ));
}
return "";
}
function tagHandler( s, s1 ) {
if ( s1.charAt(0) === "/" ) {
endTagHandler( s1 );
} else if( s1.charAt( s1.length - 1 ) === "/" ) {
emptyTagHandler( s1 );
} else {
startTagHandler( s1 );
}
return "";
}
function startTagHandler( s ) {
htmlAry.push( "<div><" + setClassToAttribute( s ) + ">" );
}
function endTagHandler( s ) {
htmlAry.push( "<" + s + "></div>" );
}
function emptyTagHandler( s ) {
htmlAry.push( "<div><" + setClassToAttribute( s ) + "></div>" );
}
function setClassToAttribute( s ) {
if ( rAttributes.test( s ) ) {
return s.replace( rAttributes, ' $1="$2"' );
}
return s;
}
}
}
自作のなんちゃってSAXなので、いろいろ面倒が増えている。
まずは入力側の loadXml()
MSXMLならencodingを気にせずにメソッド一つで読み込み完了だったのだが、自分でやるならencodingを意識しなきゃいけないんだよな。
ファイル処理でよく使う Scripting.FileSystemObject は、扱えるencodingが限定されていて駄目。 最近主流のUTF-8が読めないのが致命的。 というわけで ADODB.Stream を使う。
このADODB.Streamでいろんなencodingに対応できるのはいいのだが、ファイルを開くときにencodingを指定しないといけない。 でもこれから処理しようとするファイルのencodingが何なのかは、ファイルを開いてみないと分からないというジレンマ。
幸い、XMLの場合は先頭行に
<?xml version="1.0" encoding="UTF-8"?>
などと指定されているはずなので、とりあえずシステムのデフォルトのShift_JISとして1行だけ読んでencodingを取得し、これを指定してファイル全体を読み直している。
読み直しで一気にファイル全体を読んでいるのは、設定用のXMLファイルだからメモリを圧迫するほど大きくは無いと思ったから。
って、そもそもの作る動機が、大きなXMLの設定ファイルをなんとかしたいと思ったからだったんだよな。 自分で設定値を確認するときは 「どこの馬鹿が設定ファイルをここまで肥大化させたんだよ」 なんて文句ばかりのくせに、それをプログラムにデータとして渡すときは 「このぐらい問題無い」 と思うとは。 まあ、それが人間なんだけど。
と、個人の問題を人間全体に広げて、出力側の xmlToHtml()
String.replace() は、第2引数にfunctionが指定できる。 実際に指定すると、第1引数で指定した正規表現にマッチした文字列がfunctionの引数として渡される。 サブマッチがある場合は第2引数以降に設定される。 そしてfunctionの戻り値で置換が実行される。
これを利用して、
を、XML文字列が0になるまで繰り返す。
こうすると、XMLの先頭から要素検出毎にイベントが発生してイベントハンドラーが呼び出されるかのような、ちょっとSAXっぽい動きになるのだ。
そして動作確認。
小さいファイルは問題無い。
が、大きいファイルが駄目。 処理開始してすぐに
Error: メモリが不足しています。
と言われてしまう。 DOMを使っていた版で仮想記憶がどうとか言ってきたときは、処理開始してからある程度時間が経ってエラーになったのだが、今回のは速攻終了。
処理をちょっとずつ進めながらタスクマネージャーで観察すると、xmlToHtml内のwhileループでメモリの使用量が跳ね上がっていた。
試しにhtmlAryに追加している文字列をサブマッチじゃなくて "------" みたいな固定文字列にすると、メモリ使用量上昇のカーブは少し緩くなるけど、すぐにメモリ不足で終了してしまうのは同じ。
String.replace() は実行結果の文字列を新たに作って返す。 この引数をfunctionにしたりメソッドチェーンで繋げたりがスタックを食い潰すのかもと考えて、メソッドチェーンをやめてみた。 駄目。
イベントハンドラーをクロージャーにせず、独立したオブジェクトのメソッドとし、このオブジェクト内にhtmlAryを隠蔽してみた。 駄目。
サブマッチじゃなくてマッチした文字列全体、つまり各ハンドラーに渡す第1引数のみにして、イベントハンドラーの中でサブマッチ相当を取り出すようにしてみた。 駄目。
そういったロジックの問題ではなくて、ちょっとしたコーディングミスで巧くいってないという気もするのだが、よく判らん。 うーん…