かつて,Microsoft Excel ブックに含まれるワークシートすべてを一括して CSV データファイルに書き出すプログラムについて,記事を書いた(「Excel ワークシートの一括 CSV 変換」)。しかしながら,そこで示した方法は,Microsoft Excel 97-2003 ブック形式(拡張子 .xls)を前提としていた。Microsoft Office 2007 以降の最近の Excel はフォーマットが大きく変わり,このプログラムでは扱えない。ちょっと必要があり,Excel 2007 フォーマット(拡張子 .xlsx)のブックのワークシートすべてを一括して CSV に書き出すプログラムを作成した。今回も Perl 言語である。
Spreadsheet::XLSX
Excel 2007 フォーマットを処理できる Perl モジュールとして Spreadsheet::
Spreadsheet::
要するに,cpan -i Spreadsheet::XLSX で単純にインストールするだけでは不十分で,Spreadsheet::
Spreadsheet::XLSX-0.13 のモジュール訂正とインストール
以下の記述は Spreadsheet::XLSX バージョン 0.13 が前提である。まずは CPAN からアーカイブをダウンロードして解凍する。
% wget -nH -nd http://search.cpan.org/CPAN/authors/id/D/DM/DMOW/\ Spreadsheet-XLSX-0.13-withoutworldwriteables.tar.gz % tar zxvf Spreadsheet-XLSX-0.13-withoutworldwriteables.tar.gz -C ~/var % cd ~/var/Spreadsheet-XLSX-0.13
ここで,日本語問題訂正のパッチファイルを準備する。以下をコピーして xlsx-patch というファイル名で Spreadsheet-
*** lib/Spreadsheet/XLSX.pm.orig 2015-03-12 14:39:43.000000000 +0900
--- lib/Spreadsheet/XLSX.pm 2015-03-12 14:40:36.000000000 +0900
***************
*** 43,48 ****
--- 43,49 ----
my $mstr = $member_shared_strings->contents;
$mstr =~ s/<t\/>/<t><\/t>/gsm; # this handles an empty t tag in the xml <t/>
foreach my $si ($mstr =~ /<si.*?>(.*?)<\/si/gsm) {
+ $si =~ s/<rPh[^>]*>.*?<\/rPh>//gsm;
my $str;
foreach my $t ($si =~ /<t.*?>(.*?)<\/t/gsm) {
$t = $converter -> convert ($t) if $converter;
上記パッチを適用すると,XLSX.pm モジュールが訂正される。
% pwd /Users/isao/var/Spreadsheet-XLSX-0.13 % ls Changes* Makefile.PL* lib/ xlsx-patch MANIFEST* README* t/ % patch -l -p0 < xlsx-patch patching file lib/Spreadsheet/XLSX.pm %
パッチを適用すると 45 行目の次に $si =~ s/<rPh[^>]*>.*?<\/rPh>//gsm; という行が追加されているはずである。Excel ファイルのセルデータ XML の rPh タグにカナが格納されており,パッチで追加したコードは,この rPh タグデータをごっそり削除してしまう処理を実行するものである。パッチが面倒であればテキストエディタを使って XLSX.
foreach my $si ($mstr =~ /<si.*?>(.*?)<\/si/gsm) {
$si =~ s/<rPh[^>]*>.*?<\/rPh>//gsm;
my $str;
foreach my $t ($si =~ /<t.*?>(.*?)<\/t/gsm) {
$t = $converter -> convert ($t) if $converter;
$str .= $t;
}
さて,パッチを当てたらモジュールをインストールする。パッチ適用したモジュールを組み込むので,ここは make を使う。Spreadsheet::XLSX は Archive::Zip モジュールを前提とするので,以下はこれもあらかじめ組み込むオペレーションをも示している(すでにインストール済みなら cpan -i Archive::Zip は不要である)。
% sudo cpan -i Archive::Zip % perl Makefile.PL % make % make test % sudo make install
xlsx2csv.pl Perl コード
Excel 2007 シート CSV 一括変換 Perl プログラム xlsx2csv.pl のコードを以下に掲載する。Excel xlsx ブックファイル名を引数に取り,そのシートすべてについてセルを “|” 区切りの CSV データとして “シート名.
#!/usr/bin/perl -w
# -*- coding: utf-8; mode: cperl; -*-
# Excel 2007 format to CSV
# 2015(c) isao yasuda, All Rights Reserved.
# $Id: xlsxtocsv.pl 164 2015-03-12 10:32:38Z isao $
# DESCRIPTION
# -----------
# - .xlsx Excel Book 2007 format を読んで,Sheet名.csv を書き出す
# - セパレータは '|'
# - 表頭(項目名)行は無視する
use strict;
use utf8;
use Spreadsheet::XLSX;
binmode STDOUT, ":utf8"; binmode STDERR, ":utf8";
($#ARGV < 0) && die "Usage: $0 xlsx-file\n";
my $fnm = $ARGV[0]; # Excel file name
my $ct = 0; # csv file 出力行数
my $ca = 0; # 全出力行数
my $sep = '|';
my $re = "\\" . $sep;
# Excel object
my $xlsx = Spreadsheet::XLSX->new($fnm);
utf8::decode($fnm); # 日本語ファイル名対応
# ワークシート毎の処理
foreach my $ws (@{$xlsx->{Worksheet}}) {
my $wsnm = $ws->{"Name"};
my $mxrow = $ws->{"MaxRow"};
my $mxcol = $ws->{"MaxCol"};
my $csvfn = $wsnm . ".csv";
open(CF, ">:utf8", $csvfn) || die "* $0: $csvfn open failed:$!\n";
print STDERR "* $0: $fnm - sheet: $wsnm\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);
if (defined $cell) {
my $celv = $cell->value() ;
utf8::decode($celv);
$line .= $celv;
$line .= $sep;
}
}
unless ($line =~ /^[\s$re]*$/) { # 空白文字,セパレータのみの行は無視
$line =~ s/$re$//; # 末尾のセパレータを削除
print CF "$line\n";
$ct++;
}
}
}
close(CF);
print STDERR "* $0: $csvfn $ct lines\n";
$ca += $ct;
$ct = 0;
}
print STDERR "* $0: $fnm $ca lines proceeded.\n";
処理のポイントは以下のとおりである。
- my $xlsx = Spreadsheet::XLSX->new("xlsxファイル名"); でブックオブジェクトを取得
- @{$xlsx->{Worksheet}} で得られるシートオブジェクト配列について,シート毎(変数 $ws に格納されている)にセルを参照する
- あらかじめ $ws->{MaxRow}(最大行)及び $ws->{MaxCol}(最大列)を取得しておき,セル参照ループの限界値として用いる
- セルは $ws->{Cells}[$row][$col]->{Val} でその値を取得することができる(undef でないことを確認しつつ操作すべきである)
文字コードについて
通常 Spreadsheet::XLSX は文字を UTF-8 で出力する(Excel 2007 が内部で Unicode を用いているからだと思われる)。UTF-8 で困ることはないと思われるが,互換性等のアプリケーションの要請で,出力を別の日本語コード,たとえば,Shift_JIS で出力したい場合があるかも知れない。そのときは,Spreadsheet::
use Text::Iconv;
my $converter = Text::Iconv->new("utf-8", "shift_jis");
my $xlsx = Spreadsheet::XLSX->new($fnm, $converter);