Excelファイルの種類
- 何だかんだ言っても、一番メジャーな表計算ソフトと言えば、やはり「Microsoft Excel」でしょう。データを下さいと頼めば、所有確認など無しにExcelのデータが送られてくることも多いです。すると自然に、もらったExcelファイルからデータを読み込んで、あれこれしたいという話が出てきます。
- 今回はPerlでExcelデータを読むことについて扱いますが、対象データについては「1byte系」で構成されたものに限定させて頂きます(*1)。
- Excelにはファイル(拡張子の種類)が何種類かあります。代表的なものを見ると、一つはExcel 97-2003方式の「.xls」ファイル。もう一つは Excel 2007以降の「.xlsx」ファイルです。前者はバイナリファイル。後者はzip圧縮したXMLファイルです。
- もちろん互換性の点から、新しい2007以降のExcelでも「.xls」のファイルを読むことができるので、基本的な機能(*2)しか使っていないのであれば「.xls」のフォーマットでデータを扱うのがトラブル回避に繋がると、普通は思うでしょう。
- ところが、Excel 2007以降、VBAマクロを含んだファイルには「.xlsm」が使われるようになっています。データの形式は「.xlsx」と同じですが、その目的はマクロウイルスへの事前警戒にあるため、マクロを含んでいてもファイル名が変わらない「.xls」は、そろそろ排除される傾向にあります。
- こういった事情から、今回は「.xls」と「.xlsx」を対象にPerlからの読み込みを検討しますが、もともと「.xls」に対して作成したスクリプトをどうやって「.xlsx」に対応させるか、ソースの流用も意識して説明したいと思います。
xlsファイルの読み込み - Spreadsheet::ParseExcel
- xlsファイルからデータを読み込むには、CPANモジュールの Spreadsheet::ParseExcel を使います。このCPANモジュールは ActivePerl や Strawberry Perl にて標準ではなく追加モジュールの扱いです。使用環境に入っていない場合、モジュールを追加してください。
- 例として、2枚のワークシートに下記のデータが書かれているxlsファイル「test.xls」があるとします。
ワークシート「Sheet1」のデータ。有効データ範囲に罫線あり。左上の空白セルが「A1」/ [0,0]。
ワークシート「Sheet2」のデータ。有効データ範囲に罫線無し。左上の空白セルが「A1」/ [0,0]。
- 上記のExcelを読むためのコーディングですが、例を見た方が早いと思うのでリストを示します。リスト中の青文字部が、ParseExcel の機能を使った記述です。ワークシート毎にセルの範囲をチェック後、各セルの値を読み込んで表示します。
#!/usr/bin/perl -w
use Spreadsheet::ParseExcel;
use strict;
{
my $FileName = 'test.xls'; # テスト用Excel(xls)ファイル名
my $ExcelObj = Spreadsheet::ParseExcel->new(); # ParseExcelのオブジェクト定義
my $Book = $ExcelObj->parse($FileName); # xlsファイル読み込み/book扱い
for my $Sheet ($Book->worksheets()) { # worksheetオブジェクト取得
&GetValuesFromSheet($Sheet); # worksheetデータ取得へ
}
}
sub GetValuesFromSheet { # wroksheetデータ取得
my ($Sheet) = @_;
print "Sheet name : ",$Sheet->get_name(),"\n"; # worksheet名表示
my ($Rmin, $Rmax) = $Sheet->row_range(); # 行のデータ範囲(最小,最大)
my ($Cmin, $Cmax) = $Sheet->col_range(); # 列のデータ範囲(最小,最大)
for (my $row=$Rmin; $row<=$Rmax; $row++) { # rowは行番号
for (my $col=$Cmin; $col<=$Cmax; $col++) { # colは列番号
my $Cell = $Sheet->get_cell($row,$col); # Cellオブジェクト取得
if (defined($Cell)) {
print " CellValue[$row,$col] = ",$Cell->value(),"\n"; # 値の取得/表示
} else {
print " CellValue[$row,$col] = undefined\n";
}
}
}
}
|
- 実際に実行した結果が下記になります。値が無くても罫線等の属性があれば、get_cell(行,列) はオブジェクトを返しますが、完全に空白のセルではundefを返していることがわかります。この点は少し注意です。
Sheet name : Sheet1
CellValue[1,1] = Sheet1:11:Toride
CellValue[1,2] = Sheet1:12:Tenodai
CellValue[1,3] = Sheet1:13:Abiko
CellValue[2,1] = Sheet1:21:Kashiwa
CellValue[2,2] =
CellValue[2,3] = Sheet1:23:Matsudo
CellValue[3,1] = Sheet1:31:Kitasenjyu
CellValue[3,2] = Sheet1:32:Mikawashima
CellValue[3,3] =
CellValue[4,1] =
CellValue[4,2] =
CellValue[4,3] =
CellValue[5,1] =
CellValue[5,2] = Sheet1:52:Nippori
CellValue[5,3] = Sheet1:53:Ueno
Sheet name : Sheet2
CellValue[1,1] = Sheet2:11:Tsujido
CellValue[1,2] = Sheet2:12:Fijisawa
CellValue[1,3] = Sheet2:13:Ofuna
CellValue[2,1] = Sheet2:21:Totsuka
CellValue[2,2] = undefined
CellValue[2,3] = Sheet2:23:Yokohama
CellValue[3,1] = undefined
CellValue[3,2] = Sheet2:32:Kawasaki
CellValue[3,3] = Sheet2:33:Shinagawa
CellValue[4,1] = undefined
CellValue[4,2] = undefined
CellValue[4,3] = undefined
CellValue[5,1] = undefined
CellValue[5,2] = Sheet2:52:Shinbashi
CellValue[5,3] = Sheet2:53:Tokyo
|
xlsx / xlsmファイルの読み込み - Spreadsheet::ParseXLSX
- xlsx 又は xlsmファイルからデータを読み込むには、Spreadsheet::ParseXLSX を使用します。こちらも追加モジュールになるので、使用環境に入っていない場合、モジュールを追加してください。また、PareseXLSX は PareseExcel のアダプターとして動作するので、環境としては両方のモジュールが必要になります。
- xlsxファイルを読み込むためのCPANモジュールは他にもありますが、もともとxlsファイル用としてParseExcelを使っていたスクリプトを流用する場合、ParseXLSXが最も簡単だと考えています。
- 下記に ParseXLSX を使用したリストを示します。ほとんど ParseExcel のコードと変わりませんが、変更部を青文字にしてあります。use文とオブジェクト定義を変更するだけですね。
#!/usr/bin/perl -w
use Spreadsheet::ParseXLSX;
use strict;
{
my $FileName = 'test.xlsx'; # テスト用Excel(xlsx)ファイル名
my $ExcelObj = Spreadsheet::ParseXLSX->new(); # ParseXLSXのオブジェクト定義
my $Book = $ExcelObj->parse($FileName); # xlsxファイル読み込み/book扱い
for my $Sheet ($Book->worksheets()) { # worksheetオブジェクト取得
&GetValuesFromSheet($Sheet); # worksheetデータ取得へ
}
}
sub GetValuesFromSheet { # wroksheetデータ取得
my ($Sheet) = @_;
print "Sheet name : ",$Sheet->get_name(),"\n"; # worksheet名表示
my ($Rmin, $Rmax) = $Sheet->row_range(); # 行のデータ範囲(最小,最大)
my ($Cmin, $Cmax) = $Sheet->col_range(); # 列のデータ範囲(最小,最大)
for (my $row=$Rmin; $row<=$Rmax; $row++) { # rowは行番号
for (my $col=$Cmin; $col<=$Cmax; $col++) { # colは列番号
my $Cell = $Sheet->get_cell($row,$col); # Cellオブジェクト取得
if (defined($Cell)) {
print " CellValue[$row,$col] = ",$Cell->value(),"\n"; # 値の取得/表示
} else {
print " CellValue[$row,$col] = undefined\n";
}
}
}
}
|
- 実行結果はParseExcelと全く同じなので割愛いたします。
- 上記リストを見るとわかるように、変更点は $ExcelObj を取得するパーサーを変えているだけなので、データ読み込みに限ると、ParseExcelで作成したスクリプトを xls と xlsx の両対応に変更するのは容易です。ParseXLSXは、parse()メソッドだけが独自ですが、parse後のデータ処理については、ParseExcelのメソッドをそのまま使っているからです。
- ただし冒頭でも書きましたが、今回のレポートは日本語のようなマルチバイトデータが混ざったケースには対応していません。あくまで1バイトのデータでのみ構成されたファイルが対象です。
- どうもParseXLSXのparse()メソッドに問題があるらしく、日本語を含んだデータを読み出すと、振り仮名と思われるデータが一緒にくっついてきます。デリミタっぽいものもないためローカルで処理できず、恐らくParseXLSXを解析してバグ修正版を作成し、ローカルモジュールとして自分のスクリプトから参照させるような真似をしなければならないかも...しれません。
- このあたりの問題も対処判明したらレポートしたいと思います。
(2015/10/21追記)
少し荒っぽいですがどうにかしました。こちら「PerlでExcelデータを読む:日本語処理」を参照下さい。
|