WSHで表計算ソフトの統一API: シート上のデータの取扱いをさらに便利にしてみた


シートをスキャンした結果を,配列データとしてさくさく扱えるようにしてみた。

スキャン結果がじかに行ごとのmapになっているのが便利すぎる。

さらに,シートとセルの紐付けなどを改良。 ====

/*

   MS ExcelとOOo CalcとKingsoft Spreadsheetsを
   共通して取り扱うためのライブラリ

    ver 0.5

   ・要件:
     Excelで組んだロジックをOOo Calcや
     Kingsoft Spreadsheetsで使いまわし,その逆も可としたい。

   ・更新ログ:
     ver0.5  シート上のデータの扱いをさらに便利にした。
             ・scan_yにmap機能を付与し,イテレータを増強。
             ・オブジェクト間の内部の親子構造を手直し。
             ・セルに座標を保持。
             ・セルに値を必ず文字列として返す機能を付与。
     ver0.4  シートをy方向にスキャンする関数を追加
     ver0.3  セルのキャッシュ機構でセル読み書きを高速化

*/

/*

  設計情報

   ・クラス設計:

     IExcel --- IBook --- ISheet --- ICell
  
  
   ・名前空間はIExcelに集約


   ・設計方針:
       Decorator・factoryパターンあたりを参考に,
       オフィス製品の差異を内部にコンポジットで隠蔽して切り替え,
       外側のAPIは統一する。
*/



// ---------- 表計算ソフトのラッパーオブジェクト ---------- 


var IExcel = function(){
	// 初期化
	this.defineExcelType();
};
IExcel.prototype = {

	// 内部で使うオフィス製品のタイプ
	isMS : false,
	isKS : false,
	isOO : false,
	type_code : null,
	
	// どれを使うか調査して決める
	defineExcelType : function(){
		try{

			// MS製のオフィスがインストールされていれば最優先する
			this._excel = WScript.CreateObject("Excel.Application");
				// http://d.hatena.ne.jp/language_and_engineering/20140214/p1

			this.isMS = true;
			this.type_code = "ms";

		}catch(e){ // MSが無かったら

			try{

				// KingSoftがあれば,Excelと同一のAPIなのでこれを使う
				this._excel = WScript.CreateObject("ET.Application");
					// http://d.hatena.ne.jp/language_and_engineering/20121218/p1
				
				this.isKS = true;
				this.type_code = "ks";

			}catch(e2){ // Kingsoftも無かったら

				try{
					// 最後の手段として,OpenOffice.org Calcを使う
					var service_manager = WScript.CreateObject("com.sun.star.ServiceManager");
					this._ooo_desktop = service_manager.createInstance("com.sun.star.frame.Desktop")
						// http://d.hatena.ne.jp/language_and_engineering/20141227/OOoCalcByWSHJScript
					
					this.isOO = true;
					this.type_code = "oo";
					
				}catch(e3){
					WScript.Echo("オフィス製品を何か一つインストールしてください。");
				}

			}

		}
		
		this._books = [];
		
		// 初期化完了
		return;
	}
	,
	
	_excel : null,
	_ooo_desktop : null,
	
	
	// Visible
	setVisible : function( b ){
		if( this.isMS || this.isKS ){
			this._excel.Visible = b;
		}else{
			// TODO:
		}
	}
	,
	
	// 新規ブックを開く
	getNewBook : function(){
		var ibook;

		if( this.isMS || this.isKS ){
			this._excel.Workbooks.Add();
			var book = this._excel.Workbooks( this._excel.Workbooks.Count );
			
			// インタフェースに変換
			ibook = new IExcel.IBook( this, book );
			
			return ibook;
		}else{
			var doc = this._ooo_desktop.loadComponentFromURL(
				"private:factory/scalc", 
				"_blank", 
				0, 
				[]
			);
			
			// インタフェースに変換
			ibook = new IExcel.IBook( this, doc );
			
			return ibook;
		}
	}
	,
	
	// 既存のブックをファイルパスで開く
	openBookByFilePath : function( file_path ){
		var ibook;
		
		if( this.isMS || this.isKS ){
			this._excel.Workbooks.Open( file_path );
			var book = this._excel.Workbooks( this._excel.Workbooks.Count );

			ibook = new IExcel.IBook( this, book );
			return ibook;
		}else{
			var file_url = "file:///" + file_path.replace(/\\/g, "/");
				log( file_url );
			
			var doc = this._ooo_desktop.loadComponentFromURL(
				file_url,
				"_blank", 
				0, 
				[]
			);
			
			ibook = new IExcel.IBook( this, doc );
			return ibook;
		}
	}

};


// ---------- ブックを表すラッパオブジェクト ---------- 


