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

时间:2012-05-04 16:38:33

标签: perl excel

我需要通过Perl脚本创建动态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)

所以find / replace以某种方式工作,但我无法确定如何确定!

我做错了什么?

2 个答案:

答案 0 :(得分:2)

首先,提出一些建议:

在表达式中有太多括号,加上变量和函数,我们看不到它们的定义会使您的代码难以直观地解析。您也没有发布显示问题的小型自包含脚本,这意味着任何试图帮助您的人都必须设置测试脚本。让其他人轻松。

例如,您可以按如下方式重写您定义$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形式,那么替换会切换替换中的工作表名称,并且只会放在范围内。

这个原因似乎与Spreadsheet::WriteExcel::Formula中的解析器设置有关。普通范围被解析为range2d标记,而具有图表引用的范围被解析为range3d标记。稍后,在repeat_formula中,替换将在令牌上完成。根据我的收集,range2d令牌看起来像'_ref2dA1:A10'。因此,'_ref2dA1:A2'会在重复公式中转换为'_ref2dFeb!A1:10'。但是,当它传递给Spreadsheet::WriteExcel::Formula::parse_tokens时,代码仍会根据前缀将其分类为range2d令牌。我收集了最后的电话$parse_str .= $self->_convert_ref2d($token, $class);,然后将其转回'_ref2dA1:A10。我不确定这是否可归类为错误,但用户的POV肯定是意外的。

因此,解决方案是输入保证存在的工作表的名称。

这是一个测试脚本:

#!/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': $!";

这是截图:

Screenshot showing correct formulas

答案 1 :(得分:1)

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

将表达式从2D范围更改为3D范围是行不通的,因为表示每个公式的解析二进制结构非常不同,并且无法通过简单替换进行更改。

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

最后一点,两个Excel编写器模块都带有::Utility模块用于范围生成。