0
votes

I am new to perl. i have an excel sheet with lot of data.. I need to update it and create a graph based on the data..using perl. i am succeded in updating an existing excel.. now adding chart to it is not happening

use Spreadsheet::ParseExcel;
use Spreadsheet::ParseExcel::SaveParser;
use Spreadsheet::WriteExcel;

# Open an existing file with SaveParser
my $parser      = Spreadsheet::ParseExcel::SaveParser->new();
my $template    = $parser->Parse('MyExcel.xls');
my $worksheet   = $template->worksheet('Firstsheet');
my $chart       = $template->add_chart( type => 'line' );
$chart->add_series(
   categories => '=URV!$A$17:$A$442',
   values     => '=URV!$D$17:$D$442',
   name       => 'pended graph',
);

This is not working. Can't call method "add_chart" on an undefined value at charts4.ps line 20 Please help me with a sample working code.. Want to know whats the problem here.

2

2 Answers

1
votes

add_chart() is one of the WORKBOOK METHODS. Try code like this:

use Spreadsheet::WriteExcel;                             

my $workbook = Spreadsheet::WriteExcel->new('perl.xls'); 
$worksheet   = $workbook->add_worksheet();               
$worksheet->write('A1', 'Hi Chart!');                    
my $chart = $workbook->add_chart( type => 'line', embedded => 1, name => 'pended graph' );

# Insert the chart into the a worksheet.
$worksheet->insert_chart( 'E2', $chart );

Update

The problem is that excel is very hard to update with perl.

An Excel file is a binary file within a binary file. It contains several interlinked checksums and changing even one byte can cause it to become corrupted.

As such you cannot simply append or update an Excel file. The only way to achieve this is to read the entire file into memory, make the required changes or additions and then write the file out again.

Spreadsheet::ParseExcel will read in existing excel files:

my $parser   = Spreadsheet::ParseExcel->new();
# $workbook is a Spreadsheet::ParseExcel::Workbook object
my $workbook = $parser->Parse('blablabla.xls');

What you really want is Spreadsheet::ParseExcel::SaveParser, which is a combination of Spreadsheet::ParseExcel and Spreadsheet::WriteExcel.

Here is an example.

Summing it up, I would suggest you to read the excel data in and then try either of the following:

  • Create another xls file and use the Spreadsheet::WriteExcel::Chart library.
  • Create a xlsx file and use the Excel::Writer::XLSX::Chart library.
  • Another close option would be to read the excel in with
    Spreadsheet::ParseExcel::SaveParser and then add the chart and save
    it, but with this module all original charts are lost.

If you are on a Windows machine you may try to use Win32::OLE. Here is the example from Win32::OLE's own documentation:

use Win32::OLE;

# use existing instance if Excel is already running
eval {$ex = Win32::OLE->GetActiveObject('Excel.Application')};
die "Excel not installed" if $@;
unless (defined $ex) {
    $ex = Win32::OLE->new('Excel.Application', sub {$_[0]->Quit;})
            or die "Oops, cannot start Excel";
}

# get a new workbook
$book = $ex->Workbooks->Add;

# write to a particular cell
$sheet = $book->Worksheets(1);
$sheet->Cells(1,1)->{Value} = "foo";

# write a 2 rows by 3 columns range
$sheet->Range("A8:C9")->{Value} = [[ undef, 'Xyzzy', 'Plugh' ],
                                   [ 42,    'Perl',  3.1415  ]];

# print "XyzzyPerl"
$array = $sheet->Range("A8:C9")->{Value};
for (@$array) {
    for (@$_) {
        print defined($_) ? "$_|" : "<undef>|";
    }
    print "\n";
}

# save and exit
$book->SaveAs( 'test.xls' );
undef $book;
undef $ex;

UPDATE@2

Here is an example code:

use strict;
use Spreadsheet::WriteExcel;

my $workbook  = Spreadsheet::WriteExcel->new( 'chart_column.xls' );
my $worksheet = $workbook->add_worksheet();
my $bold      = $workbook->add_format( bold => 1 );

# Add the worksheet data that the charts will refer to.
my $headings = [ 'Category', 'Values 1', 'Values 2' ];
my $data = [
    [ 2, 3, 4, 5, 6, 7 ],
    [ 1, 4, 5, 2, 1, 5 ],
    [ 3, 6, 7, 5, 4, 3 ],
];

$worksheet->write( 'A1', $headings, $bold );
$worksheet->write( 'A2', $data );


###############################################################################
#
# Example 1. A minimal chart.
#
my $chart1 = $workbook->add_chart( type => 'column',  embedded => 1 );

# Add values only. Use the default categories.
$chart1->add_series( values => '=Sheet1!$B$2:$B$7' );