IExcel.IBook = function( parent, real_book ){
	this._parent_excel = parent;
	this._book = real_book;
	this.type_code = parent.type_code;
	this._sheets = [];
};
IExcel.IBook.prototype = {
	_parent_excel : null,
	_book : null,
	type_code : null,
	
	// ファイルパスを指定して保存
	saveAs : function( file_path ){
		if( this.type_code == "ms" || this.type_code == "ks" ){
			this._parent_excel.DisplayAlerts = false;
			this._book.SaveAs( file_path );
		}else{
			var file_url = "file:///" 
				+ file_path.replace(/\\/g, "/")
			;
			this._book.storeAsURL( file_url, [] );
		}
	},
	
	// 上書き保存
	save : function(){
		if( this.type_code == "ms" || this.type_code == "ks" ){
			this._parent_excel.DisplayAlerts = false;
			this._book.Save();
				// http://www.happy2-island.com/excelsmile/smile03/capter00303.shtml
		}else{
			this._book.store();
		}
	},
	
	// 番号でシートを取得(1始まり)
	getSheetByIndex : function( index ){
			//log("現在のシートの個数:" + this.getSheetsCount() );
			//log("index: " + index);
	
		var sheet, isheet;
		if( this.type_code == "ms" || this.type_code == "ks" ){
			sheet = this._book.Worksheets( index );
			isheet = new IExcel.ISheet( this, sheet );
			return isheet;
		}else{
			sheet = this._book.Sheets.getByIndex( index - 1 ); // 0始まり
				// http://blog.livedoor.jp/addinbox/archives/51243622.html
				// http://itref.fc2web.com/openoffice/basic/calc.html

				//sheet = this._book.Sheets( index - 1 ); // 0始まり
				//REM: これだとシートがうまく取れなかった
				
				//log( "取得したシートの名称は" + sheet.Name );
				
			isheet = new IExcel.ISheet( this, sheet );
			return isheet;
		}
	}
	,
	
	// シートの個数
	getSheetsCount : function(){
		if( this.type_code == "ms" || this.type_code == "ks" ){
			return this._book.Sheets.Count;
		}else{
			return this._book.Sheets.getCount();
		}
	}
	
	// TODO:他のブック操作メソッド

};


// ---------- シートを表すラッパオブジェクト ---------- 


IExcel.ISheet = function( parent, real_sheet ){
	this._parent_book = parent;
	this._sheet = real_sheet;
	this.type_code = parent.type_code;
	this.cell_caches = {};
};
IExcel.ISheet.prototype = {
	_parent_book : null,
	_sheet : null,
	type_code : null,
	
	// セル参照(番号は一始まり)
	getCell : function( y, x ){
			//log( y + "行" + x + "列目"  );
		
		// オブジェクト生成のコストを省くため,可能ならキャッシュから返す
		if( this.has_cell_cache( y, x ) ){
			return this.getCellByCache( y, x );
		}else{
			return this.getCellByNew( y, x );
		}
	},
	
	
	// シート内部に保持するセルのキャッシュ機構
	// これがあるとないでは,セルの読み書きの速度が4倍異なる
	
	// セルキャッシュ
	cell_caches : null,
	
	// キャッシュがあるか
	has_cell_cache : function( y, x ){
		return ( this.cell_caches[ y + "-" + x ] != null );
	},
	
	// キャッシュ登録
	storeCellCache : function( y, x, icell ){
		this.cell_caches[ y + "-" + x ] = icell;
	},
	
	// セルキャッシュを返す
	getCellByCache : function( y, x ){
		return this.cell_caches[ y + "-" + x ];
	},

	// セルオブジェクトを生成して返す
	getCellByNew : function( y, x ){
		var cell;
		if( this.type_code == "ms" || this.type_code == "ks" ){
			cell = this._sheet.Cells( y, x ); // 1始まり

		}else{
			cell = this._sheet.getCellByPosition( x - 1, y - 1 ); // 0始まりでMSと逆
		}
		
		// 変換
		var icell = new IExcel.ICell( this, cell, y, x );
		
		// キャッシュ登録
		this.storeCellCache( y, x, icell );
		
		return icell;
	},
	
	// シートを縦方向にスキャン
	scan_y : function( params, func ){
		var y_from  = params.y_from;
		var x_check = params.x_check;
		
		var continue_flag = true;
		var y = y_from;
		var results = [];
		while( continue_flag ){
			var cell = this.getCell( y, x_check );
			var s = cell.getValue();
			if( s && ( s.length > 0 ) ){
				// この行について実行
				var result = func( y, cell, s );
				
				// 実行結果を保管
				results.push( result );
				
				// 次の行へ
				y ++;
			}else{
				continue_flag = false;
			}
		}
		
		return results;
	}
	
	// TODO:他のシート操作メソッド

};


// ---------- セルを表すラッパオブジェクト ---------- 


IExcel.ICell = function( parent, real_cell, y, x ){
	this._parent_sheet = parent;
	this._cell = real_cell;
	this.type_code = parent.type_code;
	this.y = y;
	this.x = x;
};
IExcel.ICell.prototype = {
	_parent_sheet : null,
	_cell : null,
	type_code : null,
	
	// 行番号(1始まり)
	y : null,
	
	// 列番号(1始まり)
	x : null,
	
	// 値を書き込み
	setValue : function( v ){
		if( this.type_code == "ms" || this.type_code == "ks" ){
			this._cell.Value = v;
		}else{
			this._cell.String = v;
		}
	}
	,
	
	// 値を取得
	getValue : function(){
		var s = null;
		
		if( this.type_code == "ms" || this.type_code == "ks" ){
			s = this._cell.Value;
		}else{
			s = this._cell.String;
		}
		
		return s;
	},
	
	// 値を文字列として取得
	getValueAsString : function(){
		var s = this.getValue();
		if( ! s ){
			return "";
		}else{
			return "" + s;
		}
	}
	
	
	// TODO:他のセル操作メソッド
};


/*

サンプルコード


// Excel起動
var excel = new IExcel();
excel.setVisible( true );

// ファイル名を指定してブックを開く
var book = excel.openBookByFilePath( filepath );

// または新規ブックを開く
var book = excel.getNewBook();


// 最初のシート
var sheet = book.getSheetByIndex(1);


// シート内で空白が見つかるまで行をスキャン
sheet.scan_y(
  {
    x_check : 3
    y_from  : 1
  }, 
  function( y, cell ){
    // この行のセルを処理
  }
);

・・・

// ブックに名前をつけて保存
book.saveAs( filepath );

// またはブックを上書き保存
book.save();

*/