0

我想使用 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?我已经挣扎了一段时间。

非常感谢您的所有帮助!

4

1 回答 1

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模块。如果您想查看哈希或数组,请确保将数据结构作为引用传递。例如:

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函数的这种用法将按字母升序对其参数进行排序。

于 2013-03-24T13:04:41.007 回答