Excel ワークシートの一括 CSV 変換

漢詩詩語集を処理するに際して,Excel ファイルを CSV に一括変換するツール『Excel 一括 CSV』だと,JIS X 0212 のいわゆる JIS 第三・第四水準の文字の出力が「?」に化けてしまった。これをなんとかしたいと思い別のツールをいろいろ漁ってみたが,これというものが見当たらなかった。UNIX 系システムでの Excel ファイルのテキスト変換では,xlhtml というプログラムが昔から有名であるが,これとて日本語の扱いに関しては不十分なところがある。

この調査の過程で,Windows でなくても動作する Perl Excel ファイルハンドリング・モジュール Spreadsheet::ParseExcel の存在を知り,これを使って一括変換ツールを自作することにした。このモジュールは標準ではないので,コマンドラインから cpan -i Spreadsheet::ParseExcel としてインストールする。

Excel は古いバージョンで作成したものだと,ファイル内の日本語文字コードが Shift_JIS なのか CP932 なのか UCS2 なのかよくわからない。おまけに私の目的として,出力すべきファイルは UTF-8 エンコードでなければならない。Spreadsheet::ParseExcelSpreadsheet::ParseExcel::FmtJapan という日本語処理のための下位モジュールを備えている。これらを併用すれば,日本語文字コードに煩わされずにすみ,JIS X 0212 文字もうまく処理でき,かつ UTF-8 で出力できることがわかった。Spreadsheet::ParseExcel のマニュアル(perldoc Spreadsheet::ParseExcel で閲覧できる)に API 解説とサンプルコードがあるので,参照いただきたい。

ただし,Spreadsheet::ParseExcel Perl モジュールが処理できるのは Excel 2003 までの形式である。Excel 2007 以降のブックフォーマットは処理できないので注意。Excel 2007 以降をお持ちなら,「ファイル」メニュー ⇒「ファイルの種類の変更」⇒「Excel 97-2003 ブック」を選択すれば Excel 2003 形式で保存が可能である。

今回作成した Excel ワークシート一括 CSV 変換プログラム xlstocsv.pl を以下に掲げる。こんなに短いコードでできてしまうのなら,最初から自分の手を動かすべきであった。同じ課題をもつ方は必ずいらっしゃると思うので,以下のコードをコピって活用いただきたい。もちろん無保証,無サポートである。使い方は,コマンドラインで xlstocsv.pl 対象Excelファイル とする。引数に Excel ファイルを一つ指定する。すべてのワークシートが単一 CSV テキストストリームとして STDOUT に書き出される。ファイルに格納したいのならリダイレクトする。

#!/usr/bin/perl -w
# -*- coding: utf-8; mode: cperl; -*-
# Excel to CSV
use strict;
use utf8;
use Spreadsheet::ParseExcel;
use Spreadsheet::ParseExcel::FmtJapan;
binmode STDOUT, ":utf8"; binmode STDERR, ":utf8";
($#ARGV < 0) && die "Usage: $0 xls-file\n";
my $fnm = $ARGV[0]; # Excel file name
my $ct = 0;         # 出力行数
# Excel object
my $xls = Spreadsheet::ParseExcel->new();
my $fmt = Spreadsheet::ParseExcel::FmtJapan->new(); # 日本語あり
my $bko = $xls->parse($fnm, $fmt); # Excel Book object
my @wsa = $bko->worksheets();      # Worksheet object 配列
utf8::decode($fnm);
# ワークシート毎の処理
foreach my $ws (@wsa) {
    my $wsnm  = $ws->{"Name"};
    my $mxrow = $ws->{"MaxRow"};
    my $mxcol = $ws->{"MaxCol"};
    print STDERR "*** $0 $fnm - sheet: $wsnm start.\n";
    if ((defined $mxrow) && (defined $mxcol)) {
        # 行毎の処理
        for (my $row = 0; $row <= $mxrow; $row++) {
            my $line = "";
            # 列毎の処理
            for (my $col = 0; $col <= $mxcol; $col++) {
                # セル値をセット
                my $cell = $ws->get_cell($row, $col);
                $line .= $cell->value() if (defined $cell);
                $line .= ",";
            }
            print "$line\n";
            $ct++;
        }
    }
}
print STDERR "*** $0 from $fnm wrote $ct lines\n";

もしワークシート毎にファイルをばらしたいのなら,foreach my $ws (@wsa) のブロック(ワークシート単位のループ)部分の始めと終わりに独自ファイル処理(「ファイル名+シート名」でファイルハンドルを割り当てる等)を入れればよい。また,このコードは CSV をカンマ区切り形式とし,かつセルの内容に半角カンマが含まれない前提である。もし半角カンマが含まれてもよいようにするなら,得られたセル値を出力時にダブルクォーテーションマークで括る,等の処置を施していただきたい。

これで,一つの Excel ファイルに夥しいワークシートがあろうが,いちいち Excel を開いて CVS として保存しなくても,コマンドラインから一発で CSV 変換ができるようになった。おまけに FreeBSD でも Linux でも Mac OS X でも動くのである。もちろん Windows でも動くし,Excel がインストールされていなくても Excel ファイルを処理出来る,というメリットがある。

私の課題 — いただいた詩語集の処理に関しては,71 の Excel ファイルそれぞれに何十ものワークシートがあって,Excel をひとつひとつ開いてワークシート毎に CSV エクスポートするのはとてもやっていられないが,xlstocsv.pl を使えば次のコマンド操作だけで二字詩語,三字詩語の CSV(2c.csv, 3c.csv)をまとめることができた。

% foreach i (2c-xls/*.xls)
foreach? set BS=`basename $i .xls`
foreach? (xlstocsv $i > 2c-$BS.csv) >& 2c-$BS.err
foreach? end
% foreach i (3c-xls/*.xls)
foreach? set BS=`basename $i .xls`
foreach? (xlstocsv $i > 3c-$BS.csv) >& 3c-$BS.err
foreach? end
% env LC_ALL=C sort 2c-*.csv | uniq > 2c.csv
% env LC_ALL=C sort 3c-*.csv | uniq > 3c.csv

xlstocsv.pl は引数に Excel ファイルを一つしか指定できないが,複数の Excel ファイルを扱いたいなら,上の tcsh 操作のようにループで回せばよい。次のシェルスクリプトは,カレントディレクトリにあるすべての Excel ファイルを,「xls と同じファイルベース名+拡張子 csv」という名の CSV ファイルに書き出す。

#!/bin/sh
for i in *.xls
do
    BS=`basename $i .xls`
    xlstocsv.pl $BS.xls > $BS.csv
done