CSV to Microsoft Excel generator

ちょっと必要があって,複数の CSV テキストファイルからデータをこれに対応するワークシートに格納して,Microsoft Excel ブックファイルを生成する Perl プログラム csvtoxls.pl を書いた。かつて,書籍情報の Excel ファイルを読んでジャンル毎の CSV ファイルに分解し,ここから SQLite3 リレーショナルデータベースを作成するプログラムを書いたのだが,ちょうどこの逆を行うプログラムである。Microsoft Excel といっても,Office 2003 までのブック形式を生成するものである(私は個人的に Office 2003 しか所有していない)。

csvtoxls.pl 処理概要

プログラムの引数に,4 つの CSV テキストファイルのベース名と生成すべき Excel ファイル名をとるものとする。CSV ファイルベース名はそのまま Excel シート名に設定するものとする。入力する CSV テキストファイルは引数で指定した名称に拡張子 .txt を付加したものとする。例えば,
% ./csvtoxls.pl genre_0 genre_1 genre_2 genre_3 genre_4 gen.xls

として起動すると,次を順次行う。genre_0.txtgenre_1.txtgenre_2.txtgenre_3.txtgenre_4.txt の CSV ファイルをこの順番で読み,これら CSV ファイル名から拡張子 .txt を除いた名称で 5 つの Excel ワークシートを生成し,各 CSV データを対応するワークシートに格納し,ひとつの gen.xls という Excel ブックデータに纏める。

Spreadsheet::WriteExcel モジュール

Excel データ出力には Perl モジュール Spreadsheet::WriteExcel を利用した。これはセルの書式設定やチャート出力,計算式の格納を含めて取り扱うことのできる優れものである。

CSV データの各項目を Excel 表として生成するのに限れば,一般的に処理の流れとしては以下のようになるだろう。

  1. Excel book オブジェクトを生成する。
    my $xlsbook = Spreadsheet::WriteExcel->new("Excelファイル名");
  2. ワークシートオブジェクトを生成する。
    my $xlsws = $xlsbook->add_worksheet("ワークシート名");
  3. 必要に応じ,セルの書式を設定する。書式はフォントや罫線など多様に設定出来るようになっている。
    my $cellfmt = $xlsbook->add_format();
    $cellfmt->set_align('left');
  4. 必要に応じ,列の幅を設定する。「開始列」から「終了列」の範囲で設定するインタフェースである。いちばん左の列は列 0 とする。“B:F”(B列からF列まで)というような指定も可能である。
    $xlsws->set_column(開始列, 終了列, 幅);
      
  5. ワークシートのセルに値を格納する。セル書式指定($cellfmt)の指定は任意である。
    $xlsws->write(行, 列, データ, $cellfmt);
    は第 1 行,第 1 列のセルの位置をそれぞれ 0 とする数値である。write メソッドは標準的なもので,テキストデータ用の write_string,数値用の write_number などのデータ属性に応じたメソッドが用意されている。
  6. すべての出力処理が完了したらブックオブジェクトをクローズする。
    $xlsbook->close;

日本語は UTF-8 の場合,とくに意識しなくてよい。Windows 機種依存文字を扱うならば,Encode モジュールを併用して CP932 にデコードすればよい。

$xlsws->write(行, 列, decode("cp932", "データ"));

Spreadsheet::WriteExcel の仕様の詳細は,perldoc Spreadsheet::WriteExcel で閲覧出来るマニュアルを参照のこと。きちんと書かれている。

Perl コード

csvtoxls.pl コードを以下に掲載する。上記処理概略に対し,Excel 文書プロパティの設定も行っている。私の必要性から,入力 CSV の DOS 行末形式の行末コード(\r: CR + \n: LF)を削除していたり,CSV のセパレータが “|” であったりと,少しローカルな処理が入ってはいるけれども,複数の CSV をそれぞれのワークシートに格納し,ひとつの Excel ブックを生成する例として参考になればと思う。

#!/usr/bin/perl -w
# -*- coding: utf-8; mode: cperl; -*-
# csvtoxls.pl: Excel Book generator from CSV
# - usage: csvtoxls.pl sht1 sht2 sht3 sht4 sht5 xls
#     sht1-5: シート名, CSVファイルのベース名(拡張子 .txt 前提)
#     xls: 出力 xls ファイル名
# - CSVフォーマット(セパレータ '|'):
#     0対象外|1題|2副題|3著者|4刊行月|5ISBN|6価格|7刊行状態|8画像|9コメント
# 2015(c) isao yasuda, All Rights Reserved.
# $Id: csvtoxls.pl 121 2015-09-23 08:20:56Z isao $
 
