読者です 読者をやめる 読者になる 読者になる
スポンサーリンク

CSVをExcelファイルに変換するスクリプト (JScript/WSHで,カンマ区切りCSVファイルから.xlsを生成)

DB PostgreSQL WSH/JScript excel MS


JScript/WSHで,CSVを.xlsファイルに変換するスクリプト。


実は,Excelには致命的なバグ(致命的な仕様)があり,CSVの扱いがひどい。


どれほど工夫しても,CSVをExcel上で開くと,

  • 列がずれたり
  • セルの表示形式を楽に制御できず,値が変わったり
  • セル中の数式として「="〜"」と書いたら,数式として解釈されたりされなかったり・・・・

という具合に,ExcelはCSV処理時に誤認識が多すぎるのだ。

これでは,仕事にならない。



なので代わりに,

  • csvをそのままExcel上で開くのではなく,
  • 別の手段によってcsvをxlsに変換し,それからExcel上で開く

とすればよい。

これは確実な方法だ。



下記に掲載するコードを,csv2xls.js として保存。

そしてコマンドラインから,

cscript csv2xls.js old.csv new.xls

のように実行。

(※xlsのほうは絶対パスで書かないと,マイドキュメント内にファイルが生成される。)


/*
	CSVをExcelに変換するスクリプト
	
	CSVの前提条件:
		・SJIS
		・カンマ区切り
		・""によるくくり無し
		・データ中のカンマは\でエスケープされている
	
	使い方
	cscript csv2xls.js old.csv new.xls
*/



// -------------------- メイン処理 --------------------



// 引数取得
if( WScript.Arguments.length == 0 )
{
	log("引数がありません。");
	WScript.Quit();
}
var csv_path = WScript.Arguments.Unnamed(0);
var xls_path = WScript.Arguments.Unnamed(1);


// CSVを読み取る
var arr_data = get_csv_data( csv_path );


// Excelに書き込む
make_excel( arr_data, xls_path );


log("終了しました。");



// -------------------- 関数 --------------------



// CSVファイル中のデータを2次元配列で返します
function get_csv_data( csv_path )
{
	var arr_data = [];

	var fso = WScript.CreateObject("Scripting.FileSystemObject");
	var txt_r = fso.OpenTextFile( csv_path, 1 );
	var line = "";
	var arr_line_data;
	while( ! txt_r.AtEndOfStream )
	{
		line = txt_r.ReadLine();
		
		// この行を解析
		arr_line_data = parse_csv_line( line );
		arr_data.push( arr_line_data );
	}
	txt_r.Close();
	fso = null;
	
	return arr_data;
}



// CSV形式の1行の文字列を受け取り,配列として返します
function parse_csv_line( line )
{
	var arr_line_data = [];
	log( "「" + line + "」を解析します。" );
	
	// 先頭から1文字ずつ
	var index = 0; // 現在指している箇所
	var ch = ""; // 現在指している文字
	var csv_value_length = 0; // 切り出そうとしているデータの長さ
	var csv_value = ""; // 切り出された1セルのデータ
	var last_comma_index = -1; // 前回カンマがあった箇所
	var is_escaped = false; // 現在エスケープ中フラグ
	var len = line.length;
	while( true )
	{
		if( index >= len )
		{
			// 末尾に来た
			
			// 空行でなかったならば,行末をデータ区切りとみなす
			if( index > 0 )
			{
				// 登録
				record_to_arr( arr_line_data, csv_value );
			}
			break;
		}

		ch = line.charAt( index );
		log( index + " 文字目は " + ch );
		
		// 文字種によって分岐
		if( 
			// CSVの意味上のカンマが来た場合
			( ! is_escaped ) && ( ch == "," ) 
		)
		{
			// 登録
			record_to_arr( arr_line_data, csv_value );
			
			last_comma_index = index;
			is_escaped = false;
			csv_value = "";
		}
		else if( ( ! is_escaped ) && ( ch == "\\" ) )
		{
			// エスケープ状態を設定する
			is_escaped = true;
			log("\\によるエスケープが発生");
		}
		else
		{
			// \,以外のエスケープは通過させる
			if( is_escaped && ! ( ch == "," ) )
			{
				csv_value += "\\";
			}
			
			csv_value += ch;
			is_escaped = false;
		}
	
		// 次へ
		index ++;
	}
	
	return arr_line_data;
}



// CSVの1データをExcel用に加工し,配列に登録します。
function record_to_arr( arr_line_data, csv_value )
{
	var new_value = csv_value;

	// TODO:CSV上のデータ取得時に加工を行なう場合は,ここでnew_valueを変更。

	log(
		"「" 
		+ csv_value + 
		"」を登録 "
		// + "(Excel上では「" + new_value + "」)" 
	);
	arr_line_data.push( new_value );
}



// 2次元配列を受け取り,Excelに書き出します
function make_excel( arr_data, xls_path )
{
	// Excelを立ち上げる
	var excel = WScript.CreateObject("Excel.Application");
	excel.Visible = true;

	// 新規ワークブックを作成(既存の物は無視)
	excel.Workbooks.Add();
	var book = excel.Workbooks( excel.Workbooks.Count ); // ワークブックを取得

	// ブックを編集
	var sheet = book.WorkSheets("Sheet1"); // シートを取得
	var data_height = arr_data.length; // データ行数
	var data_width = 0;
	if( data_height > 0 )
	{
		data_width = arr_data[0].length; // データ列数
	}
	var csv_value;
	for( var i = 0; i < data_height; ++i )
	{
		for( var j = 0; j < data_width; ++j )
		{
			csv_value = arr_data[ i ][ j ];
 			sheet.Cells( i + 1, j + 1 ).Value = "'" + csv_value; // 文字列としてセルに書き込み
		}
	}

	// ブックを保存
	excel.DisplayAlerts = false; // 既存ファイルがあっても上書きする
	book.SaveAs( xls_path );

	// Excelを閉じて終了
	excel.Quit();
	excel = null;
}



// ログ出力
function log( str )
{
	WScript.Echo( str );
}

概説:

  • parse_csv_line() 中のwhile文で,CSVの一つの行を解析している。
    • ここを変えれば,""でくくられた(enclosed by \" であるような)CSVデータも取り扱い可能。

使用例

下記を,a.csvとして保存。

1,2,3,4,5,6,7
, ,aaa,,,',
,hello\,world,,01,2009/01/01,,

そしてコマンドラインから

cscript csv2xls.js a.csv b.xls

と実行。


コマンドプロンプト上には,下記のように解析状況が表示される。

D:\temp>cscript csv2xls.js a.csv d:\temp\b.xls
Microsoft (R) Windows Script Host Version 5.7
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

「1,2,3,4,5,6,7」を解析します。
0 文字目は 1
1 文字目は ,
「1」を登録
2 文字目は 2
3 文字目は ,
「2」を登録
4 文字目は 3
5 文字目は ,
「3」を登録
6 文字目は 4
7 文字目は ,
「4」を登録
8 文字目は 5
9 文字目は ,
「5」を登録
10 文字目は 6
11 文字目は ,
「6」を登録
12 文字目は 7
「7」を登録
「, ,aaa,,,',」を解析します。
0 文字目は ,
「」を登録
1 文字目は
2 文字目は ,
「 」を登録
3 文字目は a
4 文字目は a
5 文字目は a
6 文字目は ,
「aaa」を登録
7 文字目は ,
「」を登録
8 文字目は ,
「」を登録
9 文字目は '
10 文字目は ,
「'」を登録
「」を登録
「,hello\,world,,01,2009/01/01,,」を解析します。
0 文字目は ,
「」を登録
1 文字目は h
2 文字目は e
3 文字目は l
4 文字目は l
5 文字目は o
6 文字目は \
\によるエスケープが発生
7 文字目は ,
8 文字目は w
9 文字目は o
10 文字目は r
11 文字目は l
12 文字目は d
13 文字目は ,
「hello,world」を登録
14 文字目は ,
「」を登録
15 文字目は 0
16 文字目は 1
17 文字目は ,
「01」を登録
18 文字目は 2
19 文字目は 0
20 文字目は 0
21 文字目は 9
22 文字目は /
23 文字目は 0
24 文字目は 1
25 文字目は /
26 文字目は 0
27 文字目は 1
28 文字目は ,
「2009/01/01」を登録
29 文字目は ,
「」を登録
「」を登録
終了しました。

すると,\My Documents\b.xlsが生成される。


もしCSVが巨大なためにコマンドプロンプト上での処理が重い,と感じたら,最後のlog()内の処理をコメントアウトするとよい。

補足

DBのデータをCSVでエクスポートし,それをExcel上で編集したい,という時などに重宝する。




なお,今回のバッチを適用可能な形式のCSVとしてテーブルをエクスポートするためのSQLは下記。

postgres

COPY tablename TO '/tmp/hoge.out' WITH DELIMITER AS ',' NULL AS '';

mysql

SELECT * FROM tablename INTO OUTFILE '/tmp/hoge.out' FIELDS TERMINATED BY ',' LINES TERMINATER BY '\r\n';

ただしMySQLでは,セル内の改行が「行末の\」となって出力も途中で改行されてしまうので,別途バッチで\r\nに書き換える必要がある。