How to handle excel using OLE module? - perl

Friends, I wrote a Perl script to convert a set of CSV files into spreadsheet
format using Spreadsheet::WriteExcel. After some research I came to
conclusion that there is no option to fix column width as Auto-fit option.
So what I'm doing is in the same script I've opened that XLS file using Win32::OLE
module, while doing this I got an error message
Can't use an undefined value as a HASH reference
Corresponding code is:
# spread sheet creation
my $workbook = Spreadsheet::WriteExcel->new($file_name);
# ...
my $worksheet = $workbook->add_worksheet($work_sheet_name);
# ...
$worksheet->write($rowNum, $j,$_,$default_format);
after these steps I have some more lines in the same script:
my $Excel = Win32::OLE->GetActiveObject('Excel.Application')
|| Win32::OLE->new('Excel.Application');
$Excel->{'Visible'} = 0; #0 is hidden, 1 is visible
$Excel->{DisplayAlerts}=1; #0 is hide alerts
# Open File and Worksheet
my $return_file_name="C:\\Users\\admin\\Desktop\\Report_Gen\\$file_name";
print ">>$return_file_name<<";
my $Book = $Excel->Workbooks->Open($return_file_name); # open Excel file
foreach my $Sheet (in $Book->Sheets) {
my $LastCol = $Sheet->UsedRange->Find({What=>"*",
SearchDirection=>xlPrevious,
SearchOrder=>xlByColumns})->{Column}; # mentioned error is from this line
my $mylastcol = 'A';
for (my $m=1;$m<$LastCol;$m++) {$mylastcol++;}
my #columnheaders = ('A:'.$mylastcol);
foreach my $range (#columnheaders){
$Sheet->Columns($range)->AutoFit();
}

I wrote a Perl script to convert a set of CSV files into spreadsheet format using Spreadsheet::WriteExcel. After some research I came to conclusion that there is no option to fix column width as Auto-fit option.
Autofit is a runtime option in Excel and so it isn't possible to create it via the file format using Spreadsheet::WriteExcel.
However, the Spreadsheet::WriteExcel docs contain an example of how to simulate autofit with an explanation of some of the issues involved.

Related

Reading Xlsx from another Xlsx file

I have few Xlsx files say X.xlsx,Y.xlsx,Z.XLSX and I kept those three Xlsx files in another xlsx file say A.xlsx. Now I want to ready the content in the three xlsx files(x,y,z) at a time through A.xlsx.
Can any one help me on this.
Thanks in advance
This is easy on Windows if your target machine also has Microsoft Excel installed.
Use the Win32::OLE module to create an instance of Excel, open your master file A.xlsx and then iterate over its ->{OLEObjects} property:
#!perl
use strict;
use warnings;
use Win32::OLE 'in';
$ex = Win32::OLE->new('Excel.Application') or die "oops\n";
my $Axlsx = $ex->Open('C:\\Path\\To\\A.xlsx');
my $i=0;
for my $embedded (in $Axlsx->OLEObjects) {
$embedded->Object->Activate();
$embedded->Object->SaveAs("test$i++.xlsx");
$embedded->Object->Close;
}
After saving them, you can treat them as normal Excel files. Alternatively, you can work directly with $embedded->Object, but as you haven't told us what exactly you need to do, it's hard to give specific advice.
See also Save as an Excel file embedded in another Excel file

Rewrite existing xls files using Perl

I am trying to write data in excel files which is already exists, but the code I tried is creating a new sheet and erasing the old sheet with the data. This the code I use
#!/usr/bin/perl –w
use strict;
use Spreadsheet::WriteExcel;
# Create a new Excel file
my $FileName = 'Report.xls';
my $workbook = Spreadsheet::WriteExcel->new($FileName);
# Add a worksheet
my $worksheet1 = $workbook->add_worksheet(); #<- My Doubt
# Change width for only first column
$worksheet1->set_column(0,0,20);
# Write a formatted and unformatted string, row and column
# notation.
$worksheet1->write(0,0, "Hello");
$worksheet1->write(1,0,"HI");
$worksheet1->write(2,0,"1");
$worksheet1->write(3,0,"2"); `
How can I assign the current sheet to $worksheet1. And one more thing is I need to read specific cell from which is already exist.
Please give me some guidance .Thank you
You cannot open an existing a spreadsheet with Spreadsheet::WriteExcel and update it like that. You first want to open it using Spreadsheet::ReadExcel along with an output sheet which you open with WriteExcel. Then, you read the input file, write out existing cells, sheets etc, and make whatever edits/updates/insertions you are going to make. Then, you can close both files, remove the previous, and rename the new one (optionally backing up the previous version).
You can only really edit/change a given Excel file without going through this process by opening it using Win32::OLE, but for that you are most certainly going to need to be on a Windows system (I am not sure about the state of Wine), and this is not something you want to do on a server.
You can think of creating a file with Spreadsheet::WriteExcel as similar to opening a file with open my $fh, '>', 'output.file' ... output.file will be clobbered.
Note the line:
my $fh = FileHandle->new('>'. $self->{_filename});
in Spreadsheet::WriteExcel::Workbook->new.

Open Excel file in perl and print row count

I am using Win32::OLE module to open an excel file and get row count. The problem is when i hard code excel file path it works fine but when i dynamically pass path it throw an error saying that "cant call method workbooks on unblessed reference". Please find the below sample code.
use OLE;
use Win32::OLE::Const 'Microsoft Excel';
my $xapp= Win32::OLE->GetActiveObject('Excel.Application')
or do { Win32::OLE->new('Excel.Application', 'Quit')};
$xapp->{'Visible'} = 0;
my $file='excel.xlsx';
my $fileName="c:/users/mujeeb/desktop/".$file;
print $fileName;
my $wkb = $xapp->Workbooks->Open($fileName); //here i am getting error coz i am passing dynamic fileName;
my $wks = $wkb->Worksheets('Sheet1');
my $Tot_Rows=$wks->UsedRange->Rows->{'Count'};
print $Tot_Rows."\n";
$xapp->close;
Use backslashes in the filename.
The filename is given to excel and excel won't understand forward slashes. Perl does not convert them because Perl doesn't know the string is a file.
Are you sure that there exists a method named as Open? Because I don't see it in the documentation of Win32::OLE. Also you must add use Win32::OLE; in your code.
You could use this line of code to change the path into readable path for OLE:
my $file='excel.xlsx';
my $fileName="c:/users/mujeeb/desktop/".$file;
$fileName=~s/[\/]/\\/g;
print $fileName;
outputs:
c:\\users\\mujeeb\\desktop\\excel.xlsx

How can my Perl script determine whether an Excel file is in XLS or XLSX format?

I have a Perl script that reads data from an Excel (xls) binary file. But the client that sends us these files has started sending us XLSX format files at times. I've updated the script to be able to read those as well. However, the client sometimes likes to name the XLSX files with an .xls extension, which currently confuses the heck outta my script since it uses the file name to determine which file type it is.
An XLSX file is a zip file that contains XML stuff. Is there a simple way for my script to look at the file and tell whether it's a zip file or not? If so, I can make my script go by that instead of just the file name.
Yes, it is possible by checking magic number.
There are quite a few modules in Perl for checking magic number in a file.
An example using File::LibMagic:
use strict;
use warnings;
use File::LibMagic;
my $lm = File::LibMagic->new();
if ( $lm->checktype_filename($filename) eq 'application/zip; charset=binary' ) {
# XLSX format
}
elsif ( $lm->checktype_filename($filename) eq 'application/vnd.ms-office; charset=binary' ) {
# XLS format
}
Another example, using File::Type:
use strict;
use warnings;
use File::Type;
my $ft = File::Type->new();
if ( $ft->mime_type($file) eq 'application/zip' ) {
# XLSX format
}
else {
# probably XLS format
}
.xlsx files have the first 2 bytes as 'PK', so a simple open and examination of the first 2 characters will do.
Edit: Archive::Zip is a better
solution
# Read a Zip file
my $somezip = Archive::Zip->new();
unless ( $somezip->read( 'someZip.zip' ) == AZ_OK ) {
die 'read error';
}
Use File::Type:
my $file = "foo.zip";
my $filetype = File::Type->new( );
if( $filetype->mime_type( $file ) eq 'application/zip' ) {
# File is a zip archive.
...
}
I just tested it with a .xlsx file, and the mime_type() returned application/zip. Similarly, for a .xls file the mime_type() is application/octet-stream.
You can detect the xls file by checking the first bytes of the file for Excel headers.
A list of valid older Excel headers can be gotten from here (unless you know exact version of their Excel, check for all applicable possibilities):
http://toorcon.techpathways.com/uploads/headersig.txt
Zip headers are described here: http://en.wikipedia.org/wiki/ZIP_(file_format)#File_headers
but i'm not sure if .xlsx files have the same headers.
File::Type's logic seems to be "PK\003\004" as the file header to decide on zip files... but I'm not certain if that logic would work as far as .xlsx, not having a file to test.
The-Evil-MacBook:~ ivucica$ file --mime-type --brief file.zip
application/zip
Hence, probably comparing
`file --mime-type --brief $filename`
with application/zipwould do the trick of detecting zips. Of course, you need to have file installed which is quite usual on UNIX systems. I'm afraid I cannot provide Perl example since all knowledge of Perl evaporated from my memory, and I have no examples at hand.
I can't say about Perl, but with the framework I use, .Net, there are a number of libraries available that will manipulate zip files you could use.
Another thing that I've seen people use is the command-line version of WinZip. It give a return-value that is 0 when a file is unzipped and non-zero when there is an error.
This may not be the best way to do this, but it's a start.

How can I modify an existing Excel workbook with Perl?

With Spreadsheet::WriteExcel, I can create a new workbook, but what if I want to open an existing book and modify certain columns? How would I accomplish that?
I could parse all of the data out of the sheet using Spreadsheet::ParseExcel then write it back with new values in certain rows/columns using Spreadsheet::WriteExcel, however. Is there a module that already combines the two?
Mainly I just want to open a .xls, overwrite certain rows/columns, and save it.
Spreadsheet::ParseExcel will read in existing excel files:
my $parser = Spreadsheet::ParseExcel->new();
# $workbook is a Spreadsheet::ParseExcel::Workbook object
my $workbook = $parser->Parse('Book1.xls');
But what you really want is Spreadsheet::ParseExcel::SaveParser, which is a combination of Spreadsheet::ParseExcel and Spreadsheet::WriteExcel. There is an example near the bottom of the documentation.
If you have Excel installed, then it's almost trivial to do this with 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;
Basically, Win32::OLE gives you everything that is available to a VBA or Visual Basic application, which includes a huge variety of things -- everything from Excel and Word automation to enumerating and mounting network drives via Windows Script Host. It has come standard with the last few editions of ActivePerl.
There's a section of the Spreadsheet::WriteExcel docs that covers Modifying and Rewriting Spreadsheets.
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.
You can read and rewrite an Excel file using the Spreadsheet::ParseExcel::SaveParser module which is a wrapper around Spreadsheet::ParseExcel and Spreadsheet::WriteExcel. It is part of the Spreadsheet::ParseExcel package.
There's an example as well.
The Spreadsheet::ParseExcel::SaveParser module is a wrapper around Spreadsheet::ParseExcel and Spreadsheet::WriteExcel.
I recently updated the documentation with, what I hope, is a clearer example of how to do this.