かつて,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);