I am new to perl and having trouble with the spreadsheet excel parser module.
WHen I use $cell->value() on its own line, there appears to be no problem, however when I try to use this to insert the value into an array, it returns undef, code show below:
for my $row ($row_min .. $row_max) {
my #line;
for my $col ( $col_min .. $col_max) {
my $cell = $worksheet->get_cell( $row, $col );
$value = $cell->value;
push #line, $value if defined($cell);
print $cell->value;
print Dumper $line;
}
}
}
Here print $cell->value; returns the contents of the cell but print Dumper $line; returns undef
You have to push $cell->valuenot $value
push #line, $cell->value if defined($cell);
You should add use strict at the beginning of your program, so you get an error message in such a case.
Related
I have three functions in a Perl script to parse an Excel file to getthe desired output Excel file. I achieved the correct output, however I get an error
File error: data may have been lost
Likely the root cause is writing over the same Excel cell twice in the file.
How do I get rid of this error while maintaining the functions in the script?
Input file
A B
Apples Carrots
Oranges Broccoli
Grapes Spinach
Desire output file
A B
Apples Carrots
Oranges Broccoli
PEACHES ASPARAGUS
Perl code
use v5.10.0;
use warnings;
use Spreadsheet::ParseExcel;
use Spreadsheet::ParseExcel::SaveParser;
use Spreadsheet::WriteExcel;
my $parser = Spreadsheet::ParseExcel::SaveParser->new();
my $workbook_R = $parser->parse('C:\Perl\databases\Fruits_and_Veggies.xls');
my $workbook_W = Spreadsheet::WriteExcel->new('C:\Perl\databases\New_Fruits_and_Veggies.xls');
my $worksheet_W = $workbook_W->add_worksheet();
for our $worksheet_R ( $workbook_R->worksheets() ) {
my ( $row_min, $row_max ) = $worksheet_R->row_range();
my ( $col_min, $col_max ) = $worksheet_R->col_range();
for our $row ( $row_min .. $row_max ) {
for our $col ( $col_min .. $col_max ) {
FruitStand();
VeggieStand();
ComboStand();
#------------------------------------------------------------------------------
# sub FruitStand - parsing: replace Grapes with PEACHES
#------------------------------------------------------------------------------
sub FruitStand {
# if the cell contains Grapes write 'PEACHES' instead
my $cell_grapes = $worksheet_R->get_cell( $row, $col );
if ( $cell_grapes->value() =~ /Grapes/ ) {
$worksheet_W->write($row, $col,"PEACHES");
}
}
#------------------------------------------------------------------------------
# sub VeggieStand - parsing: repalce Spinach with ASPARAGUS
#------------------------------------------------------------------------------
sub VeggieStand {
# if the cell contains Spinach write 'ASPARAGUS' instead
my $cell_veggies = $worksheet_R->get_cell( $row, $col );
# my $cell = $worksheet_R->get_cell( $row, $col );
if (/ $cell_veggies->value() =~ /Spinach/ ) {
$worksheet_W->write($row, $col,"ASPARAGUS");
}
}
#------------------------------------------------------------------------------
# Writing all fruits and veggies with the 2 changes (PEACHES and ASPARAGUS)
#------------------------------------------------------------------------------
sub ComboStand {
my $cell = $worksheet_R->get_cell( $row, $col );
$worksheet_W->write($row, $col, $cell->value()) ;
}
}
}
}
As you have said, the error message is due to a cell being written several times. You can get rid of the error message by ensuring that each cell is only written once. Since your three subroutines have very similar functionality, they can be combined into a single set of lines that does everything, and use an if / else cascade to decide which action should be taken.
for our $row ( $row_min .. $row_max ) {
for our $col ( $col_min .. $col_max ) {
my $cell = $worksheet_R->get_cell( $row, $col );
if($cell->value() =~ /Spinach/) {
$worksheet_W->write($row, $col,"ASPARAGUS");
}
elsif($cell->value() =~ /Grapes/) {
$worksheet_W->write($row, $col,"PEACHES");
}
else {
$worksheet_W->write($row, $col, $cell->value()) ;
}
}
}
If you have to keep the functions, I suggest something like this, where you have a function that takes the current cell and applies any appropriate transformations to it, and returns the text ready for output. This keeps the repetition of reading the cell and writing the cell out of the subroutines:
for our $row ( $row_min .. $row_max ) {
for our $col ( $col_min .. $col_max ) {
my $cell = $worksheet_R->get_cell( $row, $col );
$worksheet_w->write( $row, $col, produce_check($cell->value) );
}
}
And the produce_check sub that performs the swaps would also be a good place to do any text normalisations or other checks you might want to do on the input, e.g. removing extra whitespace, setting all the output to title case, etc.
sub produce_check {
my $prod = shift;
# maybe we have to make sure there's no trailing whitespace on $prod
$prod =~ s/\s*$//;
my %swaps = (
grapes => 'peaches',
spinach => 'asparagus',
tins => 'cans',
zuchini => 'zucchini'
);
# is $prod one of pieces of produce we have to swap?
# perhaps our input is in a mixture of cases, uppercase, lowercase, titlecase
# to avoid having to add all those variations to the %swaps hash, we convert
# to lowercase using `lc`
if ( $swaps{ lc($prod) } ) {
$prod = $swaps{ lc($prod) };
}
# this line uses `ucfirst($prod)` to convert all output to titlecase.
# You could also convert everything to lowercase ( `lc($prod)` ), to
# uppercase ( `uc($prod)` ), or just leave it as-is by using `return $prod;`
return ucfirst( $prod );
}
I have the following code which parses an Excel file.. For every row, if a cell is substring of another cell on the same row, I want to delete this cell.
My data (in the .xls file) look like that:
Number1 Text1 Text2 Text3 ... TextN Number2 Number3 ... NumberN
Each number and each text is in a different cell. The number of numbers and text may vary per row.. I want to check if Text1 is a substring of Text2 or Text3 etc... similarly if Text3 is a substring of Text4 Text5 etc.. If they are substrings I want to delete these cells.
#!/usr/bin/perl -w
use strict;
use warnings;
use Spreadsheet::ParseExcel;
use diagnostics;
my $parser = Spreadsheet::ParseExcel->new();
my $workbook = $parser->parse('test.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 );
my $test = $cell->value();
if (defined $test) {
my $cellValue = $cell->value();
print"The cell value is $cellValue \n";
} else {
print "Cell value is not defined \n";
}
#my $nextCell = $worksheet->get_cell( $row, $col+1 );
#if (index($nextCell->value(), $cell->value()) != -1) {
#print "$nextCell->value() contains $cell->value()\n";
#}
#next unless $cell;
}
}
}
I get an error Can't call method "value" on an undefined value at ... I believe it has to do with the fact that when the final cell in the row is found, the $cell->value function fails because the cell is empty.. I tried checking if the value if undefined so that I avoid processing this cell but I still get the same error.. How does Perl deal with empty cells ? How can I avoid getting this error? Thanks !
The error means $cell is undef when you call $cell->value.
If you simply want to skip empty cells, why not add
next unless $cell;
in your for my $col ( ... ) loop
Edit:
you could add
if( my $test = $cell->value() ){
$cell->delete if grep{ ( my $forward = $worksheet->get_cell( $row, $_ ) ) && ( $forward =~ /\Q$test\E/ } ( $col+1 .. $colMax );
}
Edit: This does not work ( I was not sure and could not test at the time ). Sorry.
Either declare a temporary variable $forward first, AND ( which was also wrong ) call ->value:
if( my $test = $cell->value() ){
my $forward;
$cell->delete if grep{ ( $forward = $worksheet->get_cell( $row, $_ )->value ) && ( $forward =~ /\Q$test\E/ } ( $col+1 .. $colMax );
}
Or, probably better, write it as a for loop (me was trying to be too smart for me own good)
for my $pos ( $col+1 .. $colMax ){
my $forward_cell = $worksheet->get_cell( $row, $pos );
if ( $forward_cell->value =~ /\Q$text/ ){
$cell->delete;
last;
}
}
This, elegantly, goes back to my earlier point: This seems inefficient
However, it might be more efficient to first get all the actually existing cells and then delete, again grepping for a following cell that matches text. Not sure you want to do /\Q$text\E/ or /^\Q$text\E/ ( string begins with $text ), and you might not need \Q ... \E since it only escapes special characters and is unnecessary if there are none.
in my code, I have a function which verifies if a cell from an worksheet contains valid values, meaning:
1. it contains something.
2. the contained value is not SPACE, TAB or ENTER.
when i am just checking the function within a print (to print the value returned by the function for a single cell), apparently, everything works fine.
when I am integrating the "print of the result" into a while loop, for checking a range of cells, it received the error message: Can't call method "value" on an undefined value at D:\test.pl
here is my code:
use strict;
use Spreadsheet::ParseXLSX;
my $parser = Spreadsheet::ParseXLSX->new();
sub check_cell($) {
my ( $worksheet, $a, $b ) = #_;
if ( $worksheet->get_cell( $a, $b )
or not ord( $worksheet->get_cell( $a, $b )->value() ) ~~ [ 32, 9, 10 ] )
{
return 1;
} else {
return 0;
}
}
my $workbook = $parser->parse('D:\test.pl data.xlsx');
if ( !defined $workbook ) {
die $parser->error(), ".\n";
}
my $worksheet = $workbook->worksheet(0);
my $i = 8;
while ( &check_cell( $worksheet, $i, 0 ) ) {
print &check_cell( $worksheet, $i, 0 ), "\n";
$i++;
}
if I remove the while and index increment, everything works fine.
Can anyone tell me why the error occurs in 1st case?
Thank you.
When you compare two strings for equallity, you have to use eq, not ==
while( check_cell($worksheet, $i, 0) eq "correct cell" ) {
#...
};
Also, it's more natural to return 0 or 1 in your check_cell sub, so you haven't to test the result in while loop:
sub check_cell {
# return 1 if it's ok, else 0
}
while( check_cell($worksheet, $i, 0) ) {
#...
};
And the smartmatch operator ~~ has been marked as experimental since Perl 5.18.0
When $worksheet->get_cell($a, $b) doesn't return a defined value, you can't call ->value on the undefined value it returned. Don't you want to use and instead of or in the condition?
in this case, it worked modifying the condition:
instead of this:
if ( $worksheet->get_cell( $a, $b )
or not ord( $worksheet->get_cell( $a, $b )->value() ) ~~ [ 32, 9, 10 ] )
{
return 1;
}
I used:
if ( $worksheet -> get_cell($a, $b) ) {
if ( not ord($worksheet -> get_cell($a, $b) -> value()) ~~ [32, 9, 10] ){
return 1;
}
}
and the scrit exits without any error.
Thank you guys for your help.
Im trying to count whenever a thread is done in perl, and print the count. but this is not working. i keep getting either "0" or "1", im trying to add to the count then print the count right after the get request is made.
use strict;
use threads;
use LWP::UserAgent;
our $MAX //= $ARGV[1];
my $list = $ARGV[0];
open my $handle, '<', $list;
chomp(my #array = <$handle>);
close $handle;
my $lines = `cat $list | wc -l`;
my $count = 0;
my #threads;
foreach $_ (#array) {
push #threads, async{
my #chars = ("a".."z");
my $random = join '', map { #chars[rand #chars] } 1 .. 6;
my $ua = LWP::UserAgent->new;
my $url = $_ . '?session=' . $random;
my $response = $ua->get($url);
count++;
print $count;
};
sleep 1 while threads->list( threads::running ) > $MAX;
}
$_->join for #threads;
Just to summarise points in comments by #choroba and myself, and not leave the question without an answer.
You would need to include:
use threads::shared;
in your code, along with all the other use elements.
And to indicate that variable $count is shared:
my $count :shared = 0;
EDIT As per Ikegami's comment, you would have to lock the variable if you want to modify it, to avoid problems of concurrency.
{
lock($count);
$count++;
print $count;
}
And that should be enough for the variable $count to be shared.
I am having problem in parsing the output from the text file. I want to add pipe symbol in between the character to do mutliple search similar to egrep, the text file is as follows
service entered the stopped state,critical
service entered the running state,clear
Code:
open(my $data, '<', $Config_File) or die "Could not open '$Config_File"
my $reg_exp;
my $severity;
my #fields=();
while (my $line = <$data>)
{
chomp $line;
if(!$line =~ /^$/)
{
#fields = split "," , $line;
$reg_exp = $fields[0];
$severity = $fields[1];
print $reg_exp;
}
}
#print $fields[0];
#last unless defined $line;
close($data);
expected output
service entered the stopped state|service entered the running state
You are not far off, you just need to actually concatenate the strings. The simplest way would be to push the $fields[0] to an array, and wait until the input is done to print it. I.e.:
my #data;
while (my $line = <$data>) {
next if $line =~ /^$/; # no need to chomp
my #fields = split /,/, $line;
push #data, $fields[0];
}
print join("|", #data), "\n";
I sense that you are trying to achieve something else with this code, and that this is a so-called XY-problem.