ブログをはじめてみる ― 2007年04月24日 03時13分06秒
だらだらとJavaScriptのこととか書いてみよう。
といっても、Ajax系のこととかはよいブログが
すでにたくさんあるので、スキマを狙って
WSHとかHTAとかにからめていく予定。
WSHで外部スクリプトのロード ― 2007年04月24日 03時16分33秒
方針
すなおに.wsfにすればあまり苦労はないんだけど、XML中にスクリプトを書くのはなんとなく収まりが悪いので、.jsファイルのみの方向で。
基本的な考え方
とはいってもJScript自体にも、WScriptにもファイルをインクルードする機構はないから、eval()するしかない。
しかし、ライブラリロードでグローバル変数を消費するのはなんだかヤなので、withブロックで使い捨てスコープを形成することにする。
問題点
eval()はeval自身を呼び出した実行コンテキストをそのまま引き継ぐので(あってるのか?)ライブラリソースをevalする関数を作成すると、いつまでたってもグローバル空間にスクリプトはロードされない。
仕方ないので、ソースのロードのみ関数にして、あとはグローバルコンテキスト中でeval()することに。
で、実装
骨組みは以下の通り。
with( {
// ライブラリのパスリスト
libs : [
/* ここにライブラリのパスを列挙する */
],
// libs[]のインデックス
index : 0,
// ソース取得メソッド
getSource : function(path) {
var stream, fso = new ActiveXObject("Scripting.FileSystemObject");
try {
stream = fso.OpenTextFile( path, 1 );
return stream.ReadAll();
} finally {
stream.Close();
}
}
} ) {
while( index < libs.length ) {
try {
eval( getSource( libs[ index++ ] ) );
} catch(e) {
WScript.Echo( e.description || e.message || "error" );
}
}
}
たとえば、
function test(a, b) {
return a + b;
}
なんてソースを「lib.js」とした場合、
with( {
// ライブラリのパスリスト
libs : [
"lib.js"
],
// libs[]のインデックス
index : 0,
// ソース取得メソッド
getSource : function(path) {
var stream, fso = new ActiveXObject("Scripting.FileSystemObject");
try {
stream = fso.OpenTextFile( path, 1 );
return stream.ReadAll();
} finally {
stream.Close();
}
}
} ) {
while( index < libs.length ) {
try {
eval( getSource( libs[ index++ ] ) );
} catch(e) {
WScript.Echo( e.description || e.message || "error" );
}
}
}
WScript.Echo( test( "A", "B" ) ); // 'AB'
となる。
このままだとShift_JISでローカルディスクにあるライブラリしかロードできないが、あとでXHRとADODB.Streamを組み合わせてgetSourceメソッドを書き換えればWebサーバにあるライブラリのロードもできるようになる。
prototype.jsを使えるように ― 2007年04月24日 03時46分36秒
きっかけ
prototype.jsが大好きだ。組み込みオブジェクトのprototypeを汚染拡張しているところが大好きだ。
なので、WSHでも使ってみたい。
困ったことに
prototype.jsを(Windowsで、ね。)ダブルクリックしてみると、さっそく「documentは宣言されていません」とか怒られるんですよ。
これ、当然といえば当然で、prototype.jsはブラウザホストで稼動させることが前提なので、windowやらdocumentやらlocationやらを参照してるからなのだけど、この辺のオブジェクトをダミー宣言してやれば、ClassだのEnumerableだのTryだのは使用できるんですよ。
ダミースクリプト
前のエントリの感じで以下のスクリプトを、prototype.jsよりも前にロードしておくことにする。仮に「dummy.js」とでもしておこう。
// prototype.jsのためのダミー宣言
var window = { Element : null }
var document = {
getElementById : function() {},
getElementsByName : function() {},
getElementsByTagName : function() {},
createElement : function() {},
documentElement : null,
body : null
}
var navigator = { userAgent : null }
んで、prototype.jsもロード
dummy.jsよりも後なら、prototype.jsのロードができるんですよ。以下のように。
with( {
// ライブラリのパスリスト
libs : [
"dummy.js",
"prototype.js"
],
// libs[]のインデックス
index : 0,
// ソース取得メソッド
getSource : function(path) {
var stream, fso = new ActiveXObject("Scripting.FileSystemObject");
try {
stream = fso.OpenTextFile( path, 1 );
return stream.ReadAll();
} finally {
stream.Close();
}
}
} ) {
while( index < libs.length ) {
try {
eval( getSource( libs[ index++ ] ) );
} catch(e) {
WScript.Echo( e.description || e.message || "error" );
}
}
}
WScript.Echo(
$R(1,3).map( function(i) {
return "番号:" + i;
} ).join(", ")
); // 番号:1, 番号:2, 番号:3
コンソールベースのWSHだとテキスト処理かファイル処理が主になると思うけど、こういうときにEnumerableインターフェイスが使えるのって、個人的にはかなり便利で手放せない感じ。
特に、FileSystemObjectのFilesとかSubFoldersとかを取り回すEnumeratorにEnumerableなラッパーかぶせたりすると、100単位のファイルがあるディレクトリを取りまわしたりするときもメモリを消費しないですむ(処理は遅いけど)ので、お勧めです。需要はなさそうだけど。
http経由でライブラリ読み込み ― 2007年04月24日 21時04分28秒
XHRとADODB.Streamを組み合わせる
http経由でソースを取得するのはXHRで問題ないが、日本語のコメントが入ったeucエンコードのソースなんかだとちょっとアレなので、ADODB.Streamと組み合わせてみたり。まあ、定番テクニックですか。
基本的には以下の流れになる。
- XHR(Microsoft.XMLHTTP)でソースを取得
- バイナリモードで開いたADODB.StreamにXHR.responseBodyを書き込む
- ADODB.Streamを巻き戻し、テキストモードに変更する
- Charsetプロパティに"_autodetect"を指定して自動検出にする
- ReadText()でテキスト読み出しをする
getSource : function(url) {
var stream = new ActiveXObject("ADODB.Stream");
var xhr = new ActiveXObject("Microsoft.XMLHTTP");
try {
xhr.open( "GET", url, false );
xhr.send();
stream.Open();
stream.Type = 1; // バイナリモード
stream.Write( xhr.responseBody ); // バイナリ書き込み
stream.Position = 0; // 読み取り位置を巻き戻す
stream.Type = 2; // テキストモードに変更
stream.Charset = "_autodetect"; // 文字コード自動検出
return stream.ReadText();
} finally {
stream.Close();
}
}
ちょっと注意点があって、
- 先にTypeを変更してからPositionを変更する
- Typeを変更する前にCharsetを割り当てる
コードサンプル
ローカルでwebサーバが動いていて、ルート直下にdummy.jsとprototype.jsがある場合
with( {
// ライブラリのパスリスト
libs : [
"http://localhost/dummy.js",
"http://localhost/prototype.js"
],
// libs[]のインデックス
index : 0,
// ソース取得メソッド
getSource : function(url) {
var stream = new ActiveXObject("ADODB.Stream");
var xhr = new ActiveXObject("Microsoft.XMLHTTP");
try {
xhr.open( "GET", url, false );
xhr.send();
stream.Open();
stream.Type = 1; // バイナリモード
stream.Write( xhr.responseBody ); // バイナリ書き込み
// ↓ 最初のPOST時に順序が間違ってたので修正(07.04.25 02:07)
stream.Position = 0; // 読み取り位置を巻き戻す
stream.Type = 2; // テキストモードに変更
stream.Charset = "_autodetect"; // 文字コード自動検出
return stream.ReadText();
} finally {
stream.Close();
}
}
} ) {
while( index < libs.length ) {
try {
eval( getSource( libs[ index++ ] ) );
} catch(e) {
WScript.Echo( e.description || e.message || "error" );
}
}
}
WScript.Echo( $R(1, 10).inject( 0, function(total, value) {
return ( total + value );
} ) ); // → 55
ちなみにlibsの各要素で「file://ファイルへの絶対パス」を使用するとローカルファイル読み出しができる。getSource()内でパラメータを検査して判断すればURLでもファイルパスでも対応できるようになる。
蛇足
せっかくADODB.Streamでエンコードの判別を行ってるけど、_autodetect時はutf-8をうまく判別してくれないのよね...

最近のコメント