2010/12/21

テキストファイルの文字コードを判別して読み込む

不特定多数のテキストファイルから検索を行ったりするスクリプトを作る場合、文字コードを正しく認識して読み込む必要があります。
NILScriptのFileオブジェクトに用意されているload()メソッドでは、文字コードを指定するための第1引数を省略した場合、エラーにならずに読み込める文字コードを自動選択して読み込んでくれますが、誤った文字コードでも全てのバイトをデコードできる場合などは、誤認識してしまうことがあります。
より正確な文字コード認識を行いたい場合、「Charset」というユニットを使います。
以下のように記述すれば、Charsetユニットによる高精度な文字コード判別を行ってテキストファイルを読み込むことが出来ます。
var text=require('Charset').Detector.load(someFile);

また、以下のようにすることで、検出した文字コード名を得ることも可能です。
var charset=require('Charset').Detector.detect(someFile);

他にも、ストリームオブジェクトから読み込みを行いながら文字コードを判別する機能なども用意されています。

Charsetユニットでは、IEなどで使用されているmlang.dllの機能を利用して文字コードの判別を行っています。「です」「である」などの頻出文字列の出現なども手がかりにしているらしく、通常のload()で使用している方法より正確に判別してくれます。

なお、読み込まれた文字列はUnicode文字列として保持されるので、海外製の処理系にありがちな駄目文字問題に悩まされることはありません。

2010/12/13

入出力のコンソールを子プロセスと共有して進捗などを表示

NILScriptは、コマンドラインで指定された処理を行って直ちに終了するコンソールプログラムを組み合わせた自動処理の実現にも役立ちます。

動画変換やダウンロードなど時間のかかる処理を行うプログラムの多くは、標準出力で進捗状況を確認できるようになっていますが、作業中にバックグラウンドで自動処理させたい場合には、子プロセスが起動するたびにコンソールウィンドウが表示されては邪魔になってしまいます。
しかし、完全に出力を非表示にしてしまうと、残り所要時間が把握しづらく不便です。

スクリプトのプロセスが所有するコンソールに子プロセスの出力を表示するようにすれば、いちいち新たなウィンドウを表示せずに進捗状況を確認できるようになって便利です。
NILScriptのrun()やProcess.create()では、以下のようにオプションオブジェクトに「shareConsole:true」を指定する事で、子プロセスと現在のプロセスのコンソールを共有させられるので、簡単に子プロセスの出力を自分のコンソールに表示させられます。

println("1/3")
run('"'+app1+'" "'+file+'"',{shareConsole:true}).waitExit();
println("2/3")
run('"'+app2+'" "'+file+'"',{shareConsole:true}).waitExit();
println("3/3")
run('"'+app3+'" "'+file+'"',{shareConsole:true}).waitExit();
println("done");

2010/12/09

Webページのスクリーンショット画像を生成

Imageユニットが提供する画像処理機能には、ウィンドウの表示内容を画像として取得するcapture()メソッドなども用意されています。
これをATLユニットで用意されているIEコンポーネントの表示機能と組み合わせれば、以下のようにしてWebページのスクリーンショットを生成することも可能です。
use('Window','ATL');
var w=OnScreenDisplay.create({
    width:800,height:600, x:-32000,alpha:0,
    children:{
        ie:{
            type:Trident,
            top:0,bottom:0,left:0,right:0,
        }
    }
}).show(true);
w.request(function(){
    this.ie.object.observe('DWebBrowserEvents2_DocumentComplete',function(o){
        w.capture().save('webpage.png');
        w.close();
    });
    this.ie.object.navigate("http://lukewarm.s151.xrea.com/nilscript.html");
});
w.wait('destroy');
配下にIEコンポーネントを配置したOnScreenDisplayを生成し表示させた後、ウィンドウのスクリーンショットを保存しウィンドウを閉じるというイベントハンドラを、IEコンポーネントのCOMオブジェクトの読み込み完了イベントに割り当て、キャプチャしたいページのURLにnavigate()しています。

OnScreenDisplayは、Windowクラスを継承して定義されているクラスで、タイトルバーや枠などを持たず、タスクバーにも表示されず、クリックが背後のウィンドウに透過する情報表示向けのウィンドウです。
これをx:-32000という絶対に画面に表示されない位置に表示させることで、一切画面に表示されることなくキャプチャを実行させられます。
通常のウィンドウは、描画先としてデスクトップのデバイスコンテキストを使用しているため、実際に表示されていない部分は真っ黒になってしまいますが、alphaオプションで半透明にしたウィンドウは独自のデバイスコンテキストを持つため、画面外にあってもキャプチャ可能になります。
半透明にしておけば画面外に出す必要は無い気がしますが、表示される瞬間に一瞬だけちらつきが見えてしまうので、x:-32000は有った方がいいでしょう。

任意のWebページをIEコンポーネントに読み込ませて処理する

NILScriptのCOMユニットやATLユニットを使えば、IEコンポーネントでWebページを表示し、様々な操作を行うことも可能です。
しかし、NILScript上のCOMオブジェクトは明示的にfree()メソッドを呼び出して解放しなければならないため、多数のDOMノードオブジェクトを使用する処理などは記述が面倒です。

