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::ParseExcelSpreadsheet::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 &amp; Barr &lt; Quux
     Unformatted:    39668   Foo &amp; Barr &lt; 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一节。

标签