Skip to content

Commit

Permalink
Add support for custom table total functions.
Browse files Browse the repository at this point in the history
  • Loading branch information
jmcnamara committed Sep 15, 2023
1 parent 29b76ef commit 466268e
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 32 deletions.
30 changes: 27 additions & 3 deletions lib/Excel/Writer/XLSX/Package/Table.pm
Expand Up @@ -229,11 +229,20 @@ sub _write_table_column {
push @attributes, ( dataDxfId => $col_data->{_format} );
}

if ( $col_data->{_formula} ) {
if ( $col_data->{_formula} || $col_data->{_custom_total} ) {
$self->xml_start_tag( 'tableColumn', @attributes );

# Write the calculatedColumnFormula element.
$self->_write_calculated_column_formula( $col_data->{_formula} );

if ($col_data->{_formula}) {
# Write the calculatedColumnFormula element.
$self->_write_calculated_column_formula( $col_data->{_formula} );
}

if ($col_data->{_custom_total}) {
# Write the totalsRowFormula element.
$self->_write_totals_row_formula( $col_data->{_custom_total} );
}


$self->xml_end_tag( 'tableColumn' );
}
Expand Down Expand Up @@ -290,6 +299,21 @@ sub _write_calculated_column_formula {
}


##############################################################################
#
# _write_totals_row_formula()
#
# Write the <totalsRowFormula> element.
#
sub _write_totals_row_formula {

my $self = shift;
my $formula = shift;

$self->xml_data_element( 'totalsRowFormula', $formula );
}


1;


Expand Down
53 changes: 36 additions & 17 deletions lib/Excel/Writer/XLSX/Worksheet.pm
Expand Up @@ -5015,6 +5015,7 @@ sub add_table {
_name => 'Column' . $col_id,
_total_string => '',
_total_function => '',
_custom_total => '',
_formula => '',
_format => undef,
_name_format => undef,
Expand All @@ -5027,14 +5028,16 @@ sub add_table {
if ( my $user_data = $param->{columns}->[ $col_id - 1 ] ) {

# Map user defined values to internal values.
if (defined $user_data->{header} && $user_data->{header} ne "") {
if ( defined $user_data->{header}
&& $user_data->{header} ne "" )
{
$col_data->{_name} = $user_data->{header};
}

# Excel requires unique case insensitive header names.
my $name = $col_data->{_name};
my $key = lc $name;
if (exists $seen_names{$key}) {
my $key = lc $name;
if ( exists $seen_names{$key} ) {
carp "add_table() contains duplicate name: '$name'";
return -1;
}
Expand Down Expand Up @@ -5065,23 +5068,38 @@ sub add_table {

# Handle the function for the total row.
if ( $user_data->{total_function} ) {
my $function = $user_data->{total_function};
my $formula = '';

# Massage the function name.
$function = lc $function;
$function =~ s/_//g;
$function =~ s/\s//g;
my $function = $user_data->{total_function};
$function = 'countNums' if $function eq 'count_nums';
$function = 'stdDev' if $function eq 'std_dev';

my %subtotals = (
average => 101,
countNums => 102,
count => 103,
max => 104,
min => 105,
stdDev => 107,
sum => 109,
var => 110,
);

$function = 'countNums' if $function eq 'countnums';
$function = 'stdDev' if $function eq 'stddev';
if ( exists $subtotals{$function} ) {
$formula =
_table_function_to_formula( $function,
$col_data->{_name} );

$col_data->{_total_function} = $function;
}
else {
$formula = $function;
$formula =~ s/^=//;
$col_data->{_custom_total} = $formula;
$function = 'custom';
}

my $formula = _table_function_to_formula(
$function,
$col_data->{_name}

);
$col_data->{_total_function} = $function;

my $value = $user_data->{total_value} || 0;

Expand Down Expand Up @@ -5149,8 +5167,8 @@ sub add_table {

# Store the filter cell positions for use in the autofit calculation.
if ( $param->{autofilter} ) {
for my $col ($col1 .. $col2) {
$self->{_filter_cells}->{ "$row1:$col" } = 1;
for my $col ( $col1 .. $col2 ) {
$self->{_filter_cells}->{"$row1:$col"} = 1;
}
}

Expand All @@ -5161,6 +5179,7 @@ sub add_table {
}



###############################################################################
#
# add_sparkline()
Expand Down
6 changes: 3 additions & 3 deletions t/package/table/table09.t
Expand Up @@ -34,13 +34,13 @@ $worksheet->add_table(
columns => [
{ total_string => 'Total' },
{},
{ total_function => 'Average' },
{ total_function => 'COUNT' },
{ total_function => 'average' },
{ total_function => 'count' },
{ total_function => 'count_nums' },
{ total_function => 'max' },
{ total_function => 'min' },
{ total_function => 'sum' },
{ total_function => 'std Dev' },
{ total_function => 'std_dev' },
{ total_function => 'var' }
],

Expand Down
6 changes: 3 additions & 3 deletions t/regression/table09.t
Expand Up @@ -66,13 +66,13 @@ $worksheet->add_table(
columns => [
{ total_string => 'Total' },
{},
{ total_function => 'Average' },
{ total_function => 'COUNT' },
{ total_function => 'average' },
{ total_function => 'count' },
{ total_function => 'count_nums' },
{ total_function => 'max' },
{ total_function => 'min' },
{ total_function => 'sum' },
{ total_function => 'std Dev' },
{ total_function => 'std_dev' },
{ total_function => 'var' }
],
}
Expand Down
6 changes: 3 additions & 3 deletions t/regression/table10.t
Expand Up @@ -67,13 +67,13 @@ $worksheet->add_table(
columns => [
{ total_string => 'Total' },
{},
{ total_function => 'Average' },
{ total_function => 'COUNT' },
{ total_function => 'average' },
{ total_function => 'count' },
{ total_function => 'count_nums' },
{ total_function => 'max' },
{ total_function => 'min' },
{ total_function => 'sum' },
{ total_function => 'std Dev' },
{ total_function => 'std_dev' },
{ total_function => 'var', formula => 'SUM(Table1[[#This Row],[Column1]:[Column3]])', format => $format }
],
}
Expand Down
6 changes: 3 additions & 3 deletions t/regression/table17.t
Expand Up @@ -71,13 +71,13 @@ $worksheet->add_table(
columns => [
{ total_string => 'Total' },
{},
{ total_function => 'Average' },
{ total_function => 'COUNT' },
{ total_function => 'average' },
{ total_function => 'count' },
{ total_function => 'count_nums' },
{ total_function => 'max', total_value => 5 },
{ total_function => 'min' },
{ total_function => 'sum', total_value => 3 },
{ total_function => 'std Dev' },
{ total_function => 'std_dev' },
{ total_function => 'var' }
],
}
Expand Down
91 changes: 91 additions & 0 deletions t/regression/table32.t
@@ -0,0 +1,91 @@
###############################################################################
#
# Tests the output of Excel::Writer::XLSX against Excel generated files.
#
# Copyright 2000-2023, John McNamara, jmcnamara@cpan.org
#
# SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later
#

use lib 't/lib';
use TestFunctions qw(_compare_xlsx_files _is_deep_diff);
use strict;
use warnings;

use Test::More tests => 1;

###############################################################################
#
# Tests setup.
#
my $filename = 'table32.xlsx';
my $dir = 't/regression/';
my $got_filename = $dir . "ewx_$filename";
my $exp_filename = $dir . 'xlsx_files/' . $filename;

my $ignore_members = [ 'xl/calcChain.xml', '\[Content_Types\].xml', 'xl/_rels/workbook.xml.rels' ];
my $ignore_elements = { 'xl/workbook.xml' => ['<workbookView'] };


###############################################################################
#
# Test the creation of a simple Excel::Writer::XLSX file with tables.
#
use Excel::Writer::XLSX;

my $workbook = Excel::Writer::XLSX->new( $got_filename );
my $worksheet = $workbook->add_worksheet();

# Set the column width to match the target worksheet.
$worksheet->set_column('C:F', 10.288);

# Write some strings to order the string table.
$worksheet->write_string('A1', 'Column1');
$worksheet->write_string('B1', 'Column2');
$worksheet->write_string('C1', 'Column3');
$worksheet->write_string('D1', 'Column4');
$worksheet->write_string('E1', 'Total');

# Add the table.
$worksheet->add_table(
'C3:F14',
{
total_row => 1,
columns => [
{ total_string => 'Total' },
{ total_function => 'D5+D9' },
{ total_function => '=SUM([Column3])' },
{ total_function => 'count' },
],

}
);



$workbook->close();


###############################################################################
#
# Compare the generated and existing Excel files.
#

my ( $got, $expected, $caption ) = _compare_xlsx_files(

$got_filename,
$exp_filename,
$ignore_members,
$ignore_elements,
);

_is_deep_diff( $got, $expected, $caption );


###############################################################################
#
# Cleanup.
#
unlink $got_filename;

__END__

0 comments on commit 466268e

Please sign in to comment.