Merged cells are unmerging on Save, using Spreadsheet::ParseExcel - excel

I am writing a program to parse an .xls file. For that I have a template which contains five merged cells (B1,C1,D1,E1,F1) and written "User-Dependent errors" in that. In B2,C2,D2,E2,F2 I have written the error names and want to save their count every day. The code is working properly but after parsing and saving the merged cell (B1,C1,D1,E1,F1) is getting unmerged and the text is presented in B1. I need the merged cells as is (merged), even after parsing.
What do I have to do?
#!/usr/bin/perl
use strict;
use warnings;
use DBI;
use Spreadsheet::ParseExcel;
use Spreadsheet::ParseExcel::SaveParser;
my $date=$ARGV[1]; #yymmdd
my $hour=$ARGV[0]; #06
$date or $date=`date --date='1 day ago' +%Y%m%d`;
chomp $date;
chomp $hour;
my $db_name = "ravi";
my $table = "CDR";
my $sub_table = "Submission_Failures";
my $del_table = "Delivery_Failures";
my $host = "xxx.xx.x.xxx";
my $command = "cp /root/prac/CDR/CDR.xls /root/prac/CDR/CDR_Report_20$date$hour.xls";
print $command;
`$command`;
sub NULL_count
{
my $type = $_[0];
my #temp_array;
my $error_db = DBI->connect("DBI:mysql:database=$db_name;host=$host;mysql_socket=/opt/lampstack-5.5.27-0/mysql/tmp/mysql.sock","root","", {'RaiseError' => 1});
my $error_sth = $error_db->prepare("SELECT Error_list from error_potrait WHERE Date='$date' and Type='$type'");
$error_sth->execute() or die $DBI::errstr;
while (my $temp = $error_sth->fetchrow_array())
{
push(#temp_array, $temp);
}
my $temp = #temp_array;
foreach my $i ($temp .. 4)
{
$temp_array[$i] = "NULL";
}
$error_sth->finish();
return #temp_array;
}
my #db_system_errors = NULL_count ("Submission_user_error");
my #db_network_errors = NULL_count ("Submission_ESME_error");
my #db_ESME_errors = NULL_count ("Submission_system_error");
my #db_user_errors = NULL_count ("Submission_network_error");
my #del_user_errors = NULL_count ("Delivery_user_error");
my #del_network_errors = NULL_count ("Delivery_network_error");
my #del_system_errors = NULL_count ("Delivery_system_error");
my #submission_errors = (#db_network_errors,#db_system_errors,#db_ESME_errors,#db_user_errors);
my #delivery_errors = (#del_user_errors,#del_network_errors,#del_system_errors);
sub error_headers
{
my $sheet_no = shift;
my #array = #_;
my $row = 1;
my $col = 1;
# Open an existing file with SaveParser
my $parser = Spreadsheet::ParseExcel::SaveParser->new();
my $template = $parser->Parse("CDR_Report_20$date$hour.xls") or die "Cant open xls";
# Get the first worksheet.
my $sheet = $template->worksheet($sheet_no);
$sheet->AddCell( 1, 0, $date );
foreach my $value (#array)
{
$sheet->AddCell( $row, $col, $value );
++$col;
}
$template->SaveAs("CDR_Report_20$date$hour.xls");
}
error_headers (3,#submission_errors);
error_headers (4,#delivery_errors);
sub parser_excel
{
my $sql_comm = $_[0];
my $sheet_no = $_[1];
my $row = $_[2];
my $col = $_[3];
my $dbh = DBI->connect("DBI:mysql:database=$db_name;host=$host;mysql_socket=/opt/lampstack-5.5.27-0/mysql/tmp/mysql.sock","root","", {'RaiseError' => 1});
#Selecting the data to fetch
my $sth = $dbh->prepare("$sql_comm");
$sth->execute() or die $DBI::errstr;
# Open an existing file with SaveParser
my $parser = Spreadsheet::ParseExcel::SaveParser->new();
my $template = $parser->Parse("CDR_Report_20$date$hour.xls") or die "Cant open xls";
# Get the first worksheet.
my $sheet = $template->worksheet($sheet_no);
$sheet->AddCell( $_[4], 0, $date );
while (my #row = $sth->fetchrow_array())
{
my $Date_db = shift #row;
foreach my $value (#row)
{
$sheet->AddCell( $row, $col, $value );
++$col;
}
$row++;
$col=0;
}
$template->SaveAs("CDR_Report_20$date$hour.xls");
$sth->finish();
}
parser_excel("Select * from $table where Date = $date and Hour = $hour",2,1,0,0);
parser_excel("Select * from $sub_table where Date = $date and Hour = $hour",3,2,0,1);
parser_excel("Select * from $del_table where Date = $date and Hour = $hour",4,2,0,1);`

The docs for Spreadsheet::ParseExcel::SaveParser state that the module works "by reading it with Spreadsheet::ParseExcel and rewriting it with Spreadsheet::WriteExcel". So any merged cells will be lost when re-writing. You will need to use the WriteExcel module to re-create the merged cells, which means you will have to separate the reading and writing in your own script.
To merge the cells you use the "merge_range" method with a Format:
my $format = $workbook->add_format( align => 'left' );
$worksheet->merge_range('B1:F1', 'User-Dependent errors', $format);
See the docs for Spreadsheet::WriteExcel

Related

Converting XLSM file to CSV format in Perl using "$csv->print" function?

Does function CSV->PRINT only accept array reference? I am pushing $cell2_value in an array and then printing the array (i.e rows), it would be nice if I can directly write $cell2_value into an opened CSV file.
Things to take care of -
Excel cell having commas in their value would print in double-quotes.
Excel cells having "keyword" in their value would print the whole-cell value in double-quotes and inner quotes will change to ""keyword"".
 
I can write a CSV file with a few undesired outputs of excel cells. its inserting double quotes whenever it sees special characters like / or *.
CSV FILE from the below code:-
"CLASS_A,,x,Singapore,,0xABCF00C4,"/* x2-4Rw */",-,,,,,,,Yes,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"CHECK- ""CLASS_B""- WORKED",1,2,3"
"CLASS_A,,,malyaisa,," 3:0","/* ABCVF */",E,,,,,,,Yes,,,,,,,,,,,,,Yes,,,,,,Yes,Yes,Yes,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"
Desired output
CLASS_A,,x,malyaisa,,0xABCF00C4,/* x2-4Rw */,-,,,,,,,Yes,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"CHECK- ""CLASS_B""- WORKED",1,2,3
CLASS_A,,,malyaisa,, 3:0,/* ABCVF */,E,,,,,,,Yes,,,,,,,,,,,,,Yes,,,,,,Yes,Yes,Yes,,,,,,,,,,,,,,,,,,,
Why it's inserting quotes around each line? Is there any way to remove it?
sub Excel_to_CSV
{
($student_excel_file) = #_;
if($student_excel_file ne "")
{
$student_excel_out_csv_file = $student_excel_file;
$student_excel_out_csv_file =~ s/.xlsm$/_new.csv/;
my $parser_1 = Spreadsheet::ParseXLSX->new();
my $workbook = $parser_1->parse($student_excel_file);
my $csv_1 = Text::CSV->new ({ binary => 1, auto_diag => 1, sep_char => ',' });
open my $fh, ">:encoding(utf-8)", $student_excel_out_csv_file or die "failed to create $student_excel_out_csv_file: $!";
if ( !defined $workbook )
{
die $parser_1->error(), ".\n";
}
my $worksheet=$workbook_->worksheet(0);
my ( $row_min, $row_max ) = $worksheet->row_range();
my ( $col_min, $col_max ) = $worksheet->col_range();
printf("Copyig Sheet: %s from the provided student \n", $worksheet->{Name});
my $concurentEmptyLineCount = 0;
for my $row_1 ( $row_min .. $row_max )
{
my #row_elements_array;
for my $col_1 ( $col_min .. $col_max )
{
my $cell_1 = $worksheet->get_cell( $row_1, 0 );
next unless $cell_1;
$concurentEmptyLineCount=0;
my $cell_2 = $worksheet->get_cell( $row_1, $col_1);
my $cell2_value =$cell_2 -> {Val};
if(defined $cell2_value)
{
push(#row_elements_array, $cell2_value);
}
else
{
my $blank="";
push(#row_elements_array, $blank);
}
}
my $next_line="\n";
push(#row_elements_array, $next_line);
my #temp_row_elements_array= #row_elements_array;
$csv_1->print($fh, \#temp_row_elements_array);
}
close $fh;
}
return $student_excel_out_csv_file;
}
Based on my understanding of your requirements, one solution is to remove the extraneous double-quotes from $cell2_value before pushing it into #row_elements_array. For example:
$cell2_value =~ s/"(")*/$1/g;
push(#row_elements_array, $cell2_value);

Getting the background color of a cell in an existing Excel spreadsheet

I am trying to read an existing Excel spreadsheet and write it into a new Excel document with all the same format options.
I am able to read the cell contents and write them into the new Excel sheet, but I cannot get the background color of the cell.
If I use the $cell->get_format->{Fill}[2] method it gives me numbers like either 64 or 65 if the cell has a background color.
How can I get the actual color of a cell and apply the same background color to the cell in new Excel sheet?
I am doing all these things as there is no method available in the Spreadsheet::ParseExcel module to set the background color for a cell after appending data to an existing Excel sheet.
Here is my code
use Spreadsheet::WriteExcel;
use Spreadsheet::ParseExcel;
my $parser = Spreadsheet::ParseExcel->new();
my $workbook_parse = $parser->Parse( 'Report.xls' );
my $worksheet_parse = $workbook_parse->Worksheet( "Health_Report" );
my ( $col_min, $col_max ) = $worksheet_parse->col_range();
my ( $row_min, $row_max ) = $worksheet_parse->row_range();
my $workbook = Spreadsheet::WriteExcel->new( "Report_new.xls" );
my $worksheet = $workbook->addworksheet( "Health_Report" );
my $bkgd_color = $workbook->addformat();
for my $col ( $col_min .. $col_max ) {
for my $row ( $row_min .. $row_max ) {
# Return the cell object at $row and $col
my $cell = $worksheet_parse->get_cell( $row, $col );
next unless $cell;
my $value = $cell->value();
my $format = $cell->get_format();
my $backgroundcolor = $format->{Fill}->[2];
print "Row, Col = ($row, $col) ";
print "Value = $value\n";
print "Format = $backgroundcolor\n";
$bkgd_color->set_bg_color( $backgroundcolor );
### Here trying to rewrite into Excel and apply the
### same background color which the cell had previously
$worksheet->write( $row, $col, $value, $bkgd_color );
}
}
Part of my output from print statements:
Format = 65
Row, Col = (25, 4) Value = -115966
Format = 65
Row, Col = (10, 5) Value = 20170417
Format = 65
Row, Col = (11, 5) Value = 0
Format = 64
Row, Col = (16, 5) Value = 0
Format = 64
Update
It appears that the documentation is wrong. The name of the workbook object method is different from that of the class method, and you need to call
$workbook->color_idx_to_rgb($color_index)
instead
The documentation for Spreadsheet::ParseExcel says this
$font->{Color}
Returns the color index for the font. The mapping to an RGB color is defined by each workbook.
The index can be converted to a RGB string using the $workbook->ColorIdxToRGB() Parser method.
(Older versions of Spreadsheet::ParseExcel provided the ColorIdxToRGB class method, which is deprecated.)
I expect that the colour indexes in the $format->{Fill} array can be translated similarly
Hello Venkatesh k,
Try the set_color() method from (Spreadsheet::WriteExcel).
From the documentation:
Default state: Excels default color, usually black
Default action: Set the default color
Valid args: Integers from 8..63 or the following strings:
'black'
'blue'
'brown'
'cyan'
'gray'
'green'
'lime'
'magenta'
'navy'
'orange'
'pink'
'purple'
'red'
'silver'
'white'
'yellow'
Update: try this, it seems to be working for my example spread sheet.
#!/usr/bin/perl
use strict;
use warnings;
use Spreadsheet::WriteExcel;
use Spreadsheet::ParseExcel;
sub create_new_worksheet {
my ($row, $col, $value, $backgroundcolor) = #_;
# Create a new Excel workbook
my $workbook = Spreadsheet::WriteExcel->new('perl.xls');
# Add a worksheet
my $worksheet = $workbook->add_worksheet();
# Add and define a format
my $format = $workbook->add_format(bg_color => $backgroundcolor);
$format->set_align('center');
# Write a formatted and unformatted string, row and column notation.
$worksheet->write($row, $col, $value, $format);
}
my $parser = Spreadsheet::ParseExcel->new();
my $workbook_parse = $parser->Parse( 'Report.xls' );
my $worksheet_parse = $workbook_parse->Worksheet("Sheet1");
my ( $col_min, $col_max ) = $worksheet_parse->col_range();
my ( $row_min, $row_max ) = $worksheet_parse->row_range();
for my $col ( $col_min .. $col_max ) {
for my $row ( $row_min .. $row_max ) {
# Return the cell object at $row and $col
my $cell = $worksheet_parse->get_cell( $row, $col );
next unless $cell;
my $value = $cell->value();
my $format = $cell->get_format();
my $backgroundcolor = $format->{Fill}->[2];
print "Row, Col = ($row, $col) ";
print "Value = $value\n";
print "Format = $backgroundcolor\n";
create_new_worksheet($row, $col, $value, $backgroundcolor);
}
}

CSV to xls conversion in perl

i wanted to parse a csv file in perl and want to generate an excel sheet.
As of now i am able to parse CSV file and converted into xls.This code is working properly, which is giving some 6 rows and 3 colums according to CSV.which is correct.After parsing it i want to do some formating also.let say any row or colum which has "Pass" as a string that should be of green color and fail then that should be of Red color.How can i do that please help..
#!/run/pkg/TWW-perl-/5.8.8/bin/perl -w
use strict;
use warnings;
use Spreadsheet::WriteExcel;
use Text::CSV::Simple;
use Spreadsheet::ParseExcel::Format
my $infile = "/project/ls1socdft_nobackup/rev2.0/user/Shah- B53654/dft/dfta/perl/pattern_qa/output_0/xls_info.csv";
#usage() unless defined $infile && -f $infile;
my $parser = Text::CSV::Simple->new;
my #data = $parser->read_file($infile);
my $headers = shift #data;
my $outfile = shift || "/project/ls1socdft_nobackup/rev2.0/user/Shah-B53654/dft/dfta/perl/pattern_qa/output_0/xls_info.xls";
my $subject = shift || 'worksheet';
my $workbook = Spreadsheet::WriteExcel->new($outfile);
my $bold = $workbook->add_format();
$bold->set_bold(1) ;
my $color =$workbook->add_format();
$color->set_bg_color('green');
my $color1=$workbook->add_format();
$color1->set_bg_color('red');
import_data($workbook, $subject, $headers, \#data);
# Add a worksheet
sub import_data {
my $workbook = shift;
my $base_name = shift ;
my $colums = shift;
my $data = shift;
my $limit = shift || 50_000;
my $start_row = shift ||1;
my $worksheet = $workbook->add_worksheet($base_name);
$worksheet->add_write_handler(qr[\w], \&store_string_widths);
my $w = 1;
$worksheet->write('A' . $start_row, $colums,$bold);
my $i = $start_row;
my $qty = 0;
for my $row (#$data) {
$qty++;
if ($i > $limit) {
$i = $start_row;
$w++;
$worksheet = $workbook->add_worksheet("$base_name - $w");
$worksheet->write('A1', $colums);
}
$worksheet->write(1+$i++,0, $row);}
autofit_columns($worksheet);
warn "Convereted $qty rows.";
return $worksheet;
}
sub store_string_widths {
my $worksheet = shift;
my $col = $_[1];
my $token = $_[2];
# Ignore some tokens that we aren't interested in.
return if not defined $token; # Ignore undefs.
return if $token eq ''; # Ignore blank cells.
return if ref $token eq 'ARRAY'; # Ignore array refs.
return if $token =~ /^=/; # Ignore formula
# Ignore numbers
#return if $token =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d++))?$/;
# Ignore various internal and external hyperlinks. In a real scena+rio
# you may wish to track the length of the optional strings used wi+th
# urls.
return if $token =~ m{^[fh]tt?ps?://};
return if $token =~ m{^mailto:};
return if $token =~ m{^(?:in|ex)ternal:};
# We store the string width as data in the Worksheet object. We us+e
# a double underscore key name to avoid conflicts with future name +s.
#
my $old_width = $worksheet->{__col_widths}->[$col];
my $string_width = string_width($token);
if (not defined $old_width or $string_width > $old_width) {
# You may wish to set a minimum column width as follows.
#return undef if $string_width < 10;
$worksheet->{__col_widths}->[$col] = $string_width;
}
# Return control to write();
return undef;
}
sub string_width {
return length $_[0];
}
sub autofit_columns {
my $worksheet = shift;
my $col = 0;
for my $width (#{$worksheet->{__col_widths}}) {
$worksheet->set_column($col, $col, $width) if $width;
$col++;
}
}
There are two ways you can get the individual cell formatted the way you're expecting.
1: Instead of passing an array of data into the $worksheet->write() method, loop through each row and column and write each cell individually.
EX:
change
$worksheet->write('A1', $colums);
to
for (my $r=0;$r<#$colums;$r++) {
for (my $c=0;$c<#{$colums->[$r]}) {
$worksheet->write($r,$c,$colums->[$r]->[$c]);
}
Now, you can test each value being written for your criteria. If it matches, just include the format you want to use.
$worksheet->write($r,$c,$columns->[$r]->[$c],$color1);
}
2: The other option is to use Excel::Writer::XLSX
Spreadsheet::WriteExcel is in maintenance only mode and has
effectively been superseded by Excel::Writer::XLSX.
This module is more up to date and includes functions for conditional formatting, which can be added after writing your data.
Also, there should be no change in your excel generation code except for when you include the module and when you initialize it.
Then, you just specify the rules for the conditional formatting.
$worksheet->conditional_formatting( 'A1:J10',
{
type => 'text',
criteria => 'containing',
value => 'Pass',
format => $color,
}
);
$worksheet->conditional_formatting( 'A1:J10',
{
type => 'text',
criteria => 'containing',
value => 'Fail',
format => $color1,
}
);

Error when using Spreadsheet::ParseExcel

I get 2 errors when compiling the following code:
#!/usr/bin/perl
use strict;
use warnings;
use Spreadsheet::ParseExcel;
my $xlsparser = Spreadsheet::ParseExcel->new();
my $xlsbook = $parser->parse('xsl_test.xls');
my $xls = $xls->worksheet(0);
my ( $row_first, $row_last ) = $xls->row_range();
my ( $col_first, $col_last ) = $xls->col_range();
my $csv = '';
for my $row ( $row_first .. $row_last ) { #Step through each row
for my $col ( $col_first .. $col_last ) { #Step through each column
my $cell = $xls->get_cell( $row, $col ); #Get the current cell
next unless $cell;
$csv .= $cell->unformatted(); #Get the cell's raw data -- no border colors or anything like that
if ( $col == $col_last ) {
$csv .= "\n"; #Make a new line at the end of the row
} else {
$csv .= ",";
}
}
}
Errors:
global symbol "$parser" requires explicit package name at line 8
global symbol "$xls" requires explicit package name at line 9
I get the above code from http://www.ehow.com/how_7352636_convert-xls-csv-perl.html, and installed the excel module using: cpan Spreadsheet::ParseExcel Spreadsheet::XLSX Spreadsheet::Read
What's causing the error?
Those errors mean that you are using strict, but you didn't declare some variables with my. For example, you declared $xmlprser, but then you tried to use $parser, which was not declared. The code you copied has errors.
A better place to get code is the source itself: Spreadsheet::ParseExcel
Try:
my $parser = Spreadsheet::ParseExcel->new();
my $xlsbook = $parser->parse('xsl_test.xls');
my $xls = $xlsbook->worksheet(0);

Overwriting the existing Worksheet of a Excel File in Perl

With the reference to the question I had asked before is:
My Code is as below:
if ( !-e $xls_file ) #Checking whether to create a new Excel file or not
{
#if yes create a new excel file and 4 worksheets
print OUTFILE "Creatin a new sheet.\n";
$excelFileHandle= Spreadsheet::WriteExcel->new($xls_file) ;
&InitFormatting ($excelFileHandle);#For format of each cell.
($handle, $row, $col) = &AddSheet("$monthYearStr", "$SHEET_TITLE", #ReportColLabels);
($handle1, $row1, $col1) = &AddSheet("$STATUS_CODE_SHEET_NAME", "$STATUS_CODE_SHEET_TITLE", #Status_Code_labels);
($handle2, $row2, $col2) = &AddSheet("$SUCCESS_COUNT_SHEET_NAME", "$SUCCESS_SHEET_TITLE", #Success_count_labels);
($handle3, $row3, $col3) = &AddSheet("$FAILURE_COUNT_SHEET_NAME", "$FAILURE_SHEET_TITLE", #Failure_count_labels);
$new = 1;
}
else #File exists needs to parsed and append the data.
{
print OUTFILE "Opening the existing sheet.\n";
my $parser = Spreadsheet::ParseExcel->new();
my $workbook = $parser->parse($xls_file);
if ( !defined $workbook )
{
die $parser->error(), ".\n";
}
my $worksheet = $workbook->worksheet($monthYearStr);
( $row_min, $row_max ) = $worksheet->row_range();
( $col_min, $col_max ) = $worksheet->col_range();
my $worksheet1 = $workbook->worksheet($monthYearStr);
( $row_min1, $row_max1 ) = $worksheet1->row_range();
( $col_min1, $col_max1 ) = $worksheet1->col_range();
$new = 0;
}
if ( $new = 1 )
{
$handle->write($row, $col++, "$present_date", $num_field_format);
------Contiuned for remainin row and col for $monthYearStr $STATUS_CODE_SHEET_NAME $SUCCESS_COUNT_SHEET_NAME $FAILURE_COUNT_SHEET_NAME-------
}
elsif ( $new == 0)
{
###Appending the data####
$parser = Spreadsheet::ParseExcel::SaveParser->new();
$template = $parser->Parse($xls_file);
if ( ! defined $template )
{
die $parser->error(), ".\n";
}
my $worksheet = $template->worksheet($monthYearStr);
my $row = $row_max + 1;
my $cell = $worksheet->get_cell( $row_max, $row_max );
my $format_number = $cell->{FormatNo};
$worksheet->AddCell($row, $col++, "$present_date", $format_number);
---------Continued for remaining row and col only for $monthYearStr $STATUS_CODE_SHEET_NAME -----------------------------
}
Now, how do I over-write the worksheet sheet success_count and failure_count each time when the excel file exists.
Please suggest me how to do this.
Its difficult to tell what to do without the AddSheet subroutine, but you should be able to overwrite the cells in a worksheet using something like this, per the POD:
foreach $worksheet ($workbook->sheets()) {
if( $worksheet->get_name() eq $SUCCESS_COUNT_SHEET_NAME ) {
$worksheet->write('A1', $success_count);
}
elsif( $worksheet->get_name() eq $FAILURE_COUNT_SHEET_NAME ) {
$worksheet->write('A1', $failure_count);
}
}

Resources