続・javascriptの循環参照オブジェクト
以前書いた記事の続き
実際に使って行くと、色々至らぬ点が見えてきたので
かなり本格的にリファクタリングする事にした。
Object crawler
使い方
string の値を全て拾って来る方法
var callback_object = function(){ } callback_object.prototype = { ret : [], cb_string : function (path,value,cyclic,in_array,objid){ this.ret.push(value); } } var data = get_object(); // 何かのオブジェクト var callback = new callback_object; crawl_object(data,callback); callback.ret; // data内に含まれる文字列の配列
I/F
function crawl_object( data , cbobj ) { function cb_nop(path,value,cyclic,in_array,objid){ return ! cyclic; } if ( cbobj.cb_undefined === undefined ) cbobj.cb_undefined = cb_nop; if ( cbobj.cb_null === undefined ) cbobj.cb_null = cb_nop; if ( cbobj.cb_string === undefined ) cbobj.cb_string = cb_nop; if ( cbobj.cb_function === undefined ) cbobj.cb_function = cb_nop; if ( cbobj.cb_other === undefined ) cbobj.cb_other = cb_nop; if ( cbobj.cb_date === undefined ) cbobj.cb_date = cb_nop; if ( cbobj.cb_regexp === undefined ) cbobj.cb_regexp = cb_nop; if ( cbobj.cb_array === undefined ) cbobj.cb_array = cb_nop; if ( cbobj.cb_hash === undefined ) cbobj.cb_hash = cb_nop; if ( cbobj.cb_object === undefined ) cbobj.cb_object = cb_nop; if ( cbobj.cb_leave_array === undefined ) cbobj.cb_leave_array = cb_nop; if ( cbobj.cb_leave_hash === undefined ) cbobj.cb_leave_hash = cb_nop; if ( cbobj.cb_leave_object=== undefined ) cbobj.cb_leave_object= cb_nop; crawl_object_impl(data,cbobj,[],[],[],undefined); }
ロジック
function crawl_object_impl( data , cbobj , path , done , parent ) { var in_array = false; if ( parent && parent.constructor === Array ){ in_array = true; } if ( data === undefined ) { cbobj.cb_undefined(path,undefined,false,in_array,undefined) } else if (data === null ) { cbobj.cb_null(path,null,false,in_array,undefined) } else if ( typeof(data) === 'object') { var ref = false; var cyclic = false; // Check reference objects var objid = undefined; for ( var no in done ) { if ( done[no] === data ) { ref = true; objid = no; break; } } if ( ! ref ) { done.push(data); objid = done.length; } // Check cyclic objects for ( var no in path ) { if ( objid === path[no][1] ){ cyclic = true; } } if ( data.constructor === RegExp ) { cbobj.cb_regexp(path,data,cyclic,in_array,objid); }else if ( data.constructor === Date ) { cbobj.cb_date(path,data,cyclic,in_array,objid); }else if ( data.constructor === Array ) { if ( cbobj.cb_array(path,data,cyclic,in_array,objid) ) { for ( var i in data ){ path.push([i,objid]); crawl_object_impl ( data[i],cbobj,path , done , data ); path.pop(); } cbobj.cb_leave_array(path,data,cyclic,in_array,objid); } }else if ( data.constructor === Object ) { if ( cbobj.cb_hash(path,data,cyclic,in_array,objid) ) { for ( var i in data ){ path.push([i,objid]); crawl_object_impl( data[i],cbobj,path , done , data ); path.pop(); } cbobj.cb_leave_hash(path,data,cyclic,in_array,objid); } }else{ if ( cbobj.cb_object(path,data,cyclic,in_array,objid) ) { for ( var i in data ){ path.push([i,objid]); crawl_object_impl ( data[i],cbobj,path , done , data ); path.pop(); } cbobj.cb_leave_object(path,data,cyclic,in_array,objid); } } }else{ if ( typeof(data) === 'string' ) { cbobj.cb_string(path,data,false,in_array,undefined) }else if( typeof(data) === 'function' ) { cbobj.cb_function(path,data,false,in_array,undefined) }else{ cbobj.cb_other(path,data,false,in_array,undefined) } } }
解説
crawl_object(data,callback);
この関数に、解析したいデータ(data)と、解析オブジェクト(callback)を渡す。
解析オブジェクトには値の型に応じてコールバックが設定できる。
全てのコールバック関数の型は同じ。
設定されていない場合はデフォルトコールバックが採用される。
function cb_nop(path,value,cyclic,in_array,objid){ return ! cyclic; }
- path
- [ [key名,objedtID] ] : 再起的に追って来たキーとオブジェクトのパス。つまりpath[path.length-1] がvalueのキーということ
- value
- 値
- cyclic
- valueがobjectの場合、循環参照になっている場合はtrue
- in_array
- valueの親オブジェクトがArrayの場合はtrue
- objid
- valueのobjectをユニークに識別できるID
- 戻り値
- valueがobjectの場合、更に中を追う場合はtrueを戻す。falseなら追わない。
例:
{ a : [ { b : "1" //※ }] } ※印の部分を追った時のコールバックの引数 function cb_nop(path,value,cyclic,in_array,objid){ path == [ ['a',1],[0,2],['b',3] ] value == "1" cyclic == false in_array == false objid == 3 }
コールバックの種類
- cb_undefined
- undefined 型
- cb_null
- null 型
- cb_string
- string 型
- cb_function
- 関数型
- cb_other
- 他の非object型(boolen , int など)
- cb_date
- Date型(object)
- cb_regexp
- RexExp型(object)
- cb_array
- Array型(object)
- cb_hash
- Object型(object)
- cb_object
- 他のobject型(未対応とも言う・・・)
- cb_leave_array
- Array型の最後
- cb_leave_hash
- Object型の最後
- cb_leave_object
- 他のobject型の最後
応用例
関数型 , Regex型 , Date型 などに対応したシリアライザが書ける
var callback_object = function(){ } callback_object.prototype = { indent : '', suffix : '\n', buffer : '', prefix : function (path,value,cyclic,in_array,objid){ if ( cyclic ) { throw 'Cannot serialize (cyclic object) ! '; } var key = path[path.length-1]; var prefix = this.indent; if ( key && ! in_array ){ prefix += '\'' + key[0] + '\' : '; } return prefix; }, cb_undefined : function (path,value,cyclic,in_array,objid){ var prefix = this.prefix(path,value,cyclic,in_array,objid); this.buffer += prefix + 'undefined,' + this.suffix; }, cb_null : function (path,value,cyclic,in_array,objid){ var prefix = this.prefix(path,value,cyclic,in_array,objid); this.buffer += prefix + 'null,' + this.suffix; }, cb_string : function (path,value,cyclic,in_array,objid){ var prefix = this.prefix(path,value,cyclic,in_array,objid); this.buffer += prefix + '"' + value + '",' + this.suffix; }, cb_function : function (path,value,cyclic,in_array,objid){ var prefix = this.prefix(path,value,cyclic,in_array,objid); var lines = value.toString().split('\n'); for ( var i in lines ) { if ( i == 0 ){ this.buffer += prefix + lines[i]; }else{ this.buffer += this.indent + lines[i]; } this.buffer += '\n'; } this.buffer = this.buffer.replace(/\n$/,''); this.buffer += ',' + this.suffix; }, cb_other : function (path,value,cyclic,in_array,objid){ var prefix = this.prefix(path,value,cyclic,in_array,objid); this.buffer += prefix + value + ',' + this.suffix; }, cb_date : function (path,value,cyclic,in_array,objid){ var prefix = this.prefix(path,value,cyclic,in_array,objid); this.buffer += prefix + 'new Date(\''+value.toString()+'\'),' + this.suffix; }, cb_regexp : function (path,value,cyclic,in_array,objid){ var prefix = this.prefix(path,value,cyclic,in_array,objid); var flg = ((value.ignoreCase)?'i':'')+((value.multiline)?'m':'')+((value.global)?'g':''); this.buffer += prefix + 'new RegExp(\''+value.source+'\',\''+flg+'\'),' + this.suffix; }, cb_array : function (path,value,cyclic,in_array,objid){ var prefix = this.prefix(path,value,cyclic,in_array,objid); if ( value.length ) { this.buffer += prefix + '[' + this.suffix; return true; }else{ this.buffer += prefix + '[],' + this.suffix; return false; } }, cb_hash : function (path,value,cyclic,in_array,objid){ var prefix = this.prefix(path,value,cyclic,in_array,objid); this.buffer += prefix + '{' + this.suffix; this.indent += ' '; return true; }, cb_object : function (path,value,cyclic,in_array,objid){ throw "Unknown type : " + typeof(value); }, cb_leave_array : function (path,value,cyclic,in_array,objid){ this.buffer = this.buffer.replace(/,\n$/,'\n'); this.buffer += this.indent + '],' + this.suffix; }, cb_leave_hash : function (path,value,cyclic,in_array,objid){ this.indent = this.indent.replace(/ $/,''); this.buffer = this.buffer.replace(/,\n$/,'\n'); this.buffer += this.indent + '},' + this.suffix; }, } function serialize(data){ var callback = new callback_object; common.crawl_object(data,callback); callback.buffer = callback.buffer.replace(/,\n$/,'\n'); return callback.buffer; }
使い方
var data = { foo : function(){ return 'foo'; }, bar : 'bar', baz : [ { }], }; var str = serialize(data);
利用例
nodejs + jQuery で汎用クローラを書いたのだけど、nodeのメモリーリーク対策でresume機能が必須だった。
なので現在持っている『キュー配列』をそのままファイルにダンプする事にした。
キューの中身は、取得するURLと、取得したコンテンツの解析方法(jQueryのセレクタ形式)が記述されているのだけど
もっと細やかな制御をしたい時もあるので『正規表現フィルタ』や『ユーザ定義関数』を書ける様にしたかった
ところが
単純にJSON.stringfyで保存すると関数や正規表現が抜け落ちてしまう。
正規表現はまだ何とでもなるけど『ユーザ定義関数』はどうしても欲しかったので、こんな事をした訳です。
将来像
現在は未対応なのだけど、今後、クローラ設定を再帰的な構造にしたいので『循環参照』もスコープに入れている。
こんな感じのオブジェクトがシリアライズ出来ると凄くいい感じ!
var setting = { SELECTOR : 'div.list > a', ACTION : [ { SELECTOR : 'div.main > a', ACTION : [] },{ SELECTOR : 'div.sub > a', ACTION : [] }] }; setting['ACTION'][0]['ACTION'] = setting;
出来ている所まで・・・
https://github.com/cockatoo-org/Cockatoo/tree/master/src/operation/html
# -V を付けると取得したファイルを保存する。 # 単純起動 node htmlmon.js -u http://cockatoo.jp -l 20 # 標準モード(a,link,script,img) node htmlmon.js -u http://cockatoo.jp -S -l 15 # 標準クロール(同一ドメイン) node htmlmon.js -u http://cockatoo.jp -C -l 8 # 設定ファイル利用1 node htmlmon.js -c ./wiki_config.js -l 10 # 設定ファイル利用2 node htmlmon.js -c ./wiki_crawl_config.js -l 10