view release on metacpan or search on metacpan
lib/Excel/Writer/XLSX/Worksheet.pm view on Meta::CPAN
if ( $type eq 's' ) {
# Handle standard shared strings.
$string = $strings[$token];
}
else {
# Handle rich strings without html formatting.
$string = $cell->[3];
}
if ( $string !~ /\n/ ) {
$length = xl_cell_autofit_width( $string );
}
else {
# Handle multiline strings.
my @segments = split "\n", $string;
for my $string ( @segments ) {
my $seg_length =
xl_cell_autofit_width( $string );
if ( $seg_length > $length ) {
$length = $seg_length;
}
}
}
}
elsif ( $type eq 'n' ) {
# Handle numbers.
#
# We use a workaround/optimization for numbers since
# digits all have a pixel width of 7. This gives a
# slightly greater width for the decimal place and
# minus sign but only by a few pixels and
# over-estimation is okay.
$length = 7 * length $token;
}
elsif ( $type eq 't' ) {
# Handle dates.
#
# The following uses the default width for mm/dd/yyyy
# dates. It isn't feasible to parse the number format
# to get the actual string width for all format types.
$length = $self->{_default_date_pixels};
}
elsif ( $type eq 'l' ) {
# Handle boolean values.
#
# Use the Excel standard widths for TRUE and FALSE.
if ( $token ) {
$length = 31;
}
else {
$length = 36;
}
}
elsif ( $type eq 'f' ) {
# Handle formulas.
#
# We only try to autofit a formula if it has a
# non-zero value.
my $value = $cell->[3];
if ( $value ) {
$length = xl_cell_autofit_width( $value );
}
}
elsif ( $type eq 'a' || $type eq 'd' ) {
# Handle array and dynamic formulas.
my $value = $cell->[4];
if ( $value ) {
$length = xl_cell_autofit_width( $value );
}
}
# If the cell is in an autofilter header we add an
# additional 16 pixels for the dropdown arrow.
if ( $length > 0
&& exists $self->{_filter_cells}->{"$row_num:$col_num"}
)
{
$length += 16;
}
# Add the string length to the lookup hash.
my $max = $col_width{$col_num} || 0;
if ( $length > $max ) {
$col_width{$col_num} = $length;
}
}
}
}
}
# Apply the width to the column.
while ( my ( $col_num, $pixel_width ) = each %col_width ) {
# Convert the string pixel width to a character width using an
# additional padding of 7 pixels, like Excel.
my $width = _pixels_to_width( $pixel_width + 7 );
# Limit the width to the maximum user or Excel value.
if ( $width > $max_width ) {
$width = $max_width;
}
# Add the width to an existing col info structure or add a new one.
if ( exists $self->{_col_info}->{$col_num} ) {
$self->{_col_info}->{$col_num}->[0] = $width;
$self->{_col_info}->{$col_num}->[5] = 1;
}
else {
$self->{_col_info}->{$col_num} = [ $width, undef, 0, 0, 0, 1 ];
}
}
}
###############################################################################
#
# set_selection()
#
# Set which cell or cells are selected in a worksheet.
#
sub set_selection {
my $self = shift;
lib/Excel/Writer/XLSX/Worksheet.pm view on Meta::CPAN
#
# Returns: return value of called subroutine
#
sub write {
my $self = shift;
# Check for a cell reference in A1 notation and substitute row and column
if ( $_[0] =~ /^\D/ ) {
@_ = $self->_substitute_cellref( @_ );
}
my $token = $_[2];
# Handle undefs as blanks
$token = '' unless defined $token;
# First try user defined matches.
for my $aref ( @{ $self->{_write_match} } ) {
my $re = $aref->[0];
my $sub = $aref->[1];
if ( $token =~ /$re/ ) {
my $match = &$sub( $self, @_ );
return $match if defined $match;
}
}
# Match an array ref.
if ( ref $token eq "ARRAY" ) {
return $self->write_row( @_ );
}
# Match integer with leading zero(s)
elsif ( $self->{_leading_zeros} and $token =~ /^0\d+$/ ) {
return $self->write_string( @_ );
}
# Match number
elsif ( $token =~ /^([+-]?)(?=[0-9]|\.[0-9])[0-9]*(\.[0-9]*)?([Ee]([+-]?[0-9]+))?$/ ) {
return $self->write_number( @_ );
}
# Match http, https or ftp URL
elsif ( $token =~ m|^[fh]tt?ps?://| ) {
return $self->write_url( @_ );
}
# Match mailto:
elsif ( $token =~ m/^mailto:/ ) {
return $self->write_url( @_ );
}
# Match internal or external sheet link
elsif ( $token =~ m[^(?:in|ex)ternal:] ) {
return $self->write_url( @_ );
}
# Match formula
elsif ( $token =~ /^=/ ) {
return $self->write_formula( @_ );
}
# Match array formula
elsif ( $token =~ /^{=.*}$/ ) {
return $self->write_formula( @_ );
}
# Match blank
elsif ( $token eq '' ) {
splice @_, 2, 1; # remove the empty string from the parameter list
return $self->write_blank( @_ );
}
# Default: match string
else {
return $self->write_string( @_ );
}
}
###############################################################################
#
# write_row($row, $col, $array_ref, $format)
#
# Write a row of data starting from ($row, $col). Call write_col() if any of
# the elements of the array ref are in turn array refs. This allows the writing
# of 1D or 2D arrays of data in one go.
#
# Returns: the first encountered error value or zero for no errors
#
sub write_row {
my $self = shift;
# Check for a cell reference in A1 notation and substitute row and column
if ( $_[0] =~ /^\D/ ) {
@_ = $self->_substitute_cellref( @_ );
}
# Catch non array refs passed by user.
if ( ref $_[2] ne 'ARRAY' ) {
croak "Not an array ref in call to write_row()$!";
}
my $row = shift;
my $col = shift;
my $tokens = shift;
my @options = @_;
my $error = 0;
my $ret;
for my $token ( @$tokens ) {
# Check for nested arrays
if ( ref $token eq "ARRAY" ) {
$ret = $self->write_col( $row, $col, $token, @options );
}
else {
$ret = $self->write( $row, $col, $token, @options );
}
# Return only the first error encountered, if any.
$error ||= $ret;
$col++;
lib/Excel/Writer/XLSX/Worksheet.pm view on Meta::CPAN
$self->_write_single_row( $row );
}
$self->{_table}->{$row}->{$col} = [ $type, $index, $xf, $raw_string ];
return 0;
}
###############################################################################
#
# write_blank($row, $col, $format)
#
# Write a blank cell to the specified row and column (zero indexed).
# A blank cell is used to specify formatting without adding a string
# or a number.
#
# A blank cell without a format serves no purpose. Therefore, we don't write
# a BLANK record unless a format is specified. This is mainly an optimisation
# for the write_row() and write_col() methods.
#
# Returns 0 : normal termination (including no format)
# -1 : insufficient number of arguments
# -2 : row or column out of range
#
sub write_blank {
my $self = shift;
# Check for a cell reference in A1 notation and substitute row and column
if ( $_[0] =~ /^\D/ ) {
@_ = $self->_substitute_cellref( @_ );
}
# Check the number of args
return -1 if @_ < 2;
# Don't write a blank cell unless it has a format
return 0 if not defined $_[2];
my $row = $_[0]; # Zero indexed row
my $col = $_[1]; # Zero indexed column
my $xf = $_[2]; # The cell format
my $type = 'b'; # The data type
# Check that row and col are valid and store max and min values
return -2 if $self->_check_dimensions( $row, $col );
# Write previous row if in in-line string optimization mode.
if ( $self->{_optimization} == 1 && $row > $self->{_previous_row} ) {
$self->_write_single_row( $row );
}
$self->{_table}->{$row}->{$col} = [ $type, undef, $xf ];
return 0;
}
###############################################################################
#
# _prepare_formula($formula)
#
# Utility method to strip equal sign and array braces from a formula and also
# expand out future and dynamic array formulas.
#
sub _prepare_formula {
my $self = shift;
my $formula = shift;
my $expand_future_functions = shift;
# Ignore empty/null formulas.
return $formula if !$formula;
# Remove array formula braces and the leading =.
$formula =~ s/^{(.*)}$/$1/;
$formula =~ s/^=//;
# # Don't expand formulas that the user has already expanded.
return $formula if $formula =~ m/_xlfn\./;
# Expand dynamic array formulas.
$formula =~ s/\b(ANCHORARRAY\()/_xlfn.$1/g;
$formula =~ s/\b(BYCOL\()/_xlfn.$1/g;
$formula =~ s/\b(BYROW\()/_xlfn.$1/g;
$formula =~ s/\b(CHOOSECOLS\()/_xlfn.$1/g;
$formula =~ s/\b(CHOOSEROWS\()/_xlfn.$1/g;
$formula =~ s/\b(DROP\()/_xlfn.$1/g;
$formula =~ s/\b(EXPAND\()/_xlfn.$1/g;
$formula =~ s/\b(FILTER\()/_xlfn._xlws.$1/g;
$formula =~ s/\b(HSTACK\()/_xlfn.$1/g;
$formula =~ s/\b(LAMBDA\()/_xlfn.$1/g;
$formula =~ s/\b(MAKEARRAY\()/_xlfn.$1/g;
$formula =~ s/\b(MAP\()/_xlfn.$1/g;
$formula =~ s/\b(RANDARRAY\()/_xlfn.$1/g;
$formula =~ s/\b(REDUCE\()/_xlfn.$1/g;
$formula =~ s/\b(SCAN\()/_xlfn.$1/g;
$formula =~ s/\b(SEQUENCE\()/_xlfn.$1/g;
$formula =~ s/\b(SINGLE\()/_xlfn.$1/g;
$formula =~ s/\b(SORT\()/_xlfn._xlws.$1/g;
$formula =~ s/\b(SORTBY\()/_xlfn.$1/g;
$formula =~ s/\b(SWITCH\()/_xlfn.$1/g;
$formula =~ s/\b(TAKE\()/_xlfn.$1/g;
$formula =~ s/\b(TEXTSPLIT\()/_xlfn.$1/g;
$formula =~ s/\b(TOCOL\()/_xlfn.$1/g;
$formula =~ s/\b(TOROW\()/_xlfn.$1/g;
$formula =~ s/\b(UNIQUE\()/_xlfn.$1/g;
$formula =~ s/\b(VSTACK\()/_xlfn.$1/g;
$formula =~ s/\b(WRAPCOLS\()/_xlfn.$1/g;
$formula =~ s/\b(WRAPROWS\()/_xlfn.$1/g;
$formula =~ s/\b(XLOOKUP\()/_xlfn.$1/g;
if ( !$self->{_use_future_functions} && !$expand_future_functions ) {
return $formula;
}
# Future functions.
$formula =~ s/\b(ACOTH\()/_xlfn.$1/g;
$formula =~ s/\b(ACOT\()/_xlfn.$1/g;
$formula =~ s/\b(AGGREGATE\()/_xlfn.$1/g;
$formula =~ s/\b(ARABIC\()/_xlfn.$1/g;
$formula =~ s/\b(ARRAYTOTEXT\()/_xlfn.$1/g;
$formula =~ s/\b(BASE\()/_xlfn.$1/g;
$formula =~ s/\b(BETA.DIST\()/_xlfn.$1/g;
$formula =~ s/\b(BETA.INV\()/_xlfn.$1/g;
$formula =~ s/\b(BINOM.DIST.RANGE\()/_xlfn.$1/g;
$formula =~ s/\b(BINOM.DIST\()/_xlfn.$1/g;
$formula =~ s/\b(BINOM.INV\()/_xlfn.$1/g;
$formula =~ s/\b(BITAND\()/_xlfn.$1/g;
$formula =~ s/\b(BITLSHIFT\()/_xlfn.$1/g;
$formula =~ s/\b(BITOR\()/_xlfn.$1/g;
$formula =~ s/\b(BITRSHIFT\()/_xlfn.$1/g;
$formula =~ s/\b(BITXOR\()/_xlfn.$1/g;
$formula =~ s/\b(CEILING.MATH\()/_xlfn.$1/g;
$formula =~ s/\b(CEILING.PRECISE\()/_xlfn.$1/g;
$formula =~ s/\b(CHISQ.DIST.RT\()/_xlfn.$1/g;
$formula =~ s/\b(CHISQ.DIST\()/_xlfn.$1/g;
$formula =~ s/\b(CHISQ.INV.RT\()/_xlfn.$1/g;
$formula =~ s/\b(CHISQ.INV\()/_xlfn.$1/g;
$formula =~ s/\b(CHISQ.TEST\()/_xlfn.$1/g;
$formula =~ s/\b(COMBINA\()/_xlfn.$1/g;
$formula =~ s/\b(CONCAT\()/_xlfn.$1/g;
$formula =~ s/\b(CONFIDENCE.NORM\()/_xlfn.$1/g;
$formula =~ s/\b(CONFIDENCE.T\()/_xlfn.$1/g;
$formula =~ s/\b(COTH\()/_xlfn.$1/g;
$formula =~ s/\b(COT\()/_xlfn.$1/g;
$formula =~ s/\b(COVARIANCE.P\()/_xlfn.$1/g;
$formula =~ s/\b(COVARIANCE.S\()/_xlfn.$1/g;
$formula =~ s/\b(CSCH\()/_xlfn.$1/g;
$formula =~ s/\b(CSC\()/_xlfn.$1/g;
$formula =~ s/\b(DAYS\()/_xlfn.$1/g;
$formula =~ s/\b(DECIMAL\()/_xlfn.$1/g;
$formula =~ s/\b(ERF.PRECISE\()/_xlfn.$1/g;
$formula =~ s/\b(ERFC.PRECISE\()/_xlfn.$1/g;
$formula =~ s/\b(EXPON.DIST\()/_xlfn.$1/g;
$formula =~ s/\b(F.DIST.RT\()/_xlfn.$1/g;
$formula =~ s/\b(F.DIST\()/_xlfn.$1/g;
$formula =~ s/\b(F.INV.RT\()/_xlfn.$1/g;
$formula =~ s/\b(F.INV\()/_xlfn.$1/g;
$formula =~ s/\b(F.TEST\()/_xlfn.$1/g;
$formula =~ s/\b(FILTERXML\()/_xlfn.$1/g;
$formula =~ s/\b(FLOOR.MATH\()/_xlfn.$1/g;
$formula =~ s/\b(FLOOR.PRECISE\()/_xlfn.$1/g;
$formula =~ s/\b(FORECAST.ETS.CONFINT\()/_xlfn.$1/g;
$formula =~ s/\b(FORECAST.ETS.SEASONALITY\()/_xlfn.$1/g;
$formula =~ s/\b(FORECAST.ETS.STAT\()/_xlfn.$1/g;
$formula =~ s/\b(FORECAST.ETS\()/_xlfn.$1/g;
$formula =~ s/\b(FORECAST.LINEAR\()/_xlfn.$1/g;
$formula =~ s/\b(FORMULATEXT\()/_xlfn.$1/g;
$formula =~ s/\b(GAMMA.DIST\()/_xlfn.$1/g;
$formula =~ s/\b(GAMMA.INV\()/_xlfn.$1/g;
$formula =~ s/\b(GAMMALN.PRECISE\()/_xlfn.$1/g;
$formula =~ s/\b(GAMMA\()/_xlfn.$1/g;
$formula =~ s/\b(GAUSS\()/_xlfn.$1/g;
$formula =~ s/\b(HYPGEOM.DIST\()/_xlfn.$1/g;
$formula =~ s/\b(IFNA\()/_xlfn.$1/g;
$formula =~ s/\b(IFS\()/_xlfn.$1/g;
$formula =~ s/\b(IMAGE\()/_xlfn.$1/g;
$formula =~ s/\b(IMCOSH\()/_xlfn.$1/g;
$formula =~ s/\b(IMCOT\()/_xlfn.$1/g;
$formula =~ s/\b(IMCSCH\()/_xlfn.$1/g;
$formula =~ s/\b(IMCSC\()/_xlfn.$1/g;
$formula =~ s/\b(IMSECH\()/_xlfn.$1/g;
$formula =~ s/\b(IMSEC\()/_xlfn.$1/g;
$formula =~ s/\b(IMSINH\()/_xlfn.$1/g;
$formula =~ s/\b(IMTAN\()/_xlfn.$1/g;
$formula =~ s/\b(ISFORMULA\()/_xlfn.$1/g;
$formula =~ s/\b(ISOMITTED\()/_xlfn.$1/g;
$formula =~ s/\b(ISOWEEKNUM\()/_xlfn.$1/g;
$formula =~ s/\b(LET\()/_xlfn.$1/g;
$formula =~ s/\b(LOGNORM.DIST\()/_xlfn.$1/g;
$formula =~ s/\b(LOGNORM.INV\()/_xlfn.$1/g;
$formula =~ s/\b(MAXIFS\()/_xlfn.$1/g;
$formula =~ s/\b(MINIFS\()/_xlfn.$1/g;
$formula =~ s/\b(MODE.MULT\()/_xlfn.$1/g;
$formula =~ s/\b(MODE.SNGL\()/_xlfn.$1/g;
$formula =~ s/\b(MUNIT\()/_xlfn.$1/g;
$formula =~ s/\b(NEGBINOM.DIST\()/_xlfn.$1/g;
$formula =~ s/\b(NORM.DIST\()/_xlfn.$1/g;
$formula =~ s/\b(NORM.INV\()/_xlfn.$1/g;
$formula =~ s/\b(NORM.S.DIST\()/_xlfn.$1/g;
$formula =~ s/\b(NORM.S.INV\()/_xlfn.$1/g;
$formula =~ s/\b(NUMBERVALUE\()/_xlfn.$1/g;
$formula =~ s/\b(PDURATION\()/_xlfn.$1/g;
$formula =~ s/\b(PERCENTILE.EXC\()/_xlfn.$1/g;
$formula =~ s/\b(PERCENTILE.INC\()/_xlfn.$1/g;
$formula =~ s/\b(PERCENTRANK.EXC\()/_xlfn.$1/g;
$formula =~ s/\b(PERCENTRANK.INC\()/_xlfn.$1/g;
$formula =~ s/\b(PERMUTATIONA\()/_xlfn.$1/g;
$formula =~ s/\b(PHI\()/_xlfn.$1/g;
$formula =~ s/\b(POISSON.DIST\()/_xlfn.$1/g;
$formula =~ s/\b(QUARTILE.EXC\()/_xlfn.$1/g;
$formula =~ s/\b(QUARTILE.INC\()/_xlfn.$1/g;
$formula =~ s/\b(QUERYSTRING\()/_xlfn.$1/g;
$formula =~ s/\b(RANK.AVG\()/_xlfn.$1/g;
$formula =~ s/\b(RANK.EQ\()/_xlfn.$1/g;
$formula =~ s/\b(RRI\()/_xlfn.$1/g;
$formula =~ s/\b(SECH\()/_xlfn.$1/g;
$formula =~ s/\b(SEC\()/_xlfn.$1/g;
$formula =~ s/\b(SHEETS\()/_xlfn.$1/g;
$formula =~ s/\b(SHEET\()/_xlfn.$1/g;
$formula =~ s/\b(SKEW.P\()/_xlfn.$1/g;
$formula =~ s/\b(STDEV.P\()/_xlfn.$1/g;
$formula =~ s/\b(STDEV.S\()/_xlfn.$1/g;
$formula =~ s/\b(T.DIST.2T\()/_xlfn.$1/g;
$formula =~ s/\b(T.DIST.RT\()/_xlfn.$1/g;
$formula =~ s/\b(T.DIST\()/_xlfn.$1/g;
$formula =~ s/\b(T.INV.2T\()/_xlfn.$1/g;
$formula =~ s/\b(T.INV\()/_xlfn.$1/g;
$formula =~ s/\b(T.TEST\()/_xlfn.$1/g;
$formula =~ s/\b(TEXTAFTER\()/_xlfn.$1/g;
$formula =~ s/\b(TEXTBEFORE\()/_xlfn.$1/g;
$formula =~ s/\b(TEXTJOIN\()/_xlfn.$1/g;
$formula =~ s/\b(UNICHAR\()/_xlfn.$1/g;
$formula =~ s/\b(UNICODE\()/_xlfn.$1/g;
$formula =~ s/\b(VALUETOTEXT\()/_xlfn.$1/g;
$formula =~ s/\b(VAR.P\()/_xlfn.$1/g;
$formula =~ s/\b(VAR.S\()/_xlfn.$1/g;
$formula =~ s/\b(WEBSERVICE\()/_xlfn.$1/g;
$formula =~ s/\b(WEIBULL.DIST\()/_xlfn.$1/g;
$formula =~ s/\b(XMATCH\()/_xlfn.$1/g;
$formula =~ s/\b(XOR\()/_xlfn.$1/g;
$formula =~ s/\b(Z.TEST\()/_xlfn.$1/g;
return $formula;
}
###############################################################################
#
# write_formula($row, $col, $formula, $format)
#
# Write a formula to the specified row and column (zero indexed).
#
# $format is optional.
#
# Returns 0 : normal termination
# -1 : insufficient number of arguments
# -2 : row or column out of range
#
sub write_formula {
my $self = shift;
# Check for a cell reference in A1 notation and substitute row and column
if ( $_[0] =~ /^\D/ ) {
@_ = $self->_substitute_cellref( @_ );
}
if ( @_ < 3 ) { return -1 } # Check the number of args
my $row = $_[0]; # Zero indexed row
my $col = $_[1]; # Zero indexed column
my $formula = $_[2]; # The formula text string
my $xf = $_[3]; # The format object.
my $value = $_[4]; # Optional formula value.
my $type = 'f'; # The data type
# Check for dynamic array functions.
local $_ = $formula;
if ( m{\bANCHORARRAY\(}
|| m{\bBYCOL\(}
|| m{\bBYROW\(}
|| m{\bCHOOSECOLS\(}
|| m{\bCHOOSEROWS\(}
|| m{\bDROP\(}
|| m{\bEXPAND\(}
|| m{\bFILTER\(}
|| m{\bHSTACK\(}
|| m{\bLAMBDA\(}
|| m{\bMAKEARRAY\(}
|| m{\bMAP\(}
|| m{\bRANDARRAY\(}
|| m{\bREDUCE\(}
|| m{\bSCAN\(}
|| m{\bSEQUENCE\(}
|| m{\bSINGLE\(}
|| m{\bSORT\(}
|| m{\bSORTBY\(}
|| m{\bSWITCH\(}
|| m{\bTAKE\(}
|| m{\bTEXTSPLIT\(}
|| m{\bTOCOL\(}
|| m{\bTOROW\(}
|| m{\bUNIQUE\(}
|| m{\bVSTACK\(}
|| m{\bWRAPCOLS\(}
|| m{\bWRAPROWS\(}
|| m{\bXLOOKUP\(} )
{
return $self->write_dynamic_array_formula( $row, $col, $row, $col,
$formula, $xf, $value );
}
# Hand off array formulas.
if ( $formula =~ /^{=.*}$/ ) {
return $self->write_array_formula( $row, $col, $row, $col, $formula,
$xf, $value );
}
# Check that row and col are valid and store max and min values
return -2 if $self->_check_dimensions( $row, $col );
# Expand out the formula.
$formula = $self->_prepare_formula($formula);
# Write previous row if in in-line string optimization mode.
if ( $self->{_optimization} == 1 && $row > $self->{_previous_row} ) {
$self->_write_single_row( $row );
}
$self->{_table}->{$row}->{$col} = [ $type, $formula, $xf, $value ];
return 0;
}
# Internal method shared by the write_array_formula() and
# write_dynamic_array_formula() methods.
sub _write_array_formula {
my $self = shift;
my $type = shift;
my @args = @_;
# Check for a cell reference in A1 notation and substitute row and column
if ( $args[0] =~ /^\D/ ) {
my $cellref = shift @args;
# Convert single cell to range.
my @dims = $self->_substitute_cellref( $cellref );
if ( @dims == 2 ) {
@args = ( @dims, @dims, @args );
}
else {
@args = ( @dims, @args );
}
}
if ( @args < 5 ) { return -1 } # Check the number of args
my $row1 = $args[0]; # First row
my $col1 = $args[1]; # First column
my $row2 = $args[2]; # Last row
my $col2 = $args[3]; # Last column
my $formula = $args[4]; # The formula text string
my $xf = $args[5]; # The format object.
my $value = $args[6]; # Optional formula value.
# Swap last row/col with first row/col as necessary
( $row1, $row2 ) = ( $row2, $row1 ) if $row1 > $row2;
( $col1, $col2 ) = ( $col1, $col2 ) if $col1 > $col2;
# Check that row and col are valid and store max and min values.
return -2 if $self->_check_dimensions( $row1, $col1 );
return -2 if $self->_check_dimensions( $row2, $col2 );
# Define array range
my $range;
if ( $row1 == $row2 and $col1 == $col2 ) {
$range = xl_rowcol_to_cell( $row1, $col1 );
}
else {
$range =
xl_rowcol_to_cell( $row1, $col1 ) . ':'
. xl_rowcol_to_cell( $row2, $col2 );
}
# Modify the formula string, as needed.
$formula = $self->_prepare_formula($formula);
# Write previous row if in in-line string optimization mode.
my $row = $row1;
if ( $self->{_optimization} == 1 && $row > $self->{_previous_row} ) {
$self->_write_single_row( $row );
}
$self->{_table}->{$row1}->{$col1} =
[ $type, $formula, $xf, $range, $value ];
# Pad out the rest of the area with formatted zeroes.
if ( !$self->{_optimization} ) {
for my $row ( $row1 .. $row2 ) {
for my $col ( $col1 .. $col2 ) {
next if $row == $row1 and $col == $col1;
$self->write_number( $row, $col, 0, $xf );
}
}
}
return 0;
}
###############################################################################
#
# write_array_formula($row1, $col1, $row2, $col2, $formula, $format)
#
# Write an array formula to the specified row and column (zero indexed).
#
# $format is optional.
#
# Returns 0 : normal termination
# -1 : insufficient number of arguments
# -2 : row or column out of range
#
sub write_array_formula {
my $self = shift;
return $self->_write_array_formula( 'a', @_ );
}
###############################################################################
#
# write_dynamic_array_formula($row1, $col1, $row2, $col2, $formula, $format)
#
# Write a dynamic formula to the specified row and column (zero indexed).
#
# $format is optional.
#
# Returns 0 : normal termination
# -1 : insufficient number of arguments
# -2 : row or column out of range
#
sub write_dynamic_array_formula {
my $self = shift;
my $error = $self->_write_array_formula( 'd', @_ );
if ( $error == 0 ) {
$self->{_has_dynamic_functions} = 1;
}
return $error;
}
###############################################################################
#
# write_blank($row, $col, $format)
#
# Write a boolean value to the specified row and column (zero indexed).
#
# Returns 0 : normal termination (including no format)
# -2 : row or column out of range
#
sub write_boolean {
my $self = shift;
# Check for a cell reference in A1 notation and substitute row and column
if ( $_[0] =~ /^\D/ ) {
@_ = $self->_substitute_cellref( @_ );
}
my $row = $_[0]; # Zero indexed row
my $col = $_[1]; # Zero indexed column
my $val = $_[2] ? 1 : 0; # Boolean value.
my $xf = $_[3]; # The cell format
my $type = 'l'; # The data type
# Check that row and col are valid and store max and min values
return -2 if $self->_check_dimensions( $row, $col );
# Write previous row if in in-line string optimization mode.
if ( $self->{_optimization} == 1 && $row > $self->{_previous_row} ) {
$self->_write_single_row( $row );
}
$self->{_table}->{$row}->{$col} = [ $type, $val, $xf ];
return 0;
}
###############################################################################
#
# outline_settings($visible, $symbols_below, $symbols_right, $auto_style)
#
# This method sets the properties for outlining and grouping. The defaults
# correspond to Excel's defaults.
#
sub outline_settings {
my $self = shift;
$self->{_outline_on} = defined $_[0] ? $_[0] : 1;
$self->{_outline_below} = defined $_[1] ? $_[1] : 1;
lib/Excel/Writer/XLSX/Worksheet.pm view on Meta::CPAN
my $row_last = shift;
my $col_last = shift;
my $string = shift;
my $format = shift;
my @extra_args = @_; # For write_url().
# Excel doesn't allow a single cell to be merged
if ( $row_first == $row_last and $col_first == $col_last ) {
croak "Can't merge single cell";
}
# Swap last row/col with first row/col as necessary
( $row_first, $row_last ) = ( $row_last, $row_first )
if $row_first > $row_last;
( $col_first, $col_last ) = ( $col_last, $col_first )
if $col_first > $col_last;
# Check that the data range is valid and store the max and min values.
return -2 if $self->_check_dimensions( $row_first, $col_first );
return -2 if $self->_check_dimensions( $row_last, $col_last );
# Store the merge range.
push @{ $self->{_merge} }, [ $row_first, $col_first, $row_last, $col_last ];
# Write the first cell
$self->write( $row_first, $col_first, $string, $format, @extra_args );
# Pad out the rest of the area with formatted blank cells.
for my $row ( $row_first .. $row_last ) {
for my $col ( $col_first .. $col_last ) {
next if $row == $row_first and $col == $col_first;
$self->write_blank( $row, $col, $format );
}
}
}
###############################################################################
#
# merge_range_type()
#
# Same as merge_range() above except the type of write() is specified.
#
sub merge_range_type {
my $self = shift;
my $type = shift;
# Check for a cell reference in A1 notation and substitute row and column
if ( $_[0] =~ /^\D/ ) {
@_ = $self->_substitute_cellref( @_ );
}
my $row_first = shift;
my $col_first = shift;
my $row_last = shift;
my $col_last = shift;
my $format;
# Get the format. It can be in different positions for the different types.
if ( $type eq 'array_formula'
|| $type eq 'blank'
|| $type eq 'rich_string' )
{
# The format is the last element.
$format = $_[-1];
}
else {
# Or else it is after the token.
$format = $_[1];
}
# Check that there is a format object.
croak "Format object missing or in an incorrect position"
unless ref $format;
# Excel doesn't allow a single cell to be merged
if ( $row_first == $row_last and $col_first == $col_last ) {
croak "Can't merge single cell";
}
# Swap last row/col with first row/col as necessary
( $row_first, $row_last ) = ( $row_last, $row_first )
if $row_first > $row_last;
( $col_first, $col_last ) = ( $col_last, $col_first )
if $col_first > $col_last;
# Check that the data range is valid and store the max and min values.
return -2 if $self->_check_dimensions( $row_first, $col_first );
return -2 if $self->_check_dimensions( $row_last, $col_last );
# Store the merge range.
push @{ $self->{_merge} }, [ $row_first, $col_first, $row_last, $col_last ];
# Write the first cell
if ( $type eq 'string' ) {
$self->write_string( $row_first, $col_first, @_ );
}
elsif ( $type eq 'number' ) {
$self->write_number( $row_first, $col_first, @_ );
}
elsif ( $type eq 'blank' ) {
$self->write_blank( $row_first, $col_first, @_ );
}
elsif ( $type eq 'date_time' ) {
$self->write_date_time( $row_first, $col_first, @_ );
}
elsif ( $type eq 'rich_string' ) {
$self->write_rich_string( $row_first, $col_first, @_ );
}
elsif ( $type eq 'url' ) {
$self->write_url( $row_first, $col_first, @_ );
}
elsif ( $type eq 'formula' ) {
$self->write_formula( $row_first, $col_first, @_ );
}
elsif ( $type eq 'array_formula' ) {
$self->write_formula_array( $row_first, $col_first, @_ );
}
else {
croak "Unknown type '$type'";
}
# Pad out the rest of the area with formatted blank cells.
for my $row ( $row_first .. $row_last ) {
for my $col ( $col_first .. $col_last ) {
next if $row == $row_first and $col == $col_first;
$self->write_blank( $row, $col, $format );
}
}
}
###############################################################################
#
# data_validation($row, $col, {...})
#
# This method handles the interface to Excel data validation.
# Somewhat ironically this requires a lot of validation code since the
# interface is flexible and covers a several types of data validation.
#
# We allow data validation to be called on one cell or a range of cells. The
# hashref contains the validation parameters and must be the last param:
# data_validation($row, $col, {...})
# data_validation($first_row, $first_col, $last_row, $last_col, {...})
#
# Returns 0 : normal termination
# -1 : insufficient number of arguments
# -2 : row or column out of range
# -3 : incorrect parameter.
#
sub data_validation {
my $self = shift;
# Check for a cell reference in A1 notation and substitute row and column
if ( $_[0] =~ /^\D/ ) {
@_ = $self->_substitute_cellref( @_ );
}
# Check for a valid number of args.
if ( @_ != 5 && @_ != 3 ) { return -1 }
# The final hashref contains the validation parameters.
my $options = pop;
# Make the last row/col the same as the first if not defined.
my ( $row1, $col1, $row2, $col2 ) = @_;
if ( !defined $row2 ) {
$row2 = $row1;
$col2 = $col1;
}
# Check that row and col are valid without storing the values.
return -2 if $self->_check_dimensions( $row1, $col1, 1, 1 );
return -2 if $self->_check_dimensions( $row2, $col2, 1, 1 );
# Check that the last parameter is a hash list.
lib/Excel/Writer/XLSX/Worksheet.pm view on Meta::CPAN
elsif ( not exists $error_type{ lc( $param->{error_type} ) } ) {
carp "Unknown criteria type '$param->{error_type}' for parameter "
. "'error_type' in data_validation()";
return -3;
}
else {
$param->{error_type} = $error_type{ lc( $param->{error_type} ) };
}
# Convert date/times value if required.
if ( $param->{validate} eq 'date' || $param->{validate} eq 'time' ) {
my $date_time = $self->convert_date_time( $param->{value} );
if ( defined $date_time ) {
$param->{value} = $date_time;
}
if ( defined $param->{maximum} ) {
my $date_time = $self->convert_date_time( $param->{maximum} );
if ( defined $date_time ) {
$param->{maximum} = $date_time;
}
}
}
# Check that the input title doesn't exceed the maximum length.
if ( $param->{input_title} and length $param->{input_title} > 32 ) {
carp "Length of input title '$param->{input_title}'"
. " exceeds Excel's limit of 32";
return -3;
}
# Check that the error title don't exceed the maximum length.
if ( $param->{error_title} and length $param->{error_title} > 32 ) {
carp "Length of error title '$param->{error_title}'"
. " exceeds Excel's limit of 32";
return -3;
}
# Check that the input message don't exceed the maximum length.
if ( $param->{input_message} and length $param->{input_message} > 255 ) {
carp "Length of input message '$param->{input_message}'"
. " exceeds Excel's limit of 255";
return -3;
}
# Check that the error message don't exceed the maximum length.
if ( $param->{error_message} and length $param->{error_message} > 255 ) {
carp "Length of error message '$param->{error_message}'"
. " exceeds Excel's limit of 255";
return -3;
}
# Check that the input list don't exceed the maximum length.
if ( $param->{validate} eq 'list' ) {
if ( ref $param->{value} eq 'ARRAY' ) {
my $formula = join ',', @{ $param->{value} };
if ( length $formula > 255 ) {
carp "Length of list items '$formula' exceeds Excel's "
. "limit of 255, use a formula range instead";
return -3;
}
}
}
# Set some defaults if they haven't been defined by the user.
$param->{ignore_blank} = 1 if !defined $param->{ignore_blank};
$param->{dropdown} = 1 if !defined $param->{dropdown};
$param->{show_input} = 1 if !defined $param->{show_input};
$param->{show_error} = 1 if !defined $param->{show_error};
# These are the cells to which the validation is applied.
$param->{cells} = [ [ $row1, $col1, $row2, $col2 ] ];
# A (for now) undocumented parameter to pass additional cell ranges.
if ( exists $param->{other_cells} ) {
push @{ $param->{cells} }, @{ $param->{other_cells} };
}
# Store the validation information until we close the worksheet.
push @{ $self->{_validations} }, $param;
}
###############################################################################
#
# conditional_formatting($row, $col, {...})
#
# This method handles the interface to Excel conditional formatting.
#
# We allow the format to be called on one cell or a range of cells. The
# hashref contains the formatting parameters and must be the last param:
# conditional_formatting($row, $col, {...})
# conditional_formatting($first_row, $first_col, $last_row, $last_col, {...})
#
# Returns 0 : normal termination
# -1 : insufficient number of arguments
# -2 : row or column out of range
# -3 : incorrect parameter.
#
sub conditional_formatting {
my $self = shift;
my $user_range = '';
# Check for a cell reference in A1 notation and substitute row and column
if ( $_[0] =~ /^\D/ ) {
# Check for a user defined multiple range like B3:K6,B8:K11.
if ( $_[0] =~ /,/ ) {
$user_range = $_[0];
$user_range =~ s/^=//;
$user_range =~ s/\s*,\s*/ /g;
$user_range =~ s/\$//g;
}
@_ = $self->_substitute_cellref( @_ );
}
lib/Excel/Writer/XLSX/Worksheet.pm view on Meta::CPAN
mid_type => 1,
max_type => 1,
min_value => 1,
mid_value => 1,
max_value => 1,
min_color => 1,
mid_color => 1,
max_color => 1,
bar_color => 1,
bar_negative_color => 1,
bar_negative_color_same => 1,
bar_solid => 1,
bar_border_color => 1,
bar_negative_border_color => 1,
bar_negative_border_color_same => 1,
bar_no_border => 1,
bar_direction => 1,
bar_axis_position => 1,
bar_axis_color => 1,
bar_only => 1,
icon_style => 1,
reverse_icons => 1,
icons_only => 1,
icons => 1,
data_bar_2010 => 1,
);
# Check for valid input parameters.
for my $param_key ( keys %$param ) {
if ( not exists $valid_parameter{$param_key} ) {
carp "Unknown parameter '$param_key' in conditional_formatting()";
return -3;
}
}
# 'type' is a required parameter.
if ( not exists $param->{type} ) {
carp "Parameter 'type' is required in conditional_formatting()";
return -3;
}
# List of valid validation types.
my %valid_type = (
'cell' => 'cellIs',
'date' => 'date',
'time' => 'time',
'average' => 'aboveAverage',
'duplicate' => 'duplicateValues',
'unique' => 'uniqueValues',
'top' => 'top10',
'bottom' => 'top10',
'text' => 'text',
'time_period' => 'timePeriod',
'blanks' => 'containsBlanks',
'no_blanks' => 'notContainsBlanks',
'errors' => 'containsErrors',
'no_errors' => 'notContainsErrors',
'2_color_scale' => '2_color_scale',
'3_color_scale' => '3_color_scale',
'data_bar' => 'dataBar',
'formula' => 'expression',
'icon_set' => 'iconSet',
);
# Check for valid validation types.
if ( not exists $valid_type{ lc( $param->{type} ) } ) {
carp "Unknown validation type '$param->{type}' for parameter "
. "'type' in conditional_formatting()";
return -3;
}
else {
$param->{direction} = 'bottom' if $param->{type} eq 'bottom';
$param->{type} = $valid_type{ lc( $param->{type} ) };
}
# List of valid criteria types.
my %criteria_type = (
'between' => 'between',
'not between' => 'notBetween',
'equal to' => 'equal',
'=' => 'equal',
'==' => 'equal',
'not equal to' => 'notEqual',
'!=' => 'notEqual',
'<>' => 'notEqual',
'greater than' => 'greaterThan',
'>' => 'greaterThan',
'less than' => 'lessThan',
'<' => 'lessThan',
'greater than or equal to' => 'greaterThanOrEqual',
'>=' => 'greaterThanOrEqual',
'less than or equal to' => 'lessThanOrEqual',
'<=' => 'lessThanOrEqual',
'containing' => 'containsText',
'not containing' => 'notContains',
'begins with' => 'beginsWith',
'ends with' => 'endsWith',
'yesterday' => 'yesterday',
'today' => 'today',
'last 7 days' => 'last7Days',
'last week' => 'lastWeek',
'this week' => 'thisWeek',
'next week' => 'nextWeek',
'last month' => 'lastMonth',
'this month' => 'thisMonth',
'next month' => 'nextMonth',
);
# Check for valid criteria types.
if ( defined $param->{criteria}
&& exists $criteria_type{ lc( $param->{criteria} ) } )
{
$param->{criteria} = $criteria_type{ lc( $param->{criteria} ) };
}
# Convert date/times value if required.
if ( $param->{type} eq 'date' || $param->{type} eq 'time' ) {
$param->{type} = 'cellIs';
lib/Excel/Writer/XLSX/Worksheet.pm view on Meta::CPAN
}
# List of valid icon styles.
my %icon_set_styles = (
"3_arrows" => "3Arrows", # 1
"3_flags" => "3Flags", # 2
"3_traffic_lights_rimmed" => "3TrafficLights2", # 3
"3_symbols_circled" => "3Symbols", # 4
"4_arrows" => "4Arrows", # 5
"4_red_to_black" => "4RedToBlack", # 6
"4_traffic_lights" => "4TrafficLights", # 7
"5_arrows_gray" => "5ArrowsGray", # 8
"5_quarters" => "5Quarters", # 9
"3_arrows_gray" => "3ArrowsGray", # 10
"3_traffic_lights" => "3TrafficLights", # 11
"3_signs" => "3Signs", # 12
"3_symbols" => "3Symbols2", # 13
"4_arrows_gray" => "4ArrowsGray", # 14
"4_ratings" => "4Rating", # 15
"5_arrows" => "5Arrows", # 16
"5_ratings" => "5Rating", # 17
);
# Set properties for icon sets.
if ( $param->{type} eq 'iconSet' ) {
if ( !defined $param->{icon_style} ) {
carp "The 'icon_style' parameter must be specified when "
. "'type' == 'icon_set' in conditional_formatting()";
return -3;
}
# Check for valid icon styles.
if ( not exists $icon_set_styles{ $param->{icon_style} } ) {
carp "Unknown icon style '$param->{icon_style}' for parameter "
. "'icon_style' in conditional_formatting()";
return -3;
}
else {
$param->{icon_style} = $icon_set_styles{ $param->{icon_style} };
}
# Set the number of icons for the icon style.
$param->{total_icons} = 3;
if ( $param->{icon_style} =~ /^4/ ) {
$param->{total_icons} = 4;
}
elsif ( $param->{icon_style} =~ /^5/ ) {
$param->{total_icons} = 5;
}
$param->{icons} =
$self->_set_icon_properties( $param->{total_icons}, $param->{icons} );
}
# Set the formatting range.
my $range = '';
my $start_cell = ''; # Use for formulas.
# Swap last row/col for first row/col as necessary
if ( $row1 > $row2 ) {
( $row1, $row2 ) = ( $row2, $row1 );
}
if ( $col1 > $col2 ) {
( $col1, $col2 ) = ( $col2, $col1 );
}
$range = xl_range( $row1, $row2, $col1, $col2 );
$start_cell = xl_rowcol_to_cell( $row1, $col1 );
# Override with user defined multiple range if provided.
if ( $user_range ) {
$range = $user_range;
}
# Get the dxf format index.
if ( defined $param->{format} && ref $param->{format} ) {
$param->{format} = $param->{format}->get_dxf_index();
}
# Set the priority based on the order of adding.
$param->{priority} = $self->{_dxf_priority}++;
# Check for 2010 style data_bar parameters.
if ( $self->{_use_data_bars_2010}
|| $param->{data_bar_2010}
|| $param->{bar_solid}
|| $param->{bar_border_color}
|| $param->{bar_negative_color}
|| $param->{bar_negative_color_same}
|| $param->{bar_negative_border_color}
|| $param->{bar_negative_border_color_same}
|| $param->{bar_no_border}
|| $param->{bar_axis_position}
|| $param->{bar_axis_color}
|| $param->{bar_direction} )
{
$param->{_is_data_bar_2010} = 1;
}
# Special handling of text criteria.
if ( $param->{type} eq 'text' ) {
if ( $param->{criteria} eq 'containsText' ) {
$param->{type} = 'containsText';
$param->{formula} = sprintf 'NOT(ISERROR(SEARCH("%s",%s)))',
$param->{value}, $start_cell;
}
elsif ( $param->{criteria} eq 'notContains' ) {
$param->{type} = 'notContainsText';
$param->{formula} = sprintf 'ISERROR(SEARCH("%s",%s))',
$param->{value}, $start_cell;
}
elsif ( $param->{criteria} eq 'beginsWith' ) {
$param->{type} = 'beginsWith';
$param->{formula} = sprintf 'LEFT(%s,%d)="%s"',
$start_cell, length( $param->{value} ), $param->{value};
}
elsif ( $param->{criteria} eq 'endsWith' ) {
$param->{type} = 'endsWith';
$param->{formula} = sprintf 'RIGHT(%s,%d)="%s"',
$start_cell, length( $param->{value} ), $param->{value};
}
else {
carp "Invalid text criteria '$param->{criteria}' "
. "in conditional_formatting()";
}
}
# Special handling of time time_period criteria.
if ( $param->{type} eq 'timePeriod' ) {
if ( $param->{criteria} eq 'yesterday' ) {
$param->{formula} = sprintf 'FLOOR(%s,1)=TODAY()-1', $start_cell;
}
elsif ( $param->{criteria} eq 'today' ) {
$param->{formula} = sprintf 'FLOOR(%s,1)=TODAY()', $start_cell;
}
elsif ( $param->{criteria} eq 'tomorrow' ) {
$param->{formula} = sprintf 'FLOOR(%s,1)=TODAY()+1', $start_cell;
}
elsif ( $param->{criteria} eq 'last7Days' ) {
$param->{formula} =
sprintf 'AND(TODAY()-FLOOR(%s,1)<=6,FLOOR(%s,1)<=TODAY())',
$start_cell, $start_cell;
}
elsif ( $param->{criteria} eq 'lastWeek' ) {
$param->{formula} =
sprintf 'AND(TODAY()-ROUNDDOWN(%s,0)>=(WEEKDAY(TODAY())),'
. 'TODAY()-ROUNDDOWN(%s,0)<(WEEKDAY(TODAY())+7))',
$start_cell, $start_cell;
}
elsif ( $param->{criteria} eq 'thisWeek' ) {
$param->{formula} =
sprintf 'AND(TODAY()-ROUNDDOWN(%s,0)<=WEEKDAY(TODAY())-1,'
. 'ROUNDDOWN(%s,0)-TODAY()<=7-WEEKDAY(TODAY()))',
$start_cell, $start_cell;
}
elsif ( $param->{criteria} eq 'nextWeek' ) {
$param->{formula} =
sprintf 'AND(ROUNDDOWN(%s,0)-TODAY()>(7-WEEKDAY(TODAY())),'
. 'ROUNDDOWN(%s,0)-TODAY()<(15-WEEKDAY(TODAY())))',
$start_cell, $start_cell;
}
elsif ( $param->{criteria} eq 'lastMonth' ) {
$param->{formula} =
sprintf
'AND(MONTH(%s)=MONTH(TODAY())-1,OR(YEAR(%s)=YEAR(TODAY()),'
. 'AND(MONTH(%s)=1,YEAR(A1)=YEAR(TODAY())-1)))',
$start_cell, $start_cell, $start_cell;
}
elsif ( $param->{criteria} eq 'thisMonth' ) {
$param->{formula} =
sprintf 'AND(MONTH(%s)=MONTH(TODAY()),YEAR(%s)=YEAR(TODAY()))',
$start_cell, $start_cell;
}
elsif ( $param->{criteria} eq 'nextMonth' ) {
$param->{formula} =
sprintf
'AND(MONTH(%s)=MONTH(TODAY())+1,OR(YEAR(%s)=YEAR(TODAY()),'
. 'AND(MONTH(%s)=12,YEAR(%s)=YEAR(TODAY())+1)))',
$start_cell, $start_cell, $start_cell, $start_cell;
}
else {
carp "Invalid time_period criteria '$param->{criteria}' "
. "in conditional_formatting()";
}
}
# Special handling of blanks/error types.
if ( $param->{type} eq 'containsBlanks' ) {
$param->{formula} = sprintf 'LEN(TRIM(%s))=0', $start_cell;
}
if ( $param->{type} eq 'notContainsBlanks' ) {
$param->{formula} = sprintf 'LEN(TRIM(%s))>0', $start_cell;
}
if ( $param->{type} eq 'containsErrors' ) {
$param->{formula} = sprintf 'ISERROR(%s)', $start_cell;
}
if ( $param->{type} eq 'notContainsErrors' ) {
$param->{formula} = sprintf 'NOT(ISERROR(%s))', $start_cell;
}
# Special handling for 2 color scale.
if ( $param->{type} eq '2_color_scale' ) {
$param->{type} = 'colorScale';
# Color scales don't use any additional formatting.
$param->{format} = undef;
# Turn off 3 color parameters.
$param->{mid_type} = undef;
$param->{mid_color} = undef;
$param->{min_type} ||= 'min';
$param->{max_type} ||= 'max';
$param->{min_value} ||= 0;
$param->{max_value} ||= 0;
$param->{min_color} ||= '#FF7128';
$param->{max_color} ||= '#FFEF9C';
$param->{max_color} = $self->_get_palette_color( $param->{max_color} );
$param->{min_color} = $self->_get_palette_color( $param->{min_color} );
}
# Special handling for 3 color scale.
if ( $param->{type} eq '3_color_scale' ) {
$param->{type} = 'colorScale';
# Color scales don't use any additional formatting.
$param->{format} = undef;
$param->{min_type} ||= 'min';
$param->{mid_type} ||= 'percentile';
$param->{max_type} ||= 'max';
$param->{min_value} ||= 0;
$param->{mid_value} = 50 unless defined $param->{mid_value};
$param->{max_value} ||= 0;
$param->{min_color} ||= '#F8696B';
$param->{mid_color} ||= '#FFEB84';
$param->{max_color} ||= '#63BE7B';
$param->{max_color} = $self->_get_palette_color( $param->{max_color} );
$param->{mid_color} = $self->_get_palette_color( $param->{mid_color} );
$param->{min_color} = $self->_get_palette_color( $param->{min_color} );
}
# Special handling for data bar.
if ( $param->{type} eq 'dataBar' ) {
# Excel 2007 data bars don't use any additional formatting.
$param->{format} = undef;
if ( !defined $param->{min_type} ) {
$param->{min_type} = 'min';
$param->{_x14_min_type} = 'autoMin';
}
else {
$param->{_x14_min_type} = $param->{min_type};
}
if ( !defined $param->{max_type} ) {
$param->{max_type} = 'max';
$param->{_x14_max_type} = 'autoMax';
}
else {
$param->{_x14_max_type} = $param->{max_type};
}
$param->{min_value} ||= 0;
$param->{max_value} ||= 0;
$param->{bar_color} ||= '#638EC6';
$param->{bar_border_color} ||= $param->{bar_color};
$param->{bar_only} ||= 0;
$param->{bar_no_border} ||= 0;
$param->{bar_solid} ||= 0;
$param->{bar_direction} ||= '';
$param->{bar_negative_color} ||= '#FF0000';
$param->{bar_negative_border_color} ||= '#FF0000';
$param->{bar_negative_color_same} ||= 0;
$param->{bar_negative_border_color_same} ||= 0;
$param->{bar_axis_position} ||= '';
$param->{bar_axis_color} ||= '#000000';
$param->{bar_color} =
$self->_get_palette_color( $param->{bar_color} );
$param->{bar_border_color} =
$self->_get_palette_color( $param->{bar_border_color} );
$param->{bar_negative_color} =
$self->_get_palette_color( $param->{bar_negative_color} );
$param->{bar_negative_border_color} =
$self->_get_palette_color( $param->{bar_negative_border_color} );
$param->{bar_axis_color} =
$self->_get_palette_color( $param->{bar_axis_color} );
}
# Adjust for 2010 style data_bar parameters.
if ( $param->{_is_data_bar_2010} ) {
$self->{_excel_version} = 2010;
if ( $param->{min_type} eq 'min' && $param->{min_value} == 0 ) {
$param->{min_value} = undef;
}
if ( $param->{max_type} eq 'max' && $param->{max_value} == 0 ) {
$param->{max_value} = undef;
}
# Store range for Excel 2010 data bars.
$param->{_range} = $range;
}
# Strip the leading = from formulas.
$param->{min_value} =~ s/^=// if defined $param->{min_value};
$param->{mid_value} =~ s/^=// if defined $param->{mid_value};
$param->{max_value} =~ s/^=// if defined $param->{max_value};
# Store the validation information until we close the worksheet.
push @{ $self->{_cond_formats}->{$range} }, $param;
}
###############################################################################
#
# Set the sub-properties for icons.
#
sub _set_icon_properties {
my $self = shift;
my $total_icons = shift;
my $user_props = shift;
my $props = [];
# Set the default icon properties.
for ( 0 .. $total_icons - 1 ) {
push @$props,
{
criteria => 0,
value => 0,
type => 'percent'
};
}
# Set the default icon values based on the number of icons.
if ( $total_icons == 3 ) {
$props->[0]->{value} = 67;
$props->[1]->{value} = 33;
}
if ( $total_icons == 4 ) {
$props->[0]->{value} = 75;
$props->[1]->{value} = 50;
$props->[2]->{value} = 25;
}
if ( $total_icons == 5 ) {
$props->[0]->{value} = 80;
$props->[1]->{value} = 60;
$props->[2]->{value} = 40;
$props->[3]->{value} = 20;
}
# Overwrite default properties with user defined properties.
if ( defined $user_props ) {
# Ensure we don't set user properties for lowest icon.
my $max_data = @$user_props;
if ( $max_data >= $total_icons ) {
$max_data = $total_icons -1;
}
for my $i ( 0 .. $max_data - 1 ) {
# Set the user defined 'value' property.
if ( defined $user_props->[$i]->{value} ) {
$props->[$i]->{value} = $user_props->[$i]->{value};
$props->[$i]->{value} =~ s/^=//;
}
# Set the user defined 'type' property.
if ( defined $user_props->[$i]->{type} ) {
my $type = $user_props->[$i]->{type};
if ( $type ne 'percent'
&& $type ne 'percentile'
&& $type ne 'number'
&& $type ne 'formula' )
{
carp "Unknown icon property type '$props->{type}' for sub-"
. "property 'type' in conditional_formatting()";
}
else {
$props->[$i]->{type} = $type;
if ( $props->[$i]->{type} eq 'number' ) {
$props->[$i]->{type} = 'num';
}
}
}
# Set the user defined 'criteria' property.
if ( defined $user_props->[$i]->{criteria}
&& $user_props->[$i]->{criteria} eq '>' )
{
$props->[$i]->{criteria} = 1;
}
}
}
return $props;
}
###############################################################################
#
# add_table()
#
# Add an Excel table to a worksheet.
#
sub add_table {
my $self = shift;
my $user_range = '';
my %table;
my @col_formats;
# We would need to order the write statements very carefully within this
# function to support optimisation mode. Disable add_table() when it is
# on for now.
if ( $self->{_optimization} == 1 ) {
carp "add_table() isn't supported when set_optimization() is on";
return -1;
}
# Check for a cell reference in A1 notation and substitute row and column
if ( @_ && $_[0] =~ /^\D/ ) {
@_ = $self->_substitute_cellref( @_ );
}
# Check for a valid number of args.
if ( @_ < 4 ) {
carp "Not enough parameters to add_table()";
return -1;
}
lib/Excel/Writer/XLSX/Worksheet.pm view on Meta::CPAN
if ( $name =~ m/^[a-zA-Z][a-zA-Z]?[a-dA-D]?[0-9]+$/ ) {
carp "Invalid name '$name' looks like a cell name in add_table()";
return -3;
}
# Warn if the name looks like a R1C1.
if ( $name =~ m/^[rcRC]$/ || $name =~ m/^[rcRC]\d+[rcRC]\d+$/ ) {
carp "Invalid name '$name' like a RC cell ref in add_table()";
return -3;
}
$table{_name} = $param->{name};
}
# Set the table style.
if ( defined $param->{style} ) {
$table{_style} = $param->{style};
# Remove whitespace from style name.
$table{_style} =~ s/\s//g;
}
else {
$table{_style} = "TableStyleMedium9";
}
# Set the data range rows (without the header and footer).
my $first_data_row = $row1;
my $last_data_row = $row2;
$first_data_row++ if $param->{header_row};
$last_data_row-- if $param->{total_row};
# Set the table and autofilter ranges.
$table{_range} = xl_range( $row1, $row2, $col1, $col2 );
$table{_a_range} = xl_range( $row1, $last_data_row, $col1, $col2 );
# If the header row if off the default is to turn autofilter off.
if ( !$param->{header_row} ) {
$param->{autofilter} = 0;
}
# Set the autofilter range.
if ( $param->{autofilter} ) {
$table{_autofilter} = $table{_a_range};
}
# Add the table columns.
my %seen_names;
my $col_id = 1;
for my $col_num ( $col1 .. $col2 ) {
# Set up the default column data.
my $col_data = {
_id => $col_id,
_name => 'Column' . $col_id,
_total_string => '',
_total_function => '',
_custom_total => '',
_formula => '',
_format => undef,
_name_format => undef,
};
# Overwrite the defaults with any use defined values.
if ( $param->{columns} ) {
# Check if there are user defined values for this column.
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 "" )
{
$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} ) {
carp "add_table() contains duplicate name: '$name'";
return -1;
}
else {
$seen_names{$key} = 1;
}
# Get the header format if defined.
$col_data->{_name_format} = $user_data->{header_format};
# Handle the column formula.
if ( $user_data->{formula} ) {
my $formula = $user_data->{formula};
# Remove the leading = from formula.
$formula =~ s/^=//;
# Covert Excel 2010 "@" ref to 2007 "#This Row".
$formula =~ s/@/[#This Row],/g;
# Escape any future functions.
$formula = $self->_prepare_formula($formula, 1);
$col_data->{_formula} = $formula;
# We write the formulas below after the table data.
}
# Handle the function for the total row.
if ( $user_data->{total_function} ) {
my $formula = '';
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,
);
if ( exists $subtotals{$function} ) {
$formula =
_table_function_to_formula( $function,
$col_data->{_name} );
}
else {
$formula = $self->_prepare_formula($function, 1);
$col_data->{_custom_total} = $formula;
$function = 'custom';
}
$col_data->{_total_function} = $function;
my $value = $user_data->{total_value} || 0;
$self->write_formula( $row2, $col_num, $formula,
$user_data->{format}, $value );
}
elsif ( $user_data->{total_string} ) {
# Total label only (not a function).
my $total_string = $user_data->{total_string};
$col_data->{_total_string} = $total_string;
$self->write_string( $row2, $col_num, $total_string,
$user_data->{format} );
}
# Get the dxf format index.
if ( defined $user_data->{format} && ref $user_data->{format} )
{
$col_data->{_format} =
$user_data->{format}->get_dxf_index();
}
# Store the column format for writing the cell data.
# It doesn't matter if it is undefined.
$col_formats[ $col_id - 1 ] = $user_data->{format};
}
}
# Store the column data.
push @{ $table{_columns} }, $col_data;
# Write the column headers to the worksheet.
if ( $param->{header_row} ) {
$self->write_string( $row1, $col_num, $col_data->{_name},
$col_data->{_name_format} );
}
$col_id++;
} # Table columns.
# Write the cell data if supplied.
if ( my $data = $param->{data} ) {
my $i = 0; # For indexing the row data.
for my $row ( $first_data_row .. $last_data_row ) {
my $j = 0; # For indexing the col data.
for my $col ( $col1 .. $col2 ) {
my $token = $data->[$i]->[$j];
if ( defined $token ) {
$self->write( $row, $col, $token, $col_formats[$j] );
}
$j++;
}
$i++;
}
}
# Write any columns formulas after the user supplied table data to
# overwrite it if required.
$col_id = 0;
for my $col_num ( $col1 .. $col2 ) {
my $column_data = $table{_columns}->[$col_id];
if ( $column_data && $column_data->{_formula} ) {
my $formula_format = $col_formats[$col_id];
my $formula = $column_data->{_formula};
for my $row ( $first_data_row .. $last_data_row ) {
$self->write_formula( $row, $col_num, $formula,
$formula_format );
}
}
$col_id++;
}
# 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;
}
}
# Store the table data.
push @{ $self->{_tables} }, \%table;
return \%table;
}
###############################################################################
#
# add_sparkline()
#
# Add sparklines to the worksheet.
#
sub add_sparkline {
my $self = shift;
my $param = shift;
my $sparkline = {};
# Check that the last parameter is a hash list.
if ( ref $param ne 'HASH' ) {
carp "Parameter list in add_sparkline() must be a hash ref";
return -1;
}
# List of valid input parameters.
my %valid_parameter = (
location => 1,
range => 1,
type => 1,
high_point => 1,
low_point => 1,
negative_points => 1,
first_point => 1,
last_point => 1,
markers => 1,
style => 1,
series_color => 1,
negative_color => 1,
markers_color => 1,
first_color => 1,
last_color => 1,
high_color => 1,
low_color => 1,
max => 1,
min => 1,
lib/Excel/Writer/XLSX/Worksheet.pm view on Meta::CPAN
}
# 'location' is a required parameter.
if ( not exists $param->{location} ) {
carp "Parameter 'location' is required in add_sparkline()";
return -3;
}
# 'range' is a required parameter.
if ( not exists $param->{range} ) {
carp "Parameter 'range' is required in add_sparkline()";
return -3;
}
# Handle the sparkline type.
my $type = $param->{type} || 'line';
if ( $type ne 'line' && $type ne 'column' && $type ne 'win_loss' ) {
carp "Parameter 'type' must be 'line', 'column' "
. "or 'win_loss' in add_sparkline()";
return -4;
}
$type = 'stacked' if $type eq 'win_loss';
$sparkline->{_type} = $type;
# We handle single location/range values or array refs of values.
if ( ref $param->{location} ) {
$sparkline->{_locations} = $param->{location};
$sparkline->{_ranges} = $param->{range};
}
else {
$sparkline->{_locations} = [ $param->{location} ];
$sparkline->{_ranges} = [ $param->{range} ];
}
my $range_count = @{ $sparkline->{_ranges} };
my $location_count = @{ $sparkline->{_locations} };
# The ranges and locations must match.
if ( $range_count != $location_count ) {
carp "Must have the same number of location and range "
. "parameters in add_sparkline()";
return -5;
}
# Store the count.
$sparkline->{_count} = @{ $sparkline->{_locations} };
# Get the worksheet name for the range conversion below.
my $sheetname = quote_sheetname( $self->{_name} );
# Cleanup the input ranges.
for my $range ( @{ $sparkline->{_ranges} } ) {
# Remove the absolute reference $ symbols.
$range =~ s{\$}{}g;
# Remove the = from xl_range_formula(.
$range =~ s{^=}{};
# Convert a simple range into a full Sheet1!A1:D1 range.
if ( $range !~ /!/ ) {
$range = $sheetname . "!" . $range;
}
}
# Cleanup the input locations.
for my $location ( @{ $sparkline->{_locations} } ) {
$location =~ s{\$}{}g;
}
# Map options.
$sparkline->{_high} = $param->{high_point};
$sparkline->{_low} = $param->{low_point};
$sparkline->{_negative} = $param->{negative_points};
$sparkline->{_first} = $param->{first_point};
$sparkline->{_last} = $param->{last_point};
$sparkline->{_markers} = $param->{markers};
$sparkline->{_min} = $param->{min};
$sparkline->{_max} = $param->{max};
$sparkline->{_axis} = $param->{axis};
$sparkline->{_reverse} = $param->{reverse};
$sparkline->{_hidden} = $param->{show_hidden};
$sparkline->{_weight} = $param->{weight};
# Map empty cells options.
my $empty = $param->{empty_cells} || '';
if ( $empty eq 'zero' ) {
$sparkline->{_empty} = 0;
}
elsif ( $empty eq 'connect' ) {
$sparkline->{_empty} = 'span';
}
else {
$sparkline->{_empty} = 'gap';
}
# Map the date axis range.
my $date_range = $param->{date_axis};
if ( $date_range && $date_range !~ /!/ ) {
$date_range = $sheetname . "!" . $date_range;
}
$sparkline->{_date_axis} = $date_range;
# Set the sparkline styles.
my $style_id = $param->{style} || 0;
my $style = $Excel::Writer::XLSX::Package::Theme::spark_styles[$style_id];
$sparkline->{_series_color} = $style->{series};
$sparkline->{_negative_color} = $style->{negative};
$sparkline->{_markers_color} = $style->{markers};
$sparkline->{_first_color} = $style->{first};
$sparkline->{_last_color} = $style->{last};
$sparkline->{_high_color} = $style->{high};
lib/Excel/Writer/XLSX/Worksheet.pm view on Meta::CPAN
# insert_button()
#
# Insert a button form object into the worksheet.
#
sub insert_button {
my $self = shift;
# Check for a cell reference in A1 notation and substitute row and column
if ( $_[0] =~ /^\D/ ) {
@_ = $self->_substitute_cellref( @_ );
}
# Check the number of args.
if ( @_ < 3 ) { return -1 }
my $button = $self->_button_params( @_ );
push @{ $self->{_buttons_array} }, $button;
$self->{_has_vml} = 1;
}
###############################################################################
#
# set_vba_name()
#
# Set the VBA name for the worksheet.
#
sub set_vba_name {
my $self = shift;
my $vba_codename = shift;
if ( $vba_codename ) {
$self->{_vba_codename} = $vba_codename;
}
else {
$self->{_vba_codename} = "Sheet" . ($self->{_index} + 1);
}
}
###############################################################################
#
# ignore_errors()
#
# Ignore worksheet errors/warnings in user defined ranges.
#
sub ignore_errors {
my $self = shift;
my $ignores = shift;
# List of valid input parameters.
my %valid_parameter = (
number_stored_as_text => 1,
eval_error => 1,
formula_differs => 1,
formula_range => 1,
formula_unlocked => 1,
empty_cell_reference => 1,
list_data_validation => 1,
calculated_column => 1,
two_digit_text_year => 1,
);
for my $param_key ( keys %$ignores ) {
if ( not exists $valid_parameter{$param_key} ) {
carp "Unknown parameter '$param_key' in ignore_errors()";
return -3;
}
}
$self->{_ignore_errors} = {%$ignores};
}
###############################################################################
#
# Internal methods.
#
###############################################################################
###############################################################################
#
# _table_function_to_formula
#
# Convert a table total function to a worksheet formula.
#
sub _table_function_to_formula {
my $function = shift;
my $col_name = shift;
my $formula = '';
# Escape special characters, as required by Excel.
$col_name =~ s/'/''/g;
$col_name =~ s/#/'#/g;
$col_name =~ s/\[/'[/g;
$col_name =~ s/]/']/g;
my %subtotals = (
average => 101,
countNums => 102,
count => 103,
max => 104,
min => 105,
stdDev => 107,
sum => 109,
var => 110,
);
if ( exists $subtotals{$function} ) {
my $func_num = $subtotals{$function};
$formula = qq{SUBTOTAL($func_num,[$col_name])};
}
else {
carp "Unsupported function '$function' in add_table()";
}
return $formula;
}
###############################################################################
#
# _set_spark_color()
#
# Set the sparkline colour.
#
sub _set_spark_color {
my $self = shift;
my $sparkline = shift;
my $param = shift;
my $user_color = shift;
my $spark_color = '_' . $user_color;
return unless $param->{$user_color};
$sparkline->{$spark_color} =
{ _rgb => $self->_get_palette_color( $param->{$user_color} ) };
}
###############################################################################
#
# _get_palette_color()
#
# Convert from an Excel internal colour index to a XML style #RRGGBB index
# based on the default or user defined values in the Workbook palette.
#
sub _get_palette_color {
my $self = shift;
my $index = shift;
my $palette = $self->{_palette};
# Handle colours in #XXXXXX RGB format.
if ( $index =~ m/^#([0-9A-F]{6})$/i ) {
return "FF" . uc( $1 );
}
# Adjust the colour index.
$index -= 8;
# Palette is passed in from the Workbook class.
my @rgb = @{ $palette->[$index] };
return sprintf "FF%02X%02X%02X", @rgb[0, 1, 2];
}
###############################################################################
#
# _substitute_cellref()
#
# Substitute an Excel cell reference in A1 notation for zero based row and
# column values in an argument list.
#
# Ex: ("A4", "Hello") is converted to (3, 0, "Hello").
lib/Excel/Writer/XLSX/Worksheet.pm view on Meta::CPAN
push @{ $self->{_drawing_links} },
[ '/chart', '../charts/chart' . $chart_id . '.xml' ];
}
###############################################################################
#
# _get_range_data
#
# Returns a range of data from the worksheet _table to be used in chart
# cached data. Strings are returned as SST ids and decoded in the workbook.
# Return undefs for data that doesn't exist since Excel can chart series
# with data missing.
#
sub _get_range_data {
my $self = shift;
return () if $self->{_optimization};
my @data;
my ( $row_start, $col_start, $row_end, $col_end ) = @_;
# TODO. Check for worksheet limits.
# Iterate through the table data.
for my $row_num ( $row_start .. $row_end ) {
# Store undef if row doesn't exist.
if ( !exists $self->{_table}->{$row_num} ) {
push @data, undef;
next;
}
for my $col_num ( $col_start .. $col_end ) {
if ( my $cell = $self->{_table}->{$row_num}->{$col_num} ) {
my $type = $cell->[0];
my $token = $cell->[1];
if ( $type eq 'n' || $type eq 't') {
# Store a number.
push @data, $token;
}
elsif ( $type eq 's' || $type eq 'r' ) {
# Store a string.
if ( $self->{_optimization} == 0 ) {
push @data, { 'sst_id' => $token };
}
else {
push @data, $token;
}
}
elsif ( $type eq 'f' ) {
# Store a formula.
push @data, $cell->[3] || 0;
}
elsif ( $type eq 'a' || $type eq 'd') {
# Store an array formula.
push @data, $cell->[4] || 0;
}
elsif ( $type eq 'b' ) {
# Store a empty cell.
push @data, '';
}
}
else {
# Store undef if col doesn't exist.
push @data, undef;
}
}
}
return @data;
}
###############################################################################
#
# insert_image( $row, $col, $filename, $options )
#
# Insert an image into the worksheet.
#
sub insert_image {
my $self = shift;
# Check for a cell reference in A1 notation and substitute row and column.
if ( $_[0] =~ /^\D/ ) {
@_ = $self->_substitute_cellref( @_ );
}
my $row = $_[0];
my $col = $_[1];
my $image = $_[2];
my $x_offset;
my $y_offset;
my $x_scale;
my $y_scale;
my $anchor;
my $url;
my $tip;
my $description;
my $decorative;
if ( ref $_[3] eq 'HASH' ) {
# Newer hashref bashed options.
my $options = $_[3];
$x_offset = $options->{x_offset} || 0;
$y_offset = $options->{y_offset} || 0;
$x_scale = $options->{x_scale} || 1;
$y_scale = $options->{y_scale} || 1;
$anchor = $options->{object_position} || 2;
$url = $options->{url};
$tip = $options->{tip};
$description = $options->{description};
$decorative = $options->{decorative};
lib/Excel/Writer/XLSX/Worksheet.pm view on Meta::CPAN
my @vertices = $self->_position_object_pixels(
$params->{start_col}, $params->{start_row}, $params->{x_offset},
$params->{y_offset}, $params->{width}, $params->{height}
);
# Add the width and height for VML.
push @vertices, ( $params->{width}, $params->{height} );
$button->{_vertices} = \@vertices;
return $button;
}
###############################################################################
#
# Deprecated methods for backwards compatibility.
#
###############################################################################
# This method was mainly only required for Excel 5.
sub write_url_range { }
# Deprecated UTF-16 method required for the Excel 5 format.
sub write_utf16be_string {
my $self = shift;
# Convert A1 notation if present.
@_ = $self->_substitute_cellref( @_ ) if $_[0] =~ /^\D/;
# Check the number of args.
return -1 if @_ < 3;
# Convert UTF16 string to UTF8.
require Encode;
my $utf8_string = Encode::decode( 'UTF-16BE', $_[2] );
return $self->write_string( $_[0], $_[1], $utf8_string, $_[3] );
}
# Deprecated UTF-16 method required for the Excel 5 format.
sub write_utf16le_string {
my $self = shift;
# Convert A1 notation if present.
@_ = $self->_substitute_cellref( @_ ) if $_[0] =~ /^\D/;
# Check the number of args.
return -1 if @_ < 3;
# Convert UTF16 string to UTF8.
require Encode;
my $utf8_string = Encode::decode( 'UTF-16LE', $_[2] );
return $self->write_string( $_[0], $_[1], $utf8_string, $_[3] );
}
# No longer required. Was used to avoid slow formula parsing.
sub store_formula {
my $self = shift;
my $string = shift;
my @tokens = split /(\$?[A-I]?[A-Z]\$?\d+)/, $string;
return \@tokens;
}
# No longer required. Was used to avoid slow formula parsing.
sub repeat_formula {
my $self = shift;
# Convert A1 notation if present.
@_ = $self->_substitute_cellref( @_ ) if $_[0] =~ /^\D/;
if ( @_ < 2 ) { return -1 } # Check the number of args
my $row = shift; # Zero indexed row
my $col = shift; # Zero indexed column
my $formula_ref = shift; # Array ref with formula tokens
my $format = shift; # XF format
my @pairs = @_; # Pattern/replacement pairs
# Enforce an even number of arguments in the pattern/replacement list.
croak "Odd number of elements in pattern/replacement list" if @pairs % 2;
# Check that $formula is an array ref.
croak "Not a valid formula" if ref $formula_ref ne 'ARRAY';
my @tokens = @$formula_ref;
# Allow the user to specify the result of the formula by appending a
# result => $value pair to the end of the arguments.
my $value = undef;
if ( @pairs && $pairs[-2] eq 'result' ) {
$value = pop @pairs;
pop @pairs;
}
# Make the substitutions.
while ( @pairs ) {
my $pattern = shift @pairs;
my $replace = shift @pairs;
foreach my $token ( @tokens ) {
last if $token =~ s/$pattern/$replace/;
}
}
my $formula = join '', @tokens;
return $self->write_formula( $row, $col, $formula, $format, $value );
}
# Helper function to compare adjacent column information structures.
sub _compare_col_info {
my $col_options = shift;
my $previous_options = shift;
if ( defined $col_options->[0] != defined $previous_options->[0] ) {
return undef;
}
if ( defined $col_options->[0]
&& defined $previous_options->[0]
&& $col_options->[0] != $previous_options->[0] )
{
return undef;
}
if ( defined $col_options->[1] != defined $previous_options->[1] ) {
return undef;
}
if ( defined $col_options->[1]
&& defined $previous_options->[1]
&& $col_options->[1] != $previous_options->[1] )
{
return undef;
}
return undef if $col_options->[2] != $previous_options->[2];
return undef if $col_options->[3] != $previous_options->[3];
return undef if $col_options->[4] != $previous_options->[4];
return undef if $col_options->[5] != $previous_options->[5];
return 1;
}
###############################################################################
#
# XML writing methods.
#
###############################################################################
###############################################################################
#
# _write_worksheet()
#
# Write the <worksheet> element. This is the root element of Worksheet.
#
sub _write_worksheet {
my $self = shift;
my $schema = 'http://schemas.openxmlformats.org/';
my $xmlns = $schema . 'spreadsheetml/2006/main';
my $xmlns_r = $schema . 'officeDocument/2006/relationships';
my $xmlns_mc = $schema . 'markup-compatibility/2006';
lib/Excel/Writer/XLSX/Worksheet.pm view on Meta::CPAN
push @attributes, ( 'hidden' => 1 ) if $hidden;
# Only add customFormat parameter if the height is non-default.
if (
$height != $self->{_original_row_height}
|| ( $height == $self->{_original_row_height}
&& $height != $self->{_default_row_height} )
)
{
push @attributes, ( 'customHeight' => 1 );
}
push @attributes, ( 'outlineLevel' => $level ) if $level;
push @attributes, ( 'collapsed' => 1 ) if $collapsed;
if ( $self->{_excel_version} == 2010 ) {
push @attributes, ( 'x14ac:dyDescent' => '0.25' );
}
if ( $empty_row ) {
$self->xml_empty_tag_unencoded( 'row', @attributes );
}
else {
$self->xml_start_tag_unencoded( 'row', @attributes );
}
}
###############################################################################
#
# _write_empty_row()
#
# Write and empty <row> element, i.e., attributes only, no cell data.
#
sub _write_empty_row {
my $self = shift;
# Set the $empty_row parameter.
$_[7] = 1;
$self->_write_row( @_ );
}
###############################################################################
#
# _write_cell()
#
# Write the <cell> element. This is the innermost loop so efficiency is
# important where possible. The basic methodology is that the data of every
# cell type is passed in as follows:
#
# [ $row, $col, $aref]
#
# The aref, called $cell below, contains the following structure in all types:
#
# [ $type, $token, $xf, @args ]
#
# Where $type: represents the cell type, such as string, number, formula, etc.
# $token: is the actual data for the string, number, formula, etc.
# $xf: is the XF format object.
# @args: additional args relevant to the specific data type.
#
sub _write_cell {
my $self = shift;
my $row = shift;
my $col = shift;
my $cell = shift;
my $type = $cell->[0];
my $token = $cell->[1];
my $xf = $cell->[2];
my $xf_index = 0;
my %error_codes = (
'#DIV/0!' => 1,
'#N/A' => 1,
'#NAME?' => 1,
'#NULL!' => 1,
'#NUM!' => 1,
'#REF!' => 1,
'#VALUE!' => 1,
);
my %boolean = ( 'TRUE' => 1, 'FALSE' => 0 );
# Get the format index.
if ( ref( $xf ) ) {
$xf_index = $xf->get_xf_index();
}
my $range = _xl_rowcol_to_cell( $row, $col );
my @attributes = ( 'r' => $range );
# Add the cell format index.
if ( $xf_index ) {
push @attributes, ( 's' => $xf_index );
}
elsif ( $self->{_set_rows}->{$row} && $self->{_set_rows}->{$row}->[1] ) {
my $row_xf = $self->{_set_rows}->{$row}->[1];
push @attributes, ( 's' => $row_xf->get_xf_index() );
}
elsif ( $self->{_col_info}->{$col} ) {
my $col_xf = $self->{_col_info}->{$col}->[1];
if (defined $col_xf) {
push @attributes, ( 's' => $col_xf->get_xf_index() );
}
}
# Write the various cell types.
if ( $type eq 'n' || $type eq 't' ) {
# Write a number.
$self->xml_number_element( $token, @attributes );
}
elsif ( $type eq 's' || $type eq 'r' ) {
# Write a string.
if ( $self->{_optimization} == 0 ) {
$self->xml_string_element( $token, @attributes );
}
else {
my $string = $token;
# Escape control characters. See SharedString.pm for details.
$string =~ s/(_x[0-9a-fA-F]{4}_)/_x005F$1/g;
$string =~ s/([\x00-\x08\x0B-\x1F])/sprintf "_x%04X_", ord($1)/eg;
# Write any rich strings without further tags.
if ( $string =~ m{^<r>} && $string =~ m{</r>$} ) {
$self->xml_rich_inline_string( $string, @attributes );
}
else {
# Add attribute to preserve leading or trailing whitespace.
my $preserve = 0;
if ( $string =~ /^\s/ || $string =~ /\s$/ ) {
$preserve = 1;
}
$self->xml_inline_string( $string, $preserve, @attributes );
}
}
}
elsif ( $type eq 'f' ) {
# Write a formula.
my $value = $cell->[3];
$value = 0 if !defined $value;
# Check if the formula value is a string.
if ( $value
&& $value !~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ )
{
if ( exists $boolean{$value} ) {
push @attributes, ( 't' => 'b' );
$value = $boolean{$value};
}
elsif ( exists $error_codes{$value} ) {
push @attributes, ( 't' => 'e' );
}
else {
push @attributes, ( 't' => 'str' );
$value = Excel::Writer::XLSX::Package::XMLwriter::_escape_data(
$value );
}
}
$self->xml_formula_element( $token, $value, @attributes );
}
elsif ( $type eq 'a' || $type eq 'd') {
# Add metadata linkage for dynamic array formulas.
if ($type eq 'd') {
push @attributes, ( 'cm' => '1' );
}
# Write an array formula.
$self->xml_start_tag( 'c', @attributes );
$self->_write_cell_array_formula( $token, $cell->[3] );
$self->_write_cell_value( $cell->[4] );
$self->xml_end_tag( 'c' );
}
elsif ( $type eq 'l' ) {
# Write a boolean value.
push @attributes, ( 't' => 'b' );
$self->xml_start_tag( 'c', @attributes );
$self->_write_cell_value( $cell->[1] );
$self->xml_end_tag( 'c' );
}
elsif ( $type eq 'b' ) {
# Write a empty cell.
$self->xml_empty_tag( 'c', @attributes );
}
elsif ( $type eq 'e' ) {
# Write a error value (mainly for embedded images).
push @attributes, ( 't' => 'e' );
push @attributes, ( 'vm' => $cell->[3] );
$self->xml_start_tag( 'c', @attributes );
$self->_write_cell_value( $cell->[1] );
$self->xml_end_tag( 'c' );
}
}
###############################################################################
#
# _write_cell_value()
#
# Write the cell value <v> element.
#
sub _write_cell_value {
my $self = shift;
my $value = defined $_[0] ? $_[0] : '';
$self->xml_data_element( 'v', $value );
}
###############################################################################
#
# _write_cell_formula()
#
# Write the cell formula <f> element.
#
sub _write_cell_formula {
my $self = shift;
my $formula = defined $_[0] ? $_[0] : '';
$self->xml_data_element( 'f', $formula );
}
###############################################################################
#
# _write_cell_array_formula()
#
# Write the cell array formula <f> element.
#
sub _write_cell_array_formula {
my $self = shift;
my $formula = shift;
my $range = shift;
my @attributes = ( 't' => 'array', 'ref' => $range );
$self->xml_data_element( 'f', $formula, @attributes );
}
##############################################################################
#
# _write_sheet_calc_pr()
#
# Write the <sheetCalcPr> element for the worksheet calculation properties.
#
sub _write_sheet_calc_pr {
my $self = shift;
my $full_calc_on_load = 1;
my @attributes = ( 'fullCalcOnLoad' => $full_calc_on_load );
$self->xml_empty_tag( 'sheetCalcPr', @attributes );
}
###############################################################################
#
# _write_phonetic_pr()
#
# Write the <phoneticPr> element.
#
sub _write_phonetic_pr {
my $self = shift;
my $font_id = 0;
my $type = 'noConversion';
my @attributes = (
'fontId' => $font_id,
'type' => $type,
);
$self->xml_empty_tag( 'phoneticPr', @attributes );
}
###############################################################################
#
# _write_page_margins()
#
# Write the <pageMargins> element.
#
sub _write_page_margins {
my $self = shift;
my @attributes = (
'left' => $self->{_margin_left},
'right' => $self->{_margin_right},
'top' => $self->{_margin_top},
'bottom' => $self->{_margin_bottom},
'header' => $self->{_margin_header},
'footer' => $self->{_margin_footer},
);
lib/Excel/Writer/XLSX/Worksheet.pm view on Meta::CPAN
# Add a space between multiple cell ranges.
$sqref .= ' ' if $sqref ne '';
my ( $row_first, $col_first, $row_last, $col_last ) = @$cells;
# Swap last row/col for first row/col as necessary
if ( $row_first > $row_last ) {
( $row_first, $row_last ) = ( $row_last, $row_first );
}
if ( $col_first > $col_last ) {
( $col_first, $col_last ) = ( $col_last, $col_first );
}
$sqref .= xl_range( $row_first, $row_last, $col_first, $col_last );
}
if ( $param->{validate} ne 'none' ) {
push @attributes, ( 'type' => $param->{validate} );
if ( $param->{criteria} ne 'between' ) {
push @attributes, ( 'operator' => $param->{criteria} );
}
}
if ( $param->{error_type} ) {
push @attributes, ( 'errorStyle' => 'warning' )
if $param->{error_type} == 1;
push @attributes, ( 'errorStyle' => 'information' )
if $param->{error_type} == 2;
}
push @attributes, ( 'allowBlank' => 1 ) if $param->{ignore_blank};
push @attributes, ( 'showDropDown' => 1 ) if !$param->{dropdown};
push @attributes, ( 'showInputMessage' => 1 ) if $param->{show_input};
push @attributes, ( 'showErrorMessage' => 1 ) if $param->{show_error};
push @attributes, ( 'errorTitle' => $param->{error_title} )
if $param->{error_title};
push @attributes, ( 'error' => $param->{error_message} )
if $param->{error_message};
push @attributes, ( 'promptTitle' => $param->{input_title} )
if $param->{input_title};
push @attributes, ( 'prompt' => $param->{input_message} )
if $param->{input_message};
push @attributes, ( 'sqref' => $sqref );
if ( $param->{validate} eq 'none' ) {
$self->xml_empty_tag( 'dataValidation', @attributes );
}
else {
$self->xml_start_tag( 'dataValidation', @attributes );
# Write the formula1 element.
$self->_write_formula_1( $param->{value} );
# Write the formula2 element.
$self->_write_formula_2( $param->{maximum} )
if defined $param->{maximum};
$self->xml_end_tag( 'dataValidation' );
}
}
##############################################################################
#
# _write_formula_1()
#
# Write the <formula1> element.
#
sub _write_formula_1 {
my $self = shift;
my $formula = shift;
# Convert a list array ref into a comma separated string.
if ( ref $formula eq 'ARRAY' ) {
$formula = join ',', @$formula;
$formula = qq("$formula");
}
$formula =~ s/^=//; # Remove formula symbol.
$self->xml_data_element( 'formula1', $formula );
}
##############################################################################
#
# _write_formula_2()
#
# Write the <formula2> element.
#
sub _write_formula_2 {
my $self = shift;
my $formula = shift;
$formula =~ s/^=//; # Remove formula symbol.
$self->xml_data_element( 'formula2', $formula );
}
##############################################################################
#
# _write_conditional_formats()
#
# Write the Worksheet conditional formats.
#
sub _write_conditional_formats {
my $self = shift;
my @ranges = sort keys %{ $self->{_cond_formats} };
return unless scalar @ranges;
for my $range ( @ranges ) {
$self->_write_conditional_formatting( $range,
$self->{_cond_formats}->{$range} );
}
}
##############################################################################
#
# _write_conditional_formatting()
#
# Write the <conditionalFormatting> element.
#
sub _write_conditional_formatting {
my $self = shift;
my $range = shift;
my $params = shift;
my @attributes = ( 'sqref' => $range );
$self->xml_start_tag( 'conditionalFormatting', @attributes );
for my $param ( @$params ) {
# Write the cfRule element.
$self->_write_cf_rule( $param );
}
$self->xml_end_tag( 'conditionalFormatting' );
}
##############################################################################
#
# _write_cf_rule()
#
# Write the <cfRule> element.
#
sub _write_cf_rule {
my $self = shift;
my $param = shift;
my @attributes = ( 'type' => $param->{type} );
push @attributes, ( 'dxfId' => $param->{format} )
if defined $param->{format};
push @attributes, ( 'priority' => $param->{priority} );
push @attributes, ( 'stopIfTrue' => 1 )
if $param->{stop_if_true};
if ( $param->{type} eq 'cellIs' ) {
push @attributes, ( 'operator' => $param->{criteria} );
$self->xml_start_tag( 'cfRule', @attributes );
if ( defined $param->{minimum} && defined $param->{maximum} ) {
$self->_write_formula( $param->{minimum} );
$self->_write_formula( $param->{maximum} );
}
else {
my $value = $param->{value};
# String "Cell" values must be quoted, apart from ranges.
if ( $value !~ /^(\$?)([A-Z]{1,3})(\$?)(\d+)$/
&& $value !~
/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ )
{
if ( $value !~ /^".*"$/ ) {
$value = qq("$value");
}
}
$self->_write_formula( $value );
}
$self->xml_end_tag( 'cfRule' );
}
elsif ( $param->{type} eq 'aboveAverage' ) {
if ( $param->{criteria} =~ /below/ ) {
push @attributes, ( 'aboveAverage' => 0 );
}
if ( $param->{criteria} =~ /equal/ ) {
push @attributes, ( 'equalAverage' => 1 );
}
if ( $param->{criteria} =~ /([123]) std dev/ ) {
push @attributes, ( 'stdDev' => $1 );
}
$self->xml_empty_tag( 'cfRule', @attributes );
}
elsif ( $param->{type} eq 'top10' ) {
if ( defined $param->{criteria} && $param->{criteria} eq '%' ) {
push @attributes, ( 'percent' => 1 );
}
if ( $param->{direction} ) {
push @attributes, ( 'bottom' => 1 );
}
my $rank = $param->{value} || 10;
push @attributes, ( 'rank' => $rank );
$self->xml_empty_tag( 'cfRule', @attributes );
}
elsif ( $param->{type} eq 'duplicateValues' ) {
$self->xml_empty_tag( 'cfRule', @attributes );
}
elsif ( $param->{type} eq 'uniqueValues' ) {
$self->xml_empty_tag( 'cfRule', @attributes );
}
elsif ($param->{type} eq 'containsText'
|| $param->{type} eq 'notContainsText'
|| $param->{type} eq 'beginsWith'
|| $param->{type} eq 'endsWith' )
{
push @attributes, ( 'operator' => $param->{criteria} );
push @attributes, ( 'text' => $param->{value} );
$self->xml_start_tag( 'cfRule', @attributes );
$self->_write_formula( $param->{formula} );
$self->xml_end_tag( 'cfRule' );
}
elsif ( $param->{type} eq 'timePeriod' ) {
push @attributes, ( 'timePeriod' => $param->{criteria} );
$self->xml_start_tag( 'cfRule', @attributes );
$self->_write_formula( $param->{formula} );
$self->xml_end_tag( 'cfRule' );
}
elsif ($param->{type} eq 'containsBlanks'
|| $param->{type} eq 'notContainsBlanks'
|| $param->{type} eq 'containsErrors'
|| $param->{type} eq 'notContainsErrors' )
{
$self->xml_start_tag( 'cfRule', @attributes );
$self->_write_formula( $param->{formula} );
$self->xml_end_tag( 'cfRule' );
}
elsif ( $param->{type} eq 'colorScale' ) {
$self->xml_start_tag( 'cfRule', @attributes );
$self->_write_color_scale( $param );
$self->xml_end_tag( 'cfRule' );
}
elsif ( $param->{type} eq 'dataBar' ) {
$self->xml_start_tag( 'cfRule', @attributes );
$self->_write_data_bar( $param );
if ($param->{_is_data_bar_2010}) {
$self->_write_data_bar_ext( $param );
}
$self->xml_end_tag( 'cfRule' );
}
elsif ( $param->{type} eq 'expression' ) {
$self->xml_start_tag( 'cfRule', @attributes );
$self->_write_formula( $param->{criteria} );
$self->xml_end_tag( 'cfRule' );
}
elsif ( $param->{type} eq 'iconSet' ) {
$self->xml_start_tag( 'cfRule', @attributes );
$self->_write_icon_set( $param );
$self->xml_end_tag( 'cfRule' );
}
}
##############################################################################
#
# _write_icon_set()
#
# Write the <iconSet> element.
#
sub _write_icon_set {
my $self = shift;
my $param = shift;
my $icon_style = $param->{icon_style};
my $total_icons = $param->{total_icons};
my $icons = $param->{icons};
my $i;
my @attributes = ();
# Don't set attribute for default style.
if ( $icon_style ne '3TrafficLights' ) {
@attributes = ( 'iconSet' => $icon_style );
}
if ( exists $param->{'icons_only'} && $param->{'icons_only'} ) {
push @attributes, ( 'showValue' => 0 );
}
if ( exists $param->{'reverse_icons'} && $param->{'reverse_icons'} ) {
push @attributes, ( 'reverse' => 1 );
}
$self->xml_start_tag( 'iconSet', @attributes );
# Write the properties for different icon styles.
for my $icon ( reverse @{ $param->{icons} } ) {
$self->_write_cfvo(
$icon->{'type'},
$icon->{'value'},
$icon->{'criteria'}
);
}
$self->xml_end_tag( 'iconSet' );
}
##############################################################################
#
# _write_formula()
#
# Write the <formula> element.
#
sub _write_formula {
my $self = shift;
my $data = shift;
# Remove equality from formula.
$data =~ s/^=//;
$self->xml_data_element( 'formula', $data );
}
##############################################################################
#
# _write_color_scale()
#
# Write the <colorScale> element.
#
sub _write_color_scale {
my $self = shift;
my $param = shift;
$self->xml_start_tag( 'colorScale' );
$self->_write_cfvo( $param->{min_type}, $param->{min_value} );
if ( defined $param->{mid_type} ) {
$self->_write_cfvo( $param->{mid_type}, $param->{mid_value} );
}
$self->_write_cfvo( $param->{max_type}, $param->{max_value} );
$self->_write_color( 'rgb' => $param->{min_color} );
if ( defined $param->{mid_color} ) {
$self->_write_color( 'rgb' => $param->{mid_color} );
}
$self->_write_color( 'rgb' => $param->{max_color} );
$self->xml_end_tag( 'colorScale' );
}
##############################################################################
#
# _write_data_bar()
#
# Write the <dataBar> element.
#
sub _write_data_bar {
my $self = shift;
my $data_bar = shift;
my @attributes = ();
if ( $data_bar->{bar_only} ) {
push @attributes, ( 'showValue', 0 );
}
$self->xml_start_tag( 'dataBar', @attributes );
$self->_write_cfvo( $data_bar->{min_type}, $data_bar->{min_value} );
$self->_write_cfvo( $data_bar->{max_type}, $data_bar->{max_value} );
$self->_write_color( 'rgb' => $data_bar->{bar_color} );
$self->xml_end_tag( 'dataBar' );
lib/Excel/Writer/XLSX/Worksheet.pm view on Meta::CPAN
$self->_write_spark_color( 'x14:colorLast', @_ );
}
##############################################################################
#
# _write_color_high()
#
# Write the <x14:colorHigh> element.
#
sub _write_color_high {
my $self = shift;
$self->_write_spark_color( 'x14:colorHigh', @_ );
}
##############################################################################
#
# _write_color_low()
#
# Write the <x14:colorLow> element.
#
sub _write_color_low {
my $self = shift;
$self->_write_spark_color( 'x14:colorLow', @_ );
}
##############################################################################
#
# _write_ignored_errors()
#
# Write the <ignoredErrors> element.
#
sub _write_ignored_errors {
my $self = shift;
my $ignore = $self->{_ignore_errors};
if ( !defined $ignore ) {
return;
}
$self->xml_start_tag( 'ignoredErrors' );
if ( exists $ignore->{number_stored_as_text} ) {
my $range = $ignore->{number_stored_as_text};
$self->_write_ignored_error( 'numberStoredAsText', $range );
}
if ( exists $ignore->{eval_error} ) {
my $range = $ignore->{eval_error};
$self->_write_ignored_error( 'evalError', $range );
}
if ( exists $ignore->{formula_differs} ) {
my $range = $ignore->{formula_differs};
$self->_write_ignored_error( 'formula', $range );
}
if ( exists $ignore->{formula_range} ) {
my $range = $ignore->{formula_range};
$self->_write_ignored_error( 'formulaRange', $range );
}
if ( exists $ignore->{formula_unlocked} ) {
my $range = $ignore->{formula_unlocked};
$self->_write_ignored_error( 'unlockedFormula', $range );
}
if ( exists $ignore->{empty_cell_reference} ) {
my $range = $ignore->{empty_cell_reference};
$self->_write_ignored_error( 'emptyCellReference', $range );
}
if ( exists $ignore->{list_data_validation} ) {
my $range = $ignore->{list_data_validation};
$self->_write_ignored_error( 'listDataValidation', $range );
}
if ( exists $ignore->{calculated_column} ) {
my $range = $ignore->{calculated_column};
$self->_write_ignored_error( 'calculatedColumn', $range );
}
if ( exists $ignore->{two_digit_text_year} ) {
my $range = $ignore->{two_digit_text_year};
$self->_write_ignored_error( 'twoDigitTextYear', $range );
}
$self->xml_end_tag( 'ignoredErrors' );
}
##############################################################################
#
# _write_ignored_error()
#
# Write the <ignoredError> element.
#
sub _write_ignored_error {
my $self = shift;
my $type = shift;
my $sqref = shift;
my @attributes = (
'sqref' => $sqref,
$type => 1,
);
$self->xml_empty_tag( 'ignoredError', @attributes );
}
1;
__END__
=head1 NAME
Worksheet - A class for writing Excel Worksheets.
=head1 SYNOPSIS
See the documentation for L<Excel::Writer::XLSX>