Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks!
I am trying to parse an Excel Spreadsheet to insert its values into a database, but I can’t get this code to work. It doesn’t print anything. I am getting "Use of uninitialized value in array element on line 44 and 46". Is there anything I am doing complete wrong, or a better way of doing this?
Here is the code:
#!/usr/bin/perl -w use strict; use CGI qw( -oldstyle_urls :standard ); use CGI::Carp qw ( fatalsToBrowser ); use Spreadsheet::ParseExcel; print header(); my $header_account; my $header_name; my $got_number; my $got_name; my $f_to_parse="accounts.xls"; my $workbook = Spreadsheet::ParseExcel::Workbook->Parse($f_to_parse)or + die "Unable to open $f_to_parse\n"; foreach my $page (@{$workbook->{Worksheet}}) { if ((defined $page->{MinCol}) && (defined $page->{MaxCol})) { foreach my $col ($page->{MinCol} .. $page->{MaxCol}) { if ($page->{Cells}[0][$col]->{Val} eq 'Account Numbers +') { $header_account = $col; } if ($page->{Cells}[0][$col]->{Val} eq 'Account Names') { $header_name = $col; } } } if ((defined $page->{MinRow}) && (defined $page->{MaxRow})) { foreach my $row ($page->{MinRow}+1 .. $page->{MaxRow}) { $got_number = $page->{Cells}[$row][$header_account]->{Val} +; $got_name = $page->{Cells}[$row][$header_name]->{Val}; } } } # here I will insert the values from the xls file into the database if + they are true. print "got_number=$got_number\n"; print "got_name=$got_name\n";

Replies are listed 'Best First'.
Re: Help with Spreadsheet::ParseExcel
by toolic (Bishop) on Jan 11, 2011 at 16:54 UTC
    Try to separate your CGI from your Excel code. Just try running this simplified code. You should see the worksheet names printed:
    use warnings; use strict; use Spreadsheet::ParseExcel; my $f_to_parse = "accounts.xls"; my $workbook = Spreadsheet::ParseExcel::Workbook->Parse($f_to_parse); for my $sheet (@{$workbook->{Worksheet}}) { print "Sheet: ", $sheet->{Name}, "\n"; }
    Side note: The following code will not die if the file does not exist. Try it yourself:
    my $workbook = Spreadsheet::ParseExcel::Workbook->Parse($f_to_parse)or + die "Unable to open $f_to_parse\n";
    Refer to Spreadsheet::ParseExcel (the error method).
      Hi, it didn't make any difference with or without CGI, I fixed the "die" method as well, but I still have the problem with the code I posted, any suggestion?
      #!/usr/bin/perl -w use strict; use CGI qw( -oldstyle_urls :standard ); use CGI::Carp qw ( fatalsToBrowser ); use Spreadsheet::ParseExcel; my $f_to_parse = "accounts.xls"; my $parser = Spreadsheet::ParseExcel->new(); my $workbook = $parser->parse($f_to_parse); #my $workbook = Spreadsheet::ParseExcel::Workbook->Parse($f_to_parse)o +r die "Unable to open $f_to_parse\n"; if ( !defined $workbook ) { die "Got error code ", $parser->error_code, ".\n"; } for my $sheet (@{$workbook->{Worksheet}}) { print "Sheet: ", $sheet->{Name}, "\n"; } #Result: #Sheet: Sheet1 #Sheet: Sheet2 #Sheet: Sheet3

      Thanks!
        Great. So, you proved that something will get printed. Now, start adding your lines of code back in and debug with print statements to see where things start going wrong. Refer to the Basic debugging checklist for other tips.
Re: Help with Spreadsheet::ParseExcel
by jmcnamara (Monsignor) on Jan 11, 2011 at 21:07 UTC
    The code needs some basic checks on whether the worksheet contains 'Account Names' and 'Account Numbers' columns before trying to access data in those columns. In addition there should be checks on whether the accessed rows contain data.

    If the cells don't contain information then Spreadsheet::ParseExcel will return undef values.

    The following is a working example. Substitute the prints for database inserts and the warns for error reporting and you should be most of the way there.

    #!/usr/bin/perl use strict; use warnings; use Spreadsheet::ParseExcel; my $parser = Spreadsheet::ParseExcel->new(); my $workbook = $parser->parse( 'accounts.xls' ); if ( !defined $workbook ) { die $parser->error(), ".\n"; } WORKSHEET: for my $worksheet ( $workbook->worksheets() ) { my $sheetname = $worksheet->get_name(); my ( $row_min, $row_max ) = $worksheet->row_range(); my ( $col_min, $col_max ) = $worksheet->col_range(); my $account_name_col; my $account_number_col; # Skip worksheet if it doesn't contain data. if ( $row_min > $row_max ) { warn "\tWorksheet $sheetname doesn't contain data.\n"; next WORKSHEET; } # Check for certain column headers in the first data row. COLUMN: for my $col ( $col_min .. $col_max ) { my $cell = $worksheet->get_cell( $row_min, $col ); next COLUMN unless $cell; $account_name_col = $col if $cell->value() eq 'Account N +ames'; $account_number_col = $col if $cell->value() eq 'Account N +umbers'; } # Check that we found the required columns headers. if ( defined $account_name_col && defined $account_number_col +) { ROW: for my $row ( $row_min + 1 .. $row_max ) { my $name_cell = $worksheet->get_cell( $row, $account +_name_col ); my $number_cell = $worksheet->get_cell( $row, $account +_number_col ); if ( defined $name_cell && defined $number_cell ) { my $name = $name_cell->value(); my $number = $number_cell->value(); # We have the data we are looking for. Do somethin +g with it. print "Worksheet = $sheetname\n"; print "Row = $row\n"; print "Account name = $name\n"; print "Account number = $number\n\n"; } else { # Handle the error condition in some way. warn "\tWorksheet $sheetname, Row = $row doesn't c +ontain " . "'Account Names' and 'Account Numbers' data.\n +"; next ROW; } } } else { # Handle the error condition in some way. warn "\tWorksheet $sheetname: Didn't find 'Account Names' +and " . "'Account Numbers' headings.\n"; next WORKSHEET; } } __END__

    --
    John.

      Thanks for the help this made sense now! Where do I find some explanation about how you used words like "WORKSHEET", "COLUMN" in your code.
        They are just labels for use with next. It is a way of differentiating which loop level you are existing from.

        I sometimes use them for clarity if there is more than one level of nested next. I thought it might help see the call flow in this case.

        See perlfunc for an explanation of next with labels.

        --
        John.

Re: Help with Spreadsheet::ParseExcel
by jmcnamara (Monsignor) on Jan 11, 2011 at 21:24 UTC
    P.S. Could follow Perlmonks try to steer people away from using the the old Spreadsheet::ParseExcel API with direct hash access and on to the new (for the last two years) method based API.

    For example if someone turns up with a example like this from the old docs:

    use strict; use Spreadsheet::ParseExcel; my $oBook = Spreadsheet::ParseExcel::Workbook->Parse('Book1.xls'); my($iR, $iC, $oWkS, $oWkC); foreach my $oWkS (@{$oBook->{Worksheet}}) { print "--------- SHEET:", $oWkS->{Name}, "\n"; for(my $iR = $oWkS->{MinRow} ; defined $oWkS->{MaxRow} && $iR <= $oWkS->{MaxRow} ; $i +R++) { for(my $iC = $oWkS->{MinCol} ; defined $oWkS->{MaxCol} && $iC <= $oWkS->{ +MaxCol} ; $iC++) { $oWkC = $oWkS->{Cells}[$iR][$iC]; print "( $iR , $iC ) =>", $oWkC->Value, "\n" if($oWkC) +; } } }
    Try to get them to rewrite it like this from the current docs:
    use strict; use warnings; use Spreadsheet::ParseExcel; my $parser = Spreadsheet::ParseExcel->new(); my $workbook = $parser->parse('Book1.xls'); if ( !defined $workbook ) { die $parser->error(), ".\n"; } for my $worksheet ( $workbook->worksheets() ) { my ( $row_min, $row_max ) = $worksheet->row_range(); my ( $col_min, $col_max ) = $worksheet->col_range(); for my $row ( $row_min .. $row_max ) { for my $col ( $col_min .. $col_max ) { my $cell = $worksheet->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"; } } }
    The checks in the latter code should help them find their own bugs.

    --
    John.

Re: Help with Spreadsheet::ParseExcel
by cjb (Friar) on Jan 11, 2011 at 16:52 UTC

    It looks like your first if (or one of the sub ifs) is not being executed so $header_account & $header_name are not being initialized

    Edited: 20110111 16:55 GMT to clarify meaning

      That's why I cant figure it out why?