简体   繁体   中英

perl Excel::Writer::XLSX copy formula to several subsequent cells of a row

in EXCEL you can write a formula, mark the cell and use the "+" at the right bottom of the marking to copy the formula to subsequent cells of the same row or column. The relative references are adjusted automatically.

Is there a way to get this done with Excel::Writer::XLSX?

I've written a basic implementation that can do single cells both down and to the right. You can use it as an example to work out how to do more complex stuff. It filters out fixed cells with $ because those cannot be incremented. It doesn't work for pulling up or left.

I've added a test to explain what the output looks like. Explanation below.

use strict;
use warnings;
use feature 'say';

# caveat: does not check if the cells are inside of quotes!
sub pull_formula {
    my ($formula, $from, $to) = @_;

    my ($col_from, $row_from) = $from =~ m/([A-Z]+)([0-9]+)/;
    my ($col_to,   $row_to)   = $to   =~ m/([A-Z]+)([0-9]+)/;

    my @formulas;

    # determine direction, do we go down or right
    if ($col_from eq $col_to) {

        # pulling down, increment numbers
        foreach my $n (++$row_from .. $row_to) {
            $formula =~ s{
                ([A-Z]+)
                ([0-9]+)
            }{$1 . ($2+1)}gex;
            push @formulas, $formula;
        }
    }
    else {
        # pulling right, increment letters
        foreach my $n (++$col_from .. $col_to) {
            $formula =~ s{
                (?<!       \$)       # not preceded by a dollar
                (?<col>    [A-Z]+)   # column
                (?<dollar> \$?)      # optional dollar sign for row
                (?<row>    [0-9]+)   # row
            }{
                do { my $c = $+{col}; ++$c }
                    . $+{dollar}
                    . $+{row}
            }gex;
            push @formulas, $formula;
        }
    }

    return \@formulas;
}

use Test::More;

is_deeply pull_formula('=B5 + C5', 'A5', 'A8'), [
    '=B6 + C6',
    '=B7 + C7',
    '=B8 + C8',
];

is_deeply pull_formula('=MIN(B5:B$999)', 'A5', 'A8'), [
    '=MIN(B6:B$999)',
    '=MIN(B7:B$999)',
    '=MIN(B8:B$999)',
];

is_deeply pull_formula('=VLOOKUP(B3,B$2:E$7,2,FALSE)', 'B3', 'B5'), [
    '=VLOOKUP(B4,B$2:E$7,2,FALSE)',
    '=VLOOKUP(B5,B$2:E$7,2,FALSE)',
];

is_deeply pull_formula('=SUM(Y1:Y10)', 'Y11', 'AB11'), [
    '=SUM(Z1:Z10)',
    '=SUM(AA1:AA10)',
    '=SUM(AB1:AB10)',
];

done_testing;

The idea is to call the function with the formula, and the range you want to fill as two arguments. If you have access to the worksheet, you could of course do this all with just a single cell arg and how many more you want to fill in. But this is easier.

We first separate the cells into cols and rows, and then decide if we want to increment the numbers (rows) or the letters (cols).

Numbers are simpler, because we can use maths with them. The general idea is to iterate over the formula, find every occurrence of a cell, and replace it with the next higher one. The regular expression substitution we use has the /x flag to add formatting in the pattern, the /e flag to treat the replacement as Perl code, and the /g flag to do it globally, meaning repeating as often as possible.

For letters the pattern is more tricky. We need to not get the letters that are preceded by a dollar sign $ , so there's a negative lookbehind at the start. I've used named capture groups ( (?<name>pattern) ) to make the captures easier to read. Note all the whitespace is ignored, it comes from the /x modifier. For the letters it doesn't matter if we have a dollar in front of the number, but we have to capture this. If there is no dollar, $+{dollar} will be an empty string.

Perl increments letters in the same way Excel columns work. AA comes after Z , which is convenient. But Perl only does this with ranges and with the increment operator ++ , which we cannot apply to capture variables, so we have to first assign that to a new variable. In order to do that inside of the replacement, we use a do block.

I've put all the patterns into an array and returned a reference, but of course you could write to the worksheet directly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM