使用perl解析xls文件

时间:2013-03-24 12:09:17

标签: perl parsing

我想使用perl解析xls文件。 xls文件遵循结构,这使得解析有点棘手;

      col1      col2
row1  School    1
row2  Dean      John
row3  No.stu.   55
row4  some irrelevant stuff 
row5  School2   2
row6  Dean      Tony 
row7  No. stu.  60 
row8  some irrelevant stuff

我希望实现的输出是:

      col1 col2 col3
row1 School Dean No.stu. 
row2 1      John  55
row3 2      Tony  60 

到目前为止,我一直在研究的模块是Spreadsheet::ParseExcel。任何其他模块可能会帮助我离开这里?的问候,

感谢@amon回复,它提供了部分解决问题的潜在方法。但作为一名perl初学者,我在消化代码方面遇到了很多困难。

解析部分从ROW:开始,我用的是什么?我真的不知道

my ($key, $val) = map {$worksheet->get_cell($row, $_)} $col_min .. $col_max;

我可以将其解释为Spreadsheet::ParseExcel文档中给出的内容:             

for my $row ( $row_min .. $row_max ) {

            for my $col ( $col_min .. $col_max ) {

            my $cell = $worksheet->get_cell( $row, $col );}

另外,在跳转到输出部分之前,我可以查看已解析的内容吗?比如说,无论如何打印出已经累积到表%data中的变量?我一直在努力奋斗。

真的很感谢你的帮助!

1 个答案:

答案 0 :(得分:2)

您可以使用Spreadsheet::ParseExcel来阅读您的文件。迭代所有行,并将前两个字段存储在散列中。在每四行,您可以将数据写入输出,并清除哈希:

# Adapted from the module documentation
use strict; use warnings;
use Spreadsheet::ParseExcel;

my ($infile, $outfile) = @ARGV;

my $parser   = Spreadsheet::ParseExcel->new();
my $workbook = $parser->parse($infile);

die $parser->error unless defined $workbook;

# select the first worksheet
my ($worksheet) = $workbook->worksheets();

# get bounds:
my ( $row_min, $row_max ) = $worksheet->row_range();
my ( $col_min, $col_max ) = $worksheet->col_range();

# assert that there are at least two fields per row:
$row_max - $row_min >= 1 or die "To few cells per row";

my %data; # accumulate data here

ROW:
for my $row ($row_min .. $row_max) {
  # discard every fourth row:
  if ($row - $row_min && ($row - $row_min) % 3 == 0) {
    ...; # write to output
    %data = (); # clear cache
    next ROW;
  }
  my ($key, $val) = map {$worksheet->get_cell($row, $_)} $col_min .. $col_max;
  $data{$key} = $val;
}

要编写电子表格,您可以使用Spreadsheet::WriteExcel。这看起来像

# from the module documentation
my $out_workbook  = Spreadsheet::WriteExcel->new($outfile);
my $out_worksheet = $out_workbook->add_worksheet;
...;
# write data inside our loop:
my @cols = qw/School Dean No.stu/;
for my $i (0 .. $#cols) {
  my $val = delete $data{$cols[$i]} // die "uninitialized value for $cols[$i]";
  $out_worksheet->write($row, $i, $val);
}
# do some error handling
if (my @keys = keys %data) {
  die "Unexpected field(s) [@keys] encountered";
}

对于定义的或运算符//,这需要perl5 v10或更高版本。


更新

对不起,我使用了一些构造而没有正确解释它们。

丢弃每一行

我可以从一个开始计数器。每次点击4,我都会跳过这一行并重置它。但是,我已经有一个行计数器,我用它来代替。我不知道第一行是0,因为$row_min可能是任何东西。所以我将行号$row - $row_min转置为实际的行数。它从零开始。

每隔四行,这个实际计数可以被三整除:

0 1 2 3 4 5 6 · · ·
      *     *

所以我可以使用模数运算符%。但是,0 % $n == 0对于所有$n都是正确的(零可以通过所有数字整除),所以我必须使用特殊情况零。我通过在执行可分性测试之前检查我们的计数不为零来执行此操作。除零以外的所有数字都是真的,所以我可以测试我们数字的真实性。这导致了测试

if ($row - $row_min && ($row - $row_min) % 3 == 0) { ... }

map表达式

map函数采用以下任一方法:

  • map EXPRESSION, LIST
  • map { BLOCK } LIST - 请注意块与列表之间缺少逗号。

它非常像一个漂亮的foreach循环:对于列表中的每个值,$_在我们的表达式中设置为该值。然后表达式返回一个记住的值。处理完列表中的所有项目后,map将返回表达式值的列表。

例如,这是一个map表达式,它对列表中的所有数字进行平方:

my @squares = map { $_ * $_ } 1 .. 10; # 1, 4, 9 16, .. 100

我使用map来获取行内的所有单元格值:我指定所有列的列表($col_min .. $col_max),map块获取该列中的单元格当前行。

所以map返回一个单元格列表,我将其分配给“左值”列表($key, $val)。列表分配使$key具有第一个的值,$val具有第二个单元格的值。

使用简单的foreach循环编写,这看起来像:

my @cells;
for my $col ($col_min .. $col_max) {
  push @cells, $worksheet->get_cell($row, $_);
}
my $key = shift @cells;
my $val = shift @cells;

查看您的数据结构

转储数据结构以进行调试的默认方法是使用Data::Dumper模块。如果要查看散列或数组,请确保将数据结构作为引用传递。 E.g:

use Data::Dumper;   # at the top of your script
warn Dumper \%data; # where ever you need the info

如果您需要更好的格式化,您可以随时编写自己的格式:

printf "Contents of %%data for row %d:\n", $row - $row_min;
for my $key (sort keys %data) {
  printf "%10s:%s\n", $key, $data{$key}
}

sort函数的这种用法将按字母顺序升序对其参数进行排序。