use strict;
use utf8;
use SpreadSheet::WriteExcel;
binmode(STDERR, ":utf8");
 
# コマンドライン引数が 6(@ARGV 最大配列番号が 5)でなければ usage を出力して終了
($#ARGV == 5) ||
    die "Usage: $0 sht1 sht2 sht3 sht4 sht5 xls\n" .
    "  sht1-5: Worksheet name, CSV file basename (without .txt)\n" .
    "  xls: Excel file name\n";
 
# Excel book オブジェクトの生成
my $xlsfl = $ARGV[5]; # Excel ファイル名
my $xlsbook = Spreadsheet::WriteExcel->new($xlsfl);
 
# 文書プロパティ設定
$xlsbook->set_properties(
                       title    => '書籍管理データベース',
                       author   => 'isao yasuda',
                       comments => '最新書籍データベースから逆生成',
                      );
 
# セルフォーマット設定
# - 水平: left; 垂直: top; 折返し表示: on;
my $cellfmt = $xlsbook->add_format();
$cellfmt->set_text_wrap();
$cellfmt->set_align('left');
$cellfmt->set_align('top');
 
# CSVファイル毎の処理
for (my $i = 0; $i < 5; $i++) {
    # CSV file open
    my $sn = $ARGV[$i];
    open(CI, "<:utf8", "$sn.txt") || die "* $0 open failed $sn.txt: $!\n";
 
    # 引数の文字列のシート名で Excel worksheet を生成する
    my $xlsws = $xlsbook->add_worksheet($sn);
 
    # ワークシート列幅の個別設定
    $xlsws->set_column(1, 1, 14); # col 1: タイトル to 14
    $xlsws->set_column(3, 3, 10); # col 3: 著者名 to 10
    $xlsws->set_column(5, 5, 17); # col 5: ISBN to 17
    $xlsws->set_column(8, 8, 20); # col 8: 画像ファイル名 to 20
    $xlsws->set_column(9, 9, 50); # col 9: コメント(説明文) to 50
 
    my $icnt = 0; # CSV入力行カウンタ
    my $rp = 0;   # シート行ポインタ
 
    # テーブルヘッダ出力
    $xlsws->write_string($rp, 0, "対象外");
    $xlsws->write_string($rp, 1, "タイトル");
    $xlsws->write_string($rp, 2, "副題");
    $xlsws->write_string($rp, 3, "著者名");
    $xlsws->write_string($rp, 4, "刊行月");
    $xlsws->write_string($rp, 5, "ISBN");
    $xlsws->write_string($rp, 6, "価格");
    $xlsws->write_string($rp, 7, "刊行状態");
    $xlsws->write_string($rp, 8, "画像ファイル名");
    $xlsws->write_string($rp, 9, "コメント(説明文)");
    $rp++;
 
    # CSVデータ出力
    while (<CI>) {
        $_ =~ s/\r\n//; # DOS形式行末コードを削除
        $icnt++;
        # CSV カラム分解(セパレータ '|'),カラム数チェック
        my @col = split(/\|/, $_);
        ($#col == 9) || die "* $0 $sn.txt csv format illegal.\n";
        # Excel シートに格納
        for (my $j = 0; $j < 10; $j++) {
            my $rc;
            $rc = $xlsws->write_string($rp, $j, $col[$j], $cellfmt);
            # 非0 でエラー
            ($rc) &&
                die "* $0 writing error occured. code: $rc ($sn.txt: $icnt)\n";
        }
        $rp++;
    }
    close(CI);
    print STDERR "* $0 sheet $sn normally written $icnt records.\n";
}
$xlsbook->close;
print STDERR "* $0 normally generated $xlsfl.\n";

実行結果

% ./csvtoxls.pl 0_kushu 1_kashu 2_sonota 3_haidan 4_kadan test.xls
* ./csvtoxls.pl sheet 0_kushu normally written 117 records.
* ./csvtoxls.pl sheet 1_kashu normally written 92 records.
* ./csvtoxls.pl sheet 2_sonota normally written 93 records.
* ./csvtoxls.pl sheet 3_haidan normally written 10 records.
* ./csvtoxls.pl sheet 4_kadan normally written 9 records.
* ./csvtoxls.pl normally generated test.xls.

生成された Excel を開いた状態は下図のとおり。プログラムのなかで行ったセルの書式設定により,セル表示が「水平左寄せ」,「垂直上端揃え」,「折り返して全体を表示オン」となっている。列の幅もきちんと設定されている。

20150923-csvtoxls.png
図 1. 生成された Excel ブック