レベル: 初級 Teodor Zlatanov (tzz@iglou.com)Gold Software Systems
2001年 9月 01日 Microsoft Excelは、デスクトップ向けスプレッドシート・アプリケーションとして最も一般的なものですが、Perlによる使用は最近になってようやく本格化してきました。この記事では、WindowsおよびLinuxでのPerlによるExcelファイルの読み取り/書き込み、ならびにいくつかのシンプル・モジュールについて考察します。この記事の著者Teodor Zlatanov氏は、1992年以来コミュニティーで活躍中のPerlのエキスパートで、特にテキスト構文解析に関するオープン・ソース活動に力を入れています。
Excelファイルの構文解析はどう考えても難問です。昨年まで、UNIX用のモジュールは全く存在せず、Windows版Excelファイルのデータ検索にはWin32::OLEモジュールを使用するしかありませんでした。しかし、2人のPerlスペシャリストと多くのボランティアによる支援と貢献によって、この状況も変わりつつあります。
Spreadsheet::WriteExcelおよびSpreadsheet::ParseExcel
2000年に川合孝典氏とJohn McNamara氏がCPANで発表したSpreadsheet::WriteExcelおよびSpreadsheet::ParseExcelモジュールによって、それほど簡単ではないにせよ、どのプラットフォームでもExcelファイルからのデータ抽出が可能となりました。
後で説明しますが、Windows環境ではWin32::OLEの方が依然としてシンプルで信頼性の高いソリューションであり、Spreadsheet::WriteExcelモジュールでも、データやワークシートに対するより高い操作性が求められる場合にはWin32::OLEの使用が推奨されます。Win32::OLEは、ActiveState Perlツールキットに付属しており、他の多くのWindowsアプリケーションとのOLEによる連動を可能にします。ただし、このモジュールを使用する場合も、マシンにExcelエンジンがインストールされていることと(通常はExcel本体とともにインストールされる)、Excelのライセンスが必要となります。
Excelデータの構文解析が必要なアプリケーションは数多くありますが、ほんのいくつかの例を挙げると、CSVへのExcelのエクスポート、共有ドライブにあるスプレッドシートとのインターラクション、(報告書作成用の)財務データのデータベースへの移動、他のフォーマットで提供されないデータの分析、などがあります。
この記事で示す例を実行するには、Perl 5.6.0がシステムにインストールされていることが必要です。できれば、システムは最新(2000年以降)の主要なUNIX(Linux、Solaris、BSD)であることが望まれますが、それ以前のバージョンのPerlやUNIX、その他オペレーティング・システムでも同様に動作するはずです。動作しない場合は、皆さん自身の課題として取り組んでみてください。
Windowsでの例:構文解析
このセクションはWindowsマシンのみを対象としていますが、その他のセクションはLinuxを対象とします。
まず、Perlの編集およびデバッグ用にActiveState Perl(ここではバージョン628を使用)またはActiveState Komodo IDEをインストールしてください。Komodoは、ホーム・ユーザー向けにフリー・ライセンスを提供しており、簡単に入手できます(ダウンロード・サイトは、この記事末尾の参考文献を参照)。
Spreadsheet::ParseExcelおよびSpreadsheet::WriteExcelモジュールを、ActiveState PPMパッケージ・マネージャーでインストールすることは困難です。PPMは歴史が浅く、オプションの設定も難しく、ヘルプ画面がスクロールオフしてしまううえ、デフォルトではモジュールの依存関係が無視されてインストールされます。コマンド・ラインからPPMを起動するには「ppm」と入力し、以下のコマンドを発行します。
リスト1:PPMコマンドによるExcelモジュールのインストール
ppm> install OLE::Storage_Lite
ppm> install Spreadsheet::ParseExcel
ppm> install Spreadsheet::WriteExcel
|
この場合、モジュールのインストールは失敗します。これは、IO::Scalarがまだ使用できないことが原因です。PPMに関する問題の追求はあきらめて、その代わりに組み込みWin32::OLEモジュールを使用した方がよいでしょう。しかし、この記事が掲載される頃には、ActiveStateによってこの問題のフィックスがリリースされるでしょう。
ActiveStateツールキットのWin32::OLEでは、以下のコードによって、セルごとにワークシートのダンプが可能です。
win32excel.plをダウンロード
リスト2:win32excel.pl
#!/usr/bin/perl -w
use strict;
use Win32::OLE qw(in with);
use Win32::OLE::Const 'Microsoft Excel';
$Win32::OLE::Warn = 3; # die on errors...
# get already active Excel application or open new
my $Excel = Win32::OLE->GetActiveObject('Excel.Application')
|| Win32::OLE->new('Excel.Application', 'Quit');
# open Excel file
my $Book = $Excel->Workbooks->Open("c:/komodo projects/test.xls");
# You can dynamically obtain the number of worksheets, rows, and columns
# through the Excel OLE interface. Excel's Visual Basic Editor has more
# information on the Excel OLE interface. Here we just use the first
# worksheet, rows 1 through 4 and columns 1 through 3.
# select worksheet number 1 (you can also select a worksheet by name)
my $Sheet = $Book->Worksheets(1);
foreach my $row (1..4)
{
foreach my $col (1..3)
{
# skip empty cells
next unless defined $Sheet->Cells($row,$col)->{'Value'};
# print out the contents of a cell printf "At ($row, $col) the value is %s and the formula is %s\n",
$Sheet->Cells($row,$col)->{'Value'},
$Sheet->Cells($row,$col)->{'Formula'}; }
}
# clean up after ourselves
$Book->Close;
|
セルに対する値の割り当ては、以下のように簡単に行うことができます。
$sheet->Cells($row, $col)->{'Value'} = 1;
|
Linuxでの例:構文解析
このセクションはUNIX、特にLinuxを対象としており、Windows環境でのテストは行われていません。
Spreadsheet::ParseExcelモジュールのドキュメンテーションに優れたLinuxでの構文解析例が示されています。ここではその例を借用して、その仕組みについて説明します。
parse-excel.plをダウンロード
リスト3:parse-excel.pl
#!/usr/bin/perl -w
use strict;
use Spreadsheet::ParseExcel;
my $oExcel = new Spreadsheet::ParseExcel;
die "You must provide a filename to $0 to be parsed as an Excel file" unless @ARGV;
my $oBook = $oExcel->Parse($ARGV[0]);
my($iR, $iC, $oWkS, $oWkC);
print "FILE :", $oBook->{File} , "\n";
print "COUNT :", $oBook->{SheetCount} , "\n";
print "AUTHOR:", $oBook->{Author} , "\n"
if defined $oBook->{Author};
for(my $iSheet=0; $iSheet < $oBook->{SheetCount} ; $iSheet++)
{
$oWkS = $oBook->{Worksheet}[$iSheet];
print "--------- SHEET:", $oWkS->{Name}, "\n";
for(my $iR = $oWkS->{MinRow} ;
defined $oWkS->{MaxRow} && $iR <= $oWkS->{MaxRow} ;
$iR++)
{
for(my $iC = $oWkS->{MinCol} ;
defined $oWkS->{MaxCol} && $iC <= $oWkS->{MaxCol} ;
$iC++)
{
$oWkC = $oWkS->{Cells}[$iR][$iC];
print "( $iR , $iC ) =>", $oWkC->Value, "\n" if($oWkC);
}
}
}
|
この例のテストは、Excel 97で行いました。もしうまく動作しない場合は、Excel フォーマットをExcel 97フォーマットに変換してみてください。Spreadsheet::ParseExcelのperldocページでは、Excel 95でもExcel 2000でも互換性があるとされています。
この例では、スプレッドシートを$oBookというトップレベル・オブジェクトに構文解析しています。$oBookには、"File"、"SheetCount"、"Author"といった、プログラムをサポートするプロパティーがあります。これらプロパティーの詳細については、Spreadsheet::ParseExcelのperldocページの「workbook」セクションをご覧ください。
ワークブックにはいくつかのワークシートが含まれ、ワークブックのSheetCountプロパティーによって反復されます。各ワークシートにはMinRowとMinColとそれぞれに対応するMaxRowとMaxColというプロパティーがあり、それらによってワークシートのアクセス範囲を決めることができます。これらプロパティーの詳細については、Spreadsheet::ParseExcelのperldocページの「worksheet」セクションをご覧ください。
Cellsプロパティーを使用するとワークシートからセルを取得することができます。リスト3ではそうして$oWkCオブジェクトを取得しています。セルに関するプロパティーの詳細については、Spreadsheet::ParseExcelのperldocページの「Cell」セクションをご覧ください。ドキュメンテーションによると、特定のセル内にある数式を取得する方法はないようです。
Linuxでの例:書き込み
このセクションはUNIX、特にLinuxを対象としており、Windows環境でのテストは行われていません。
たくさんのSpreadsheet::WriteExcelのサンプルが、通常はExamplesディレクトリー(/usr/lib/perl5/site_perl/5.6.0/Spreadsheet/WriteExcel/examples)にありますが、他の場所にもあります。ディレクトリーが見つからない場合は、Perl管理者にお問い合わせください。
Spreadsheet::WriteExcelの問題点は、既存のExcelファイルへの書き込みにそれを使用できないことです。その場合、Spreadsheet::ParseExcelを使用して、既存のExcelファイルからデータをインポートする必要があります。一方、Spreadsheet::WriteExcelの優れている点は、Excel 5からExcel 2000までの互換性があることです。
以下のプログラムは、Excelファイルからデータを抽出し、変更(すべての数値を2倍にする)を行い、新規Excelファイルに書き込む方法を示しています。データのみが保存され、書式設定やプロパティーは保存されません。また、数式は除去されます。
excel-x2.plをダウンロード
リスト4:excel-x2.pl
#!/usr/bin/perl -w
use strict;
use Spreadsheet::ParseExcel;
use Spreadsheet::WriteExcel;
use Data::Dumper;
# cobbled together from examples for the Spreadsheet::ParseExcel and
# Spreadsheet::WriteExcel modules
my $sourcename = shift @ARGV;
my $destname = shift @ARGV or die "invocation: $0 <source file> <destination file>";
my $source_excel = new Spreadsheet::ParseExcel;
my $source_book = $source_excel->Parse($sourcename)
or die "Could not open source Excel file $sourcename: $!";
my $storage_book;
foreach my $source_sheet_number (0 .. $source_book->{SheetCount}-1)
{
my $source_sheet = $source_book->{Worksheet}[$source_sheet_number];
print "--------- SHEET:", $source_sheet->{Name}, "\n";
# sanity checking on the source file: rows and columns should be sensible
next unless defined $source_sheet->{MaxRow};
next unless $source_sheet->{MinRow} <= $source_sheet->{MaxRow};
next unless defined $source_sheet->{MaxCol};
next unless $source_sheet->{MinCol} <= $source_sheet->{MaxCol};
foreach my $row_index ($source_sheet->{MinRow} .. $source_sheet->{MaxRow})
{
foreach my $col_index ($source_sheet->{MinCol} .. $source_sheet->{MaxCol})
{
my $source_cell = $source_sheet->{Cells}[$row_index][$col_index];
if ($source_cell)
{
print "( $row_index , $col_index ) =>", $source_cell->Value, "\n";
if ($source_cell->{Type} eq 'Numeric')
{
$storage_book->{$source_sheet->{Name}}->{$row_index}->{$col_index} = $source_cell->Value*2;
}
else
{
$storage_book->{$source_sheet->{Name}}->{$row_index}->{$col_index} = $source_cell->Value;
} # end of if/else
} # end of source_cell check
} # foreach col_index
} # foreach row_index
} # foreach source_sheet_number
print "Perl recognized the following data (sheet/row/column order):\n";
print Dumper $storage_book;
my $dest_book = Spreadsheet::WriteExcel->new("$destname")
or die "Could not create a new Excel file in $destname: $!";
print "\n\nSaving recognized data in $destname...";
foreach my $sheet (keys %$storage_book)
{
my $dest_sheet = $dest_book->addworksheet($sheet);
foreach my $row (keys %{$storage_book->{$sheet}})
{
foreach my $col (keys %{$storage_book->{$sheet}->{$row}})
{
$dest_sheet->write($row, $col, $storage_book->{$sheet}->{$row}->{$col});
} # foreach column
} # foreach row
} # foreach sheet
$dest_book->close();
print "done!\n";
|
注目すべき点は、プログラムにおいて、データの抽出部分と保存部分が強制的に分離されていることです。これら2つの操作は同時に行うことも可能ですが、この分離によって、バグ・フィックスや改良が容易になります。
前述の問題は、XML::Excel CPANモジュールを使用した方がはるかにうまく解決できますが、XMLをExcelに変換し直す特別のコンバーターを作成する必要があります。また、この方法でのデータのインポートには、DBD::ExcelモジュールによるDBIインターフェースを使用することもできます。なお、Spreadsheet::ParseExcel::SaveParserモジュールにはSpreadsheet::ParseExcelが付属しており、2つのExcelファイル間の変換が可能とされていますが、ドキュメンテーションやサンプルは付属していません。私のWebサイト(参考文献を参照)には、SaveParserの使用例が掲載されています。ただし、これは試験的なものであり、うまく機能しない可能性も高いことをご了承ください。
まとめ
Windows環境では、Excelがマシンにインストールされていない場合を除いて、Win32::OLEモジュールの使用をお勧めします。現時点では、Win32::OLEがExcelデータを取得する最も簡単な方法ですが、Spreadsheet::WriteExcelおよびSpreadsheet::ParseExcelモジュールもそれに近づきつつあります。
UNIX、特にLinux環境でプログラムからExcelデータにアクセスする場合は、Spreadsheet::WriteExcelおよびSpreadsheet::ParseExcelモジュールを使用することをお勧めします。ただし、これらは最新のモジュールですから、安定性が必要な場合には完璧とは言えないことを予めご了承ください。
また、GnumericやStarOffice(参考文献を参照)のようなパッケージを検討するのもよいでしょう。これらは、完全なGUIインターフェースとExcelファイルのインポート/エクスポート機能を備えており、プログラムからExcelデータへのアクセスを必要としないかぎり役立ちます。私自身も両方のアプリケーションを使用していますが、日常の作業には申し分ないと感じています。
参考文献
著者について  | |  | Teodor Zlatanovは1999年にボストン大学を卒業し、コンピューター・エンジニアリングで学位を取得しています。1992年以来プログラマーとして働いており、Perl、Java、C、C++などの言語を使用してきています。関心を持っている領域としてはオープン・ソース作業、Perl、テキスト構文解析、3層のクライアント/サーバー・データベース・アーキテクチャー、Unixのシステム管理などです。助言や間違いの指摘を歓迎しています。連絡先はtzz@bu.eduです。 |
記事の評価
|