# Insert the chart into the main worksheet.
$worksheet->insert_chart( 'E2', $chart1 );

###############################################################################
#
# Example 2. One more chart
#
my $chart2 = $workbook->add_chart( type => 'column', embedded => 1 );

# Configure the chart.  # change the categories if required change the values as required
$chart2->add_series(
    categories => '=Sheet1!$A$4:$A$7',
    values     => '=Sheet1!$B$4:$B$7',
);

$worksheet->insert_chart( 'N1', $chart2, 3, 3 );

Also,
If you don't mind xlsx over xls, you may use Excel::Writer::XLSX. It is more actively maintained.

0
votes

The trick to be able to parse and use at the same time the functions inside the WriteExcel module is to use the the use Spreadsheet::ParseExcel::SaveParser; module.

Below i have an example. The example will not use the chart functions but the problem you have is not on how to use the chart functions of WriteExcel module but on how to parse an existing excel file and then use that parsed information with the WriteExcel modul (which is originally thought only for NEW excel files).

if ( ( -f $excel_file_name ) && ( ( stat $excel_file_name )[7] > 0 ) ) {

#PARSE EXCEL
use Spreadsheet::ParseExcel;
use Spreadsheet::ParseExcel::SaveParser;

# Open the template with SaveParser
my $parser   = new Spreadsheet::ParseExcel::SaveParser;
my $template = $parser->Parse("$excel_file_name");
my $sheet    = 0;
my $row      = 0;
my $col      = 0;

if ( !defined $template ) {
    die $parser->error(), " Perlline:", __LINE__, " \n ";    #probably the file is already open by your GUI
}

# Get the format from specific cell
my $format = $template->{Worksheet}[$sheet]->{Cells}[$row][$col]->{FormatNo};

# Add a new worksheet
#for my $worksheet ( $template->worksheets() ) {
my $worksheet_parser = $template->worksheet("$metrict_data_worksheet_name");
my ( $row_min, $row_max ) = $worksheet_parser->row_range();
my ( $col_min, $col_max ) = $worksheet_parser->col_range();
my @row_array_value;
for my $row ( 1 .. $row_max ) {    #avoid header start from 1
    for my $col ( $col_min .. $col_max ) {

        my $cell = $worksheet_parser->get_cell( $row, $col );
        next unless $cell;

        #print "Row, Col    = ($row, $col)\n";
        #print "Value       = ", $cell->value(),       "\n";
        #print "Unformatted = ", $cell->unformatted(), "\n";
        #print "\n";
        push( @row_array_value, $cell->value() );

    }    #end header column loops for one regression
}    #end row loop all lines
     #}

# The SaveParser SaveAs() method returns a reference to a
# Spreadsheet::WriteExcel object. If you wish you can then
# use this to access any of the methods that aren't
# available from the SaveParser object. If you don't need
# to do this just use SaveAs().
#
my $workbook;

{
    # SaveAs generates a lot of harmless warnings about unset
    # Worksheet properties. You can ignore them if you wish.
    local $^W = 0;

    # Rewrite the file or save as a new file
    my $check_if_possible2write = Spreadsheet::WriteExcel->new($excel_file_name);
    if ( defined $check_if_possible2write ) {    #if not possible it will be undef
        $workbook = $template->SaveAs("$excel_file_name");#IMPORTANT this is of type WriteExcel and not ParseExcel
    }
    else {
        print "Not possible to write the Excel file :$excel_file_name, another user may have the file open. Aborting... ", __LINE__, " \n ";
        exit;
    }
}
#####################FROM HERE YOU CAN USE AGAIN use Spreadsheet::WriteExcel; ####################
use Spreadsheet::WriteExcel;
my $worksheet = $workbook->sheets("$metrict_data_worksheet_name");

my $column_header_count = 0;
foreach my $name ( sort { lc $a cmp lc $b } keys %merged_all_metrics ) {

    $worksheet->write( $row_max + 1, $column_header_count, "$merged_all_metrics{$name}" );    #row,col start
    $column_header_count++;
}
$worksheet->set_column( 'A:L', 50, undef, 0, 1, 0 );                                          #grouping                                                     #comp_src group
$worksheet->set_column( 'N:R', 50, undef, 0, 1, 0 );                                          #grouping
$workbook->close() or die "Error closing file: $!";                                           #CLOSE
}

The important part of the code is what happens after the comment line:

#####################FROM HERE YOU CAN USE AGAIN use Spreadsheet::WriteExcel; ####################

After that point you will see that you have a $workbook handler. This variable has all the information parsed and more important is that it is from type WriteExcel Object so you will have all the methods of this module available.

Important Notice. The parser is not able to parse charts and formulas (only values), therefore you will have to write then again on each parse->write loop.