Perl Excel模块(一)SpreadSheet::Read
简介
Spreadsheet::Read模块,是用来从excel表格中读取数据的模块。
这里需要注意的是Perl语言中的电子表格处理模块读和写是分开的。也就是说并没模块可以同时对同一个表格文件进行读写操作。
例子
#引入模块
use Spreadsheet::Read;
#读取一个.csv文件,与查规的逗号分割不同,这个文件是用分号分割的
my $book = ReadData ("test.csv", sep => ";");
my $book = ReadData ("test.sxc");
my $book = ReadData ("test.ods");
my $book = ReadData ("test.xls");
my $book = ReadData ("test.xlsx");
#解析一个句柄(参考下面的ReadData方法内容),这个句柄是一个xls文件
my $book = ReadData ($fh, parser => "xls");
#另一种打开文件的方式
Spreadsheet::Read::add ($book, "sheet.csv");
#打开excel中的第一个sheet表格
my $sheet = $book->[1];
#选择该sheet中的A3单元格。
my $cell = $book->[1]{A3};
my $cell = $book->[1]{cell}[1][3]; # 与上面相同的含义,仅仅是格式不同
# 面向对象的写法
my $book = Spreadsheet::Read->new ("file.csv");
my $sheet = $book->sheet (1);
my $cell = $sheet->cell ("A3");
my $cell = $sheet->cell (1, 3);
$book->add ("test.xls");
描述
Spreadsheet::Read整合所有的Spreadsheet读取数据的模块,他能够自动识别文件并选择不同的Spreadsheet模块来读取数据:比如,使用openoffice或者libreoffice时,他会调用Spreadsheet::ReadSXC模块;使用微软excel时,他会调用 Spreadsheet::ParseExcel, Spreadsheet::ParseXLSX, 或者Spreadsheet::XLSX模块;使用csv文件时,他会用 Text::CSV_XS 或Text::CSV_PP。这些都是read模块在后代调用的,无须担心。
数据结构
Spreadsheet::Read模块读取完数据将返回一个数组引用(也就是文章开头的$book
),这个引用结构象这样:
$book = [
# 数组中下标为0的变量,主要为excel中的sheet结构
{ sheets => 2,
sheet => {
"Sheet 1" => 1,
"Sheet 2" => 2,
},
parsers => [ {
type => "xls",
parser => "Spreadsheet::ParseExcel",
version => 0.59,
}],
error => undef,
},
# 下标1变量表示sheet1中的数据
{ parser => 0,
label => "Sheet 1",
maxrow => 2,
maxcol => 4,
cell => [ undef,
[ undef, 1 ],
[ undef, undef, undef, undef, undef, "Nugget" ],
],
attr => [],
merged => [],
active => 1,
A1 => 1,
B5 => "Nugget",
},
# 下标2的变量同理,依次类推
{ parser => 0,
label => "Sheet 2",
:
:
在上面代码例子中我们会看到数组引用中索引为1的变量(表示Sheet 1的变量)中,同一个单元格的内容同时存在于cell二维数组中,和一个使用excel单元格表示法的索引中,例如A1。这两种变量可以使用三个函数相互转换:cell2cr (), cr2cell (), 和 col2label ()。(A1这种表示在官方文档中称作control简写为cr,cell称作cell,2即英文to)。
control和cell间的区别是,cell仅仅包括了单元格中的值,而control中还包含了单元格的格式信息。
出了上述两点外,我们还可以从$book中提取出某一个sheet的散列变量:
my %sheet2 = %{$book->[$book->[0]{sheet}{"Sheet 2"}]};
带格式的与不带格式的
cell和control的区别就在于单元格是否存在格式数据,比如日期格式、时间格式、百分比等。由于SpreadSheet::Read模块中excel文件读取的具体实现,依赖于封装的其他模块库(在描述一节中我们已经提到过),因此对于control中的数据是否带格式,根据实际调用的模块存在一定区别。例如,csv(逗号分割)文件,它的cell和control中的数据是相等的。现在,我们使用一个例子来展示带格式和不带格式的数据间的区别:
use Spreadsheet::Read;
my $file = "files/example.xlsx";
my $workbook = Spreadsheet::Read->new ($file);
my $info = $workbook->[0];
say "Parsed $file with $info->{parser}-$info->{version}";
my $sheet = $workbook->sheet (1);
say join "\t" => "Formatted:", $sheet->row (1);
say join "\t" => "Unformatted:", $sheet->cellrow (1);
下面是例子代码的返回值:
#文件files/example.xlsx 使用 Spreadsheet::ParseXLSX-0.27 的结果
Formatted: 8-Aug Foo & Barr < Quux
Unformatted: 39668 Foo & Barr < Quux
#文件files/example.xlsx 使用 Spreadsheet::XLSX-0.15 的结果
Formatted: 39668 Foo & Barr < Quux
Unformatted: 39668 Foo & Barr < Quux
函数与方法
new
my $book = Spreadsheet::Read->new (...) or die $@;
该方法的参数请参考ReadData方法。如果new方法参数为空,那么可以通过$book变量调用add方法增加参数:
my $book = Spreadsheet::Read->new ();
$book->add ("file.csv");
$book->add ("file.cslx");
ReadData
my $book = ReadData ($source [, option => value [, ... ]]);
my $book = ReadData ("file.csv", sep => ',', quote => '"');
my $book = ReadData ("file.xls", dtfmt => "yyyy-mm-dd");
my $book = ReadData ("file.ods");
my $book = ReadData ("file.sxc");
my $book = ReadData ("content.xml");
my $book = ReadData ($content);
my $book = ReadData ($content, parser => "xlsx");
my $book = ReadData ($fh, parser => "xlsx");
my $book = ReadData (\$content, parser => "xlsx");
该方法可以将文件、字符串或者IO句柄作为参数。
关于IO句柄,可以是调用File::Temp时得到的临时文件,或者调用IO::Scalar时得到一个IO句柄。
Spreadsheet::ReadSXC模块保存sheet表的顺序,需要0.20版本。
关于$content和\$content的选择,需要有使用者自己选择。
关于ReadData支持的参数:
parse:
手动强制将数据解析成想要的格式,比如”csv”, “prl” (或 “perl”), “sc” (或 “squirelcalc”), “sxc” (或 “oo”,”ods”, “openoffice”, “libreoffice”) ,”xls” (或 “excel”), 和”xlsx” (或”excel2007″).
如果打算选择读取一个IO句柄,而不是文件,请一定要使用这个参数。
关于格式的解析,Spreadsheet::ReadSXC模块会自己判断并搜索调用的模块,比如csv格式的文件有两种模块——Text::CSV_XS 或Text::CSV_PP可以调用。如果你认为其中一种比较好用,希望一直使用这个模块,可以直接在环境变量中设置:
env SPREADSHEET_READ_CSV=Text::CSV_PP
cells:是否生成命名的索引变量,例如A1。默认为true。
rc:是否生成{cell}[c][r]索引变量。默认为true。
attr:是否生成{attr}[c][r]索引变量,默认为false。参考“单元格属性”一节。
clip:如果设置了,模块会删除表格末尾(行末尾、列末尾)没有数据的单元格。如果一个sheet表没有数据,且这个选项设置为true,那么模块会自动跳过这个sheet表,不进行读取操作。
strip:如果设置了,会清除每个区域内首尾空格。
strip leading strailing
----- ------- ---------
0 n/a n/a
1 strip n/a
2 n/a strip
3 strip strip
pivot:如果设置了,会调换单元格的行和列,比如我们有一个这样的表格:
A1 B1 C1 E1
A2 C2 D2
A3 B3 C3 D3 E3
#当我们启用了pivot后,会得到:
A1 A2 A3
B1 B3
C1 C2 C3
D2 D3
E1 E3
sep:设置csv文件中的分隔符,默认为“,”。
quote:设置csv文件中引号符号,默认“””。
dtfmt:设置日期格式,默认为“yyyy-mm-dd”。
debug:开启debug调试
passwd:当excel设置了密码,可以用此参数提交密码以读取数据。
以下几个函数很简单,主要是单元格索引间的转换,仅列例子:
col2label
my $col_id = col2label (col);
my $col_id = $book->col2label (col); # OO
my $id = col2label ( 4); # $id now "D"
my $id = col2label (28); # $id now "AB"
cr2cell
my $cell = cr2cell (col, row);
my $cell = $book->cr2cell (col, row); # OO
my $cell = cr2cell ( 4, 14); # $cell now "D14"
my $cell = cr2cell (28, 4); # $cell now "AB4"
cell2cr
my ($col, $row) = cell2cr ($cell);
my ($col, $row) = $book->cell2cr ($cell); # OO
my ($col, $row) = cell2cr ("D14"); # returns ( 4, 14)
my ($col, $row) = cell2cr ("AB4"); # returns (28, 4)
row
my @row = row ($sheet, $row)
my @row = Spreadsheet::Read::row ($book->[1], 3);
my @row = $book->row ($sheet, $row); # OO
得到一行带格式的数据(类似于”$sheet->{A3} .. $sheet->{G3}”)。注意$row索引是从0开始的。
cellrow
my @row = cellrow ($sheet, $row);
my @row = Spreadsheet::Read::cellrow ($book->[1], 3);
my @row = $book->cellrow ($sheet, $row); # OO
得到一行不带格式的数据(类似于$sheet->{cell}[1][3] ..$sheet->{cell}[7][3])。注意索引是从0开始的。
rows
my @rows = rows ($sheet);
my @rows = Spreadsheet::Read::rows ($book->[1]);
my @rows = $book->rows (1); # OO
将{cell}变量中的索引[column][row]转换为[row][column]。需要注意的是,返回值的索引是从0开始的,而原始 {cell} 的索引是从1开始的。
parses
parses ($format);
Spreadsheet::Read::parses ("CSV");
$book->parses ("CSV"); # OO
根据要求的格式($format)解析文件。
Version
my $v = Version ()
my $v = Spreadsheet::Read::Version ()
my $v = Spreadsheet::Read->VERSION;
my $v = $book->Version (); # OO
返回Spreadsheet::Read的版本
sheets
my $sheets = $book->sheets; #返回sheets的数量
my @sheets = $book->sheets; #返回sheets的label(标签)
sheet
my $sheet = $book->sheet (1); # OO
my $sheet = $book->sheet ("Foo"); # OO
将一个sheet对象从book中提取出来,参数为sheet的下标索引或label名称。如果没有找到sheet将返回undef。请不要将excel中sheet命名为1到sheet最大数量的数值间的任意数字。
当时用label名称作为参数时,方法会首先查找控制结构中的label列表。如果没有找到,它会继续查找文件中实际的label名称并返回第一个符合的对象。
如果label返回值,那么它将是”Spreadsheet::Read::Sheet”类的对象实例。
add
my $book = ReadData ("file.csv");
Spreadsheet::Read::add ($book, "file.xlsx");
my $book = Spreadsheet::Read->new ("file.csv");
$book->add ("file.xlsx"); # OO
Sheets中的方法
maxcol
my $col = $sheet->maxcol;
返回最后一列存在有效数据的列索引,索引是从1开始计数的。
maxrow
my $row = $sheet->maxrow; #类似maxcol
cell
my $cell = $sheet->cell ("A3"); #返回带有格式的单元格值
my $cell = $sheet->cell (1, 3); #返回无格式的单元格值
attr
my $cell = $sheet->attr ("A3");
my $cell = $sheet->attr (1, 3);
返回单元格的属性信息,仅在ReadData方法中attr启用时生效。
col2label
my $col_id = $sheet->col2label (col);
my $id = $sheet->col2label ( 4); # $id now "D"
my $id = $sheet->col2label (28); # $id now "AB"
cr2cell
my $cell = $sheet->cr2cell (col, row);
my $cell = $sheet->cr2cell ( 4, 14); # $cell now "D14"
my $cell = $sheet->cr2cell (28, 4); # $cell now "AB4"
cell2cr
my ($col, $row) = $sheet->cell2cr ($cell);
my ($col, $row) = $sheet->cell2cr ("D14"); # returns ( 4, 14)
my ($col, $row) = $sheet->cell2cr ("AB4"); # returns (28, 4)
col
my @col = $sheet->column ($col);
得到一列带格式的单元格值,从0开始索引。
cellcolumn
my @col = $sheet->cellcolumn ($col); #同col得到不带格式的值
row、cellrow——两个方法类似于上面col和cellcolumn
my @row = $sheet->row ($row);
my @row = $sheet->cellrow ($row);
rows
my @rows = $sheet->rows ();
将{cell}变量中的索引[column][row]转换为[row][column]。需要注意的是,返回值的索引是从0开始的,而原始 {cell} 的索引是从1开始的。
label
my $label = $sheet->label;
$sheet->label ("New sheet label");
设置一个为sheet新的label。需要注意,这不会改变控制结构中的label值。
active
my $sheet_is_active = $sheet->active;
判断当前sheet是否为活动的,是返回1,不是返回0。
读取CSV文件时的注意事项
解析CSV时,如果参数是一个文件,且没有设置sep、quote属性,模块会自动的从文件中获取第一行信息,来检测分隔符和引号的表示方法。Text::CSV_XS(或Text::CSV_PP)能够自动检测,并且以第一个遇到的回车\r来作为第一行的结尾提取。
CSV解析IO句柄时,如果不是默认的逗号分隔符及双引号,需要自己去设置sep、quote属性。
当CSV解析出错时,会返回标准错误信息。同时,调用$csv->error_diag方法会返回一个匿名列表$ss->[0]{error}存储错误信息。
my $ss = ReadData ("bad.csv");
$ss->[0]{error} and say $ss->[0]{error}[1];
CSV没有sheets label信息,默认label时它的文件名称。当然你也可以手动修改:
my $ss = Spreadsheet::Read->new ("/some/place/test.csv", label => "Test")
单元格属性
如果ReadData中attr属性设置为true:
my $book = ReadData ("book.xls", attr => 1);
my $book = Spreadsheet::Read->new ("book.xlsx", attr => 1);
设置完成后,模块会读取每个单元格的属性,就像这样:
{ label => "Sheet 1",
maxrow => 5,
maxcol => 2,
cell => [ undef,
[ undef, 1 ],
[ undef, undef, undef, undef, undef, "Nugget" ],
],
attr => [ undef,
[ undef, {
type => "numeric",
fgcolor => "#ff0000",
bgcolor => undef,
font => "Arial",
size => undef,
format => "## ##0.00",
halign => "right",
valign => "top",
uline => 0,
bold => 0,
italic => 0,
wrap => 0,
merged => 0,
hidden => 0,
locked => 0,
enc => "utf-8",
}, ]
[ undef, undef, undef, undef, undef, {
type => "text",
fgcolor => "#e2e2e2",
bgcolor => undef,
font => "Letter Gothic",
size => 15,
format => undef,
halign => "left",
valign => "top",
uline => 0,
bold => 0,
italic => 0,
wrap => 0,
merged => 0,
hidden => 0,
locked => 0,
enc => "iso8859-1",
}, ]
merged => [],
A1 => 1,
B5 => "Nugget",
},
注意,attr变量的索引是从1开始计数的。
属性中所有的字段都被excel文件支持,如果你要用模块解析其他类型的文件那有些字段将会失效(这句话意思就是,老老实实拿着模块处理电子表格就行了)。
需要注意,如果一列或一行被隐藏,那么其中单元格的隐藏属性也是开启的。
如果你要获取单元格的属性:
my $attr = $book[1]{attr}[1][3]; # Direct structure
my $attr = $book->sheet (1)->attr (1, 3); # Same using OO
my $attr = $book->sheet (1)->attr ("A3"); # Same using OO
如果你想要的得到单元格字体:
my $font = $book[1]{attr}[1][3]{font};
my $font = $book->sheet (1)->attr (1, 3)->{font};
my $font = $book->sheet (1)->attr ("A3")->font;
关于合并单元格属性:
merge对大多数人来说,没有多大的用处。如果感兴趣可以阅读模块文档中Merged cells一节。