2

我需要通过 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);

where@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)

所以查找/替换以某种方式工作,但我无法确定如何!

我究竟做错了什么?

4

2 回答 2

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提供的函数在单元格符号和行/列索引之间进行转换。

问题似乎是,如果存储的公式不在 formSheetName!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': $!";

这是一个屏幕截图:

显示正确公式的屏幕截图

于 2012-05-04T19:41:43.297 回答
1

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

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

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

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

于 2012-05-04T22:45:31.690 回答