为什么在Perl的Spreadsheet :: WriteExcel模块中使用repeat_formula时,我的2D范围不会被3D范围所取代?

我需要通过Perl脚本创build一个dynamic的Excel公式。

我存储一个包含假范围的公式

my $stored_formula = $worksheet->store_formula('=MEDIAN(A1:A2)'); 

在脚本中,我计算了新的范围

 $formula_range = $mm_arr[$mmindx-1] . "!" . num2col(2+$form_off) . ((($serv-1)*5)+5) . ":" . num2col((2+31+$form_off)) . ((($serv-1)*5)+5); 

其中@mm_arr是一个数组("Jan", "Feb", …)num2col是我用来将列号转换为字母的函数。

下面一行是repeat_formula

 $worksheet->repeat_formula( (($serv-1)*5)+4, 1+$mmindx, $stored_formula, undef, 'A1:A2', $formula_range ); 

我期望得到:

 =median(Feb!K3:AH3) 

但是我得到:

 =median(K3:AH3) 

所以发现/replace是不知何故的工作,但我不能确定如何!

我究竟做错了什么?

首先,几点build议:

在你的expression式中有太多的括号,加上variables和函数的定义是我们看不到的,所以你的代码难以用视觉来parsing。 你也没有发布一个小的,自包含的脚本来展示这个问题,这意味着任何试图帮助你的人都必须build立一个testing脚本。 使其他人容易。

例如,您定义$formula_range可以重写为:

 my $formula_range = sprintf('%s!%s%d:%s%d', $mm_arr[$mmindx - 1], num2col(2 + $form_off), 5 + 5 * ($serv - 1), num2col(2 + 31 + $form_off), 5 + 5 * ($serv - 1), ); 

请记住,您可以使用Spreadsheet :: WriteExcel :: Utility提供的函数在单元格表示法和行/列索引之间进行转换。

问题似乎是,如果存储的公式不是以SheetName!Range的formsSheetName!Range ,那么replace就会replacereplace中的工作表名称,并且仅放入范围中。

原因似乎与Spreadsheet::WriteExcel::Formula的parsing器设置有关。 一个普通范围被parsing为一个range2d标记,而一个带有表单引用的范围被parsing为一个range3d标记。 稍后,在repeat_formula ,replace是在令牌上完成的。 从我所能得到的结果range2drange2d标记看起来像是'_ref2dA1:A10' 。 所以, '_ref2dA1:A2'被重复公式转换为'_ref2dFeb!A1:10' 。 但是,当它传递给Spreadsheet::WriteExcel::Formula::parse_tokens ,代码依然会根据前缀将其分类为range2d标记。 我收集了最后一次调用$parse_str .= $self->_convert_ref2d($token, $class); 然后把它变回'_ref2dA1:A10 。 我不确定这是否可以归类为一个错误,但是用户的POV肯定是意外的。

所以,解决方法是放入保证存在的工作表的名称。

这是一个testing脚本:

 #!/usr/bin/env perl use strict; use warnings; use Spreadsheet::WriteExcel; use Const::Fast; const my @MONTHS => qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); const my $WORKBOOK => 'test.xls'; my $book = Spreadsheet::WriteExcel->new($WORKBOOK) or die "Cannot open '$WORKBOOK': $!"; my %sheets = map {$_ => $book->add_worksheet($_)} Summary => @MONTHS; for my $m (@MONTHS) { for my $n (1 .. 10) { $sheets{ $m }->write_number($n - 1, 0, $n); } } my $formula = $sheets{Summary}->store_formula('=MEDIAN(Summary!A1:A2)'); for my $n (0 .. 1) { my $replacement = sprintf(q{'%s'!A1:A10}, $MONTHS[$n]); $sheets{Summary}->repeat_formula($n, 0, $formula, undef, 'Summary!A1:A2' => $replacement ); } $book->close or die "Error closing '$WORKBOOK': $!"; 

这里是一个截图:

截图显示正确的公式

store_formula()/repeat_formula()方法是使用Parse :: RecDescent的缓慢公式分析的历史解决方法。 它们仅用于replace简单和相似的范围。

将expression式从2D范围更改为3D范围不会起作用,因为表示每个公式的parsing二进制结构是非常不同的,不能用简单的replace来更改。

你可以在Spreadsheet :: WriteExcel中解决这个问题,但是我build议你使用Excel :: Writer :: XLSX这是一个API兼容替代Spreadsheet :: WriteExcel,并且不需要store_formula()/repeat_formula()加快重复的公式。

最后要说明的是,两个Excel写入器模块都带有:: Utility模块,用于范围生成。