CSVをExcelファイルに変換するスクリプト (JScript/WSHで,カンマ区切りCSVファイルから.xlsを生成)
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に書き換える必要がある。