そこでおすすめなのが、HTTPD機能で生成した動的WebページをIEコンポーネント上に表示し、Webページ側のJavaScriptで処理を行わせる方法です。
HTTPDには、Webページ上のJavaScriptとNILScript側の関数を連携させるためのclientSide関数とserverSide関数という機能が用意されています。
clientSide関数はブラウザ上で実行されるため、DOMオブジェクトの明示的開放が要らないだけでなく、jQueryオブジェクトも利用できるので、複雑なDOM操作も手軽に記述可能です。

任意のURLをIE上に読み込んでDOMを利用して情報の取得などを行いたい場合には、HTTPD上に用意したWebページのように自由にスクリプトを埋め込んで実行させることは出来ませんが、ブックマークレット機能を利用することで同様のことが行なえます。
ブックマークレットとは、「javascript:」に続いてスクリプトを記述したURIをブックマークなどから開くことで、Webページ上で任意の処理を実行させられるという手法のことです。
NILScriptのHTTPDでは、NILScript上で定義したclientSide/serverSide関数を利用した処理をブックマークレットとして呼び出せるようにする機能が用意されています。
このブックマークレットのURLをIEコンポーネントのnavigate()メソッドで読み込ませれば、表示しているWebページ上でNILScript上の機能を利用した任意の処理を実行させられます。

以下は、この手法の利用してWebページのbody要素の高さを調べて表示するという例です。
use('Window','ATL','HTTPD');
var result;
var httpd=HTTPD.create({
    serverSide:{
        report:function(value){
            //受取った解析結果を変数に代入し、ウィンドウを閉じる(4)
            result=value;
            w.close();
        },
    },
    bookmarklets:{
        analyze:function(){
            //ブラウザ上でWebページを解析し、結果をserverSide関数に渡す(3)
            report([$(document.body).height()]);
        },
    },
});
var w=PlainWindow.create({
    width:800,height:600,
    children:{
        ie:{
            type:Trident,
            top:0,bottom:0,left:0,right:0,
        }
    }
});
w.request(function(){
    this.ie.object.observe('DWebBrowserEvents2_DocumentComplete',function(o){
        //読み込みが完了したらブックマークレットを読み込ませる(2)
        this.navigate(httpd.bookmarkletURL('analyze'));
    });
    //解析したいWebページを読み込ませる(1)
    this.ie.object.navigate("http://lukewarm.s151.xrea.com/nilscript.html");
});
//ウィンドウが閉じられたら、解析結果を利用した処理を行う(5)
w.wait('destroy');
println(result);
HTTPD上では、analyzeというブックマークレット関数とreport()というserverSide関数を定義しています。
ブックマークレット上では、「$」でjQueryオブジェクトが利用できる他、serverSideで定義した関数の呼び出しが可能です。
serverSide関数は、第1引数に関数に渡す引数の配列、第2引数に返り値を受取るコールバック関数(省略可)を指定するという特殊な呼び出し方になっていることに注意してください。
また、bookmarkletsの関数は一旦文字列化されてブラウザ上で実行されるため、NILScript側の変数を直接参照することは出来ず、ブラウザのスクリプトエンジンがサポートしていない構文も利用できないことにも注意が必要です。

次に、Tridentコントロールを一つだけ配置したPlainWindowオブジェクトを生成します。
そして、このIEコンポーネントのCOMオブジェクトのobserve()で、Webページの読み込み完了時にブックマークレットの実行を行うというイベントハンドラを登録し、さらに調査したいURLを読み込ませます。
この処理は、ウィンドウの初期化が完了される前に実行されてしまわないように、ウィンドウを所有しているスレッド上で処理を実行させるrequest()メソッドを利用して呼び出します。
Webページの読み込みが行われると、スクリプト中のコメントの後ろに付けられた番号の順番で処理が実行されていき、最終的に調査結果の表示が行われます。

2010/12/08

NILScriptのプロセス自身のメモリ使用量を取得する

大量のデータを処理するスクリプトを作っていてメモリ使用量が心配になったときや、メモリの解放漏れが無いか確かめたいときなどには、スクリプトのプロセスのメモリ使用状況を調べたくなります。
そんなときいちいちタスクマネージャでスクリプトのプロセスを探してメモリ使用量を調べるのは面倒です。

NILScriptのProcessオブジェクトには、プロセスのメモリ使用状況を取得する機能があり、「Main.process」で現在スクリプトを実行しているプロセスをProcessオブジェクトとして得ることができるので、自プロセスのメモリ使用状況を簡単に取得できます。

例えば、以下のようにすれば、Imageオブジェクトを生成して解放したときにメモリの解放漏れがあるかどうかが分かります。
use('Image');
for(var i=0;i<100;i++){
    try{
        var img=Image.create(1920,1080);
    }finally{
        img.free();
    }
}
println(Main.process.pagefileUsage);

なお、Imageオブジェクトのfree()が正しく全てのリソースを解放していなかった件については、先ほど修正しました。