WSHで,エクセルシート上の列内・全項目を日本語・漢字から半角英数字の読みに変換するバッチ


一つ前のエントリで作った,シート内を縦方向にスキャンする関数を応用。

一行ずつスキャンして,読みがなをkakasiコマンドで生成している。

====

kakasiで読みがなを付与.bat

cscript do.wsf
pause


do.wsf

<job>
	<script src="lib_log.js" />
	<script src="lib_arr.js" />
	<script src="lib_cmd.js" />
	<script src="lib_excel.js" />
	<script src="main.js" />
</job>


lib_arr.js

// 配列のイテレータ
Array.prototype.each = function( func ){
	for( var i = 0; i < this.length; i ++ ){
		func.call( this, this[i], i ); 
	}
	return this; // チェインを継続
};


// map
Array.prototype.map = function( func ){
	var _arr = [];
	this.each(function( item, ind ){
		_arr.push( func.call( _arr, item, ind ) );
	});
	return _arr;
};


// オブジェクトを配列に変換
Array.src = function( iterable, func_item, func_length ){
	var arr = [];
	
	var length = null;
	if( func_length ){
		length = func_length( iterable );
	}else{
		iterable.length;
	}
	
	for( var i = 0; i < length; i ++ ){
		if( func_item ){
			arr.push( func_item( iterable, i ) );
		}else{
			arr.push( iterable[ i ] );
		}
	}
	return arr;
};


// Rubyのinjectに相当するメソッド
// http://d.hatena.ne.jp/TipsMemo+computer-technology/20150110/p1
Array.prototype.reduce = function( func, init_value ){
	// 初期値をセット
	var result = init_value;
	
	// 各要素ごとに
	this.each(function( item ){
		// 結果を累積更新する
		result = func( result, item )
	});
	
	// 累積結果を返す
	return result;
};


// 多次元配列を1次元にならす関数。
// 内部でreduceを使用
Array.prototype.flatten = function(){
	return this.reduce(
		function( result, item ){
	    	return (
	    		//Array.isArray( item ) // WSHや古いIEでは動かない
	    		( item instanceof Array )
	    			// 対象要素が配列ならば,再帰する
	    			? result.concat( item.flatten() ) 

	    			// 対象要素が配列でなければ,要素として採用
	    			: result.concat( item )
	    	);
		},
		
		// 空配列からはじめる
		[]
	);
};


lib_cmd.js

// ファイルパス文字列からファイル名を抽出
String.prototype.file_path_to_file_name = function(){
	return this.replace(/^.+\\([^\\]+)$/, "$1");
};


// ファイルパス文字列からフォルダパスを抽出
String.prototype.file_path_to_dir_path = function(){
	return this.replace(/\\[^\\]+$/, "");
};



// 配列のイテレータ
Array.prototype.each = function( func ){
	for( var i = 0; i < this.length; i ++ ){
		func.call( this, this[i], i ); 
	}
	return this; // チェインを継続
};

// map
Array.prototype.map = function( func ){
	var _arr = [];
	this.each(function( item, ind ){
		_arr.push( func.call( _arr, item, ind ) );
	});
	return _arr;
};


function log(s){ WScript.Echo(s); }

//
function err(s){
	try{
		WScript.StdErr.WriteLine(s); 
		// リダイレクトされずにコンソールにも表示される
	}catch(e){
		// GUIで実行された場合は標準出力だけでよい
	}
	
	log(s);
}



// コマンド実行結果を行ごとに配列として取得
// http://d.hatena.ne.jp/language_and_engineering/20081225/1230198688
function exec_cmd( str_cmd, params ){

	// ------ パラメータを取得 -----

	// コマンドを実行したいカレントフォルダのパス
	var current_dir    = ( params ) ? params.current_dir : null;
	
	// カレントフォルダを,このスクリプトの存在するフォルダとするか
	var current_dir_same_as_this_script = ( params ) ? params.current_dir_same_as_this_script : false;
	
	// 通信コマンドが終了するまで待つかどうか
	var wait_while_net = ( params ) ? params.wait_while_net : false;
	
	// 出力行から一行抜き出すための正規表現
	var get_one_output_re = ( params ) ? params.get_one_output_as : null;

	// 出力行から抜き出した一行をキャプチャするための文字列
	var capture_output_str = ( params ) ? params.capture_output_as : null;

	// コマンドを実際には実行しないデバッグモード
	var just_debug_mode = ( params ) ? params.just_debug : false;
	
	// ------ パラメータ取得終わり -----
	

	var ws = WScript.CreateObject("WScript.Shell");
	
	// 必要ならカレントフォルダをセット
	if( current_dir ){
		ws.CurrentDirectory = params.current_dir;
	}else
	if( current_dir_same_as_this_script ){
		ws.CurrentDirectory = WScript.ScriptFullName.file_path_to_dir_path();
	}

	// もしデバッグモードなら,ここで終了
	if( just_debug_mode ){
		err( "カレントフォルダ:" + ws.CurrentDirectory + ", コマンド:" + str_cmd );
		err( "デバッグモードのため,コマンドは実行しません。" );
		
		return;
	}

	err( "実行します:カレントフォルダ:" + ws.CurrentDirectory + ", コマンド:" + str_cmd );

	// コマンド実行
		var proc = ws.Exec( "cmd /c " + str_cmd );
	
	// もしメール送信など通信系のコマンドであれば,終了まで待つ
	if( wait_while_net ){
		while( proc.Status == 0 ){
			WScript.Sleep(100);
		}
		// http://d.hatena.ne.jp/language_and_engineering/20100913/p1
	}
	
	// 出力を取得
	var str_out = proc.StdOut.ReadAll();
	
	// 出力の末尾の空行を削除
	var output_arr = str_out.split("\r\n");
	output_arr.pop();
	
	// 出力から一行だけ取り出す?
	if( get_one_output_re ){
		log("出力から一行取り出します。");
		var match_result = null;
		
		// 行ごとに検査
		output_arr.each(function(line){
			var m = line.match( get_one_output_re );
			
			// 初回マッチについて
			if( m && ( ! match_result ) ){
				// マッチ結果からキャプチャした文字列を保管
				match_result = line.replace(
					get_one_output_re, 
					capture_output_str 
				);
					err("マッチ結果を発見:" + match_result);
			}
		});
		
		// 全行を検査し終わってもマッチしていなかったらNG
		if( ! match_result ){
			throw("出力のどの行もマッチしませんでした。");
		}
		
		// マッチ結果を返す
		return match_result;
	}
	
	// 通常は出力の配列をそのまま返す
	return output_arr;
}




lib_excel.js

//
// MS ExcelとOOo CalcとKingsoft Spreadsheetsを
// 共通して取り扱うためのライブラリ
//
// ver0.3(セルのキャッシュ機構でセル読み書きを高速化)
//

// 要件:
// Excelで組んだロジックをOOcで使いまわし,その逆も可としたい。

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


/*

  クラス設計:

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

*/



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


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 = parent;
	this._book = real_book;
	this.type_code = parent.type_code;
	this._sheets = [];
};
IExcel.IBook.prototype = {
	_parent : null,
	_book : null,
	type_code : null,
	
	// ファイルパスを指定して保存
	saveAs : function( file_path ){
		if( this.type_code == "ms" || this.type_code == "ks" ){
			this._parent.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.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._parent, 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._parent, 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 = parent;
	this._sheet = real_sheet;
	this.type_code = parent.type_code;
	this.cell_caches = {};
};
IExcel.ISheet.prototype = {
	_parent : 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._parent, cell );
		
		// キャッシュ登録
		this.storeCellCache( y, x, icell );
		
		return icell;
	},
	
	// シートを縦方向にスキャン
	scan_y : function( params, func ){
		var y_from  = params.y_from;
		var check_x = params.check_x;
		
		var continue_flag = true;
		var y = y_from;
		while( continue_flag ){
			var cell = this.getCell( y, check_x );
			var s = cell.getValue();
			if( s && ( s.length > 0 ) ){
				func( y, cell );
				y ++;
			}else{
				continue_flag = false;
			}
		}
	}
	
	// TODO:他のシート操作メソッド

};


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


IExcel.ICell = function( parent, real_cell ){
	this._parent = parent;
	this._cell = real_cell;
	this.type_code = parent.type_code;
};
IExcel.ICell.prototype = {
	_parent : null,
	_cell : null,
	type_code : null,
	
	// 値を書き込み
	setValue : function( v ){
		if( this.type_code == "ms" || this.type_code == "ks" ){
			this._cell.Value = v;
		}else{
			this._cell.String = v;
		}
	}
	,
	
	// 値を取得
	getValue : function(){
		if( this.type_code == "ms" || this.type_code == "ks" ){
			return this._cell.Value;
		}else{
			return this._cell.String;
		}
	}
	
	
	// TODO:他のセル操作メソッド
};


/*

サンプルコード


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

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

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


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

・・・

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

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

*/



lib_log.js

function log(s){ WScript.Echo(s); }

String.prototype.log = function(){
	log( this );
};




main.js

var filepath = WScript.ScriptFullName.replace(/\\[^\\]+$/, "\\" )
	+ "merge.ods"
;


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

// 対象ブックを開く
var book = excel.openBookByFilePath(filepath);

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


// 全行について
sheet.scan_y(
	{
		y_from  : 1,
		check_x : 4
	},
	function( y, cell ){
		var kwd = cell.getValue();
		var cate = sheet.getCell( y, 2 ).getValue();
		
		var kakasi_command = 'echo '
			+ cate
			+ " "
			+ kwd
			+ ' | kakasi -w | kakasi -Ja -Ka -Ha -Ea'
		;
		
		var yomi = exec_cmd( kakasi_command ).join("");
		
		// キャメルにする(小文字だけのアルファベットが続くと読みづらいので)
		yomi = yomi
			// はてなの記事URLに_と英数字だけが使える
			.replace(/\-/g, "_")
			.replace(/\^/g, "")
			.replace(/'/g, "")
			.replace(/\./g, "")
			.split(" ")
			.map(function(token){
				if( token.length < 1 ) return "";
				
				var top_c = token.slice(0,1);
				
				return top_c.toUpperCase() + token.slice( 1, token.length );
			})
			.join(" ")
			.replace(/ /, "_") // 最初の区切りだけはアンダーバーにする
			.replace(/ /g, "")
		;
		
		// 念のため検査
		if( yomi.match( /^[a-zA-Z0-9_]+$/ ) ){
			// OK
		}else{
			log("読みが不正:" + yomi);
			throw "NG";
		}
		
		sheet.getCell( y, 7 ).setValue( yomi );
	}
);


// ブックを保存
//book.saveAs( filepath );




こりゃ便利だわ・・・。

何が便利って,

  • ここで作った「シート内の一括読みがな生成バッチ」も便利だし,
  • 上記に記載したように,フォルダ内の全バッチを一発でまとめて,ソースコードを列挙するためのバッチも作ってあって,それも便利。

それぞれ参照:

WSH表計算ソフトの統一API: シート内を縦方向にスキャンする関数を追加 - ソフトウェア勉強ログとサンプルコード
http://source-code-student.hatenablog.jp/entry/20150115/p4


Windowsで「kakasi」のコマンドを使い,日本語文章を単語に分解,ローマ字変換する方法 (kakasi形態素解析するWindowsバッチのサンプルコード)
http://d.hatena.ne.jp/language_and_engineering/20150109/KakasiOnWindowsUsageSample


フォルダ内の全バッチ系ファイルを,はてなのブログ投稿用にまとめるバッチ - ソフトウェア勉強ログとサンプルコード
http://source-code-student.hatenablog.jp/entry/20150105/p3