Thread excelPerl
(15 answers)
Opened by
Strat
at 2006-11-05 23:06
User since 2003-08-04
5246
Artikel
ModeratorIn
Hallo Leute,
da ich heute irgendwie nicht einschlafen konnte, gibt's eine neue Version v0.10. Die hat als Erweiterung eine Liste namens @c (ich ueberlege noch, ob ich sie nicht @C nennen sollte), die Zugriff auf die Attribute der Zellen einer Reihe gibt (z.B. setzen oder auslesen von Formaten, Ausrichtungen, ...)
Datei excelPerl.pl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
#! /usr/bin/perl use warnings; use strict;
our $VERSION = 0.10;
use Win32; # automatically loaded with Activestate Perl use File::Basename; # for PrintUsage use Getopt::Long; # for parsing program arguments use FindBin; use lib $FindBin::Bin; use ExcelPerl; use Win32::OLE::Const 'Microsoft Excel';
$|++; # no suffering from buffering
my( $file, $perlCode ); my $worksheetNumber = 1; # use first worksheet as default my $headline = 0; # don't skip a headline as default my $colSeparator = "\t"; # standard col separator for $_
GetOptions( 'file=s' => \$file, 'sheet=i' => \$worksheetNumber, 'headline=i' => \$headline, 'ane=s' => \$perlCode, 'colseparator' => \$colSeparator, ) or die &PrintUsage();
# check file and convert it to absolute path if necessary defined $file or die &PrintUsage(); $file = Win32::GetFullPathName( $file );
my $xlsPerl = ExcelPerl->new( excelFile => $file, worksheetNumber => $worksheetNumber, ) ->open;
$xlsPerl->excel->{Visible} = 1;
# skip all lines until -headline if available $xlsPerl->currentRow( $headline ) if $headline; $xlsPerl->colSeparator( $colSeparator );
# evaluate the perl code from argument -ane ( thanks @pKai for the idea ) my $aneCode = eval "sub { my \$xlsPerl = shift; my \$c = shift; my \@c = \@\$c; my \@F = \@_;
# prepare some other helpful variables local \$. = \$xlsPerl->currentRow(); no warnings \'uninitialized\'; local \$_ = join( \$xlsPerl->colSeparator, \@F ) . \"\\n\"; $perlCode; }"; die $@ if $@;
my @F; while( my @cells = $xlsPerl->getNextRow( \@F ) ) { $aneCode->($xlsPerl, \@cells, @F); } # while
# ------------------------------------------------------------ sub PrintUsage {
my $bin = File::Basename::basename($0); die <<EOH; $bin -sheet 1 -headline 1 -ane "print qq~\$.: \$F[0], \$F[1]\\n~" -file excelFile
try perldoc $bin for more details
EOH
} # PrintUsage # ------------------------------------------------------------
=pod
=head1 excelPerl
You know about the perl parameters -ane? This script is a way to try to do the same thing to Excel files.
=head1 Prerequisites
=over 1
=item Win32::OLE
=item Excel needs to be installed
=back
=head1 Description
Like perl -ane with a plaintext file, excelPerl.pl loops over an excel file and automatically (-a) splits up the columns of one row into an array with the name @F. Since @F is a ied array, by changing one element of @F you change the content of the excel cells.
If you need one whole line with a tailing \n, you can use the variable $_ (like perl -ne) which joins @F by the value given in the parameter -colseparator (default: \t)
If you need to know the current line number (like $. for while), you can use the special variable $. (big surprise).
=head1 Params:
-file String: Name of excel file (full path!) -sheetnumber Integer: which sheet shell I read (1..n, default: 1) -headline Integer: line number of headline (used for skipping headline) -ane String: Perl-Code to execute -colseparator String: how to join columns in $_ (default: \t)
Abbreviations of params are allowed, as long as they are unique, e.g
excelPerl.pl -s 2 -h 1 -ane "print qq~$.: $F[0] $F[5]\n~" -f c:\test.xls
excelPerl.pl -ane "$c[0]->{Font}->{Name} = 'Courier New'" -f c:\test.xls
=head1 Special Variables:
$. Row number @F Array containing the values of one complete row $_ Row joined by -colseparator, with \n at the end @c Advanced: List of cells (e.g. to change a format). You may even use excel constants.
=head2 Some examples for $c[$i]-> ($i is index of column):
{Font}->{Name} name of font: 'Courier New', 'Arial', ... {Font}->{FontStyle} style of font: 'Bold Italic', ... {Font}->{Size} font size: ... 10 11 12 ... {Font}->{Strikethrough} strike through: 0 or 1 {HorizontalAlignment} align: xlCenter, xlRight, xlLeft, xlJustify {VerticalAlignment} align vertically: xlCenter, xlTop, xlBottom {WrapText} wrap text in line: 0 or 1 {FormulaR1C1} formula: "=CONCATENATE(RC[-1],$R[-1]C[-1])"
=head1 Bugs/...
This code is very, very, very experimental!
If you do changes, you better save the workbook manually, that's the reason why the parameter -visible always is on.
=head1 SEE ALSO
I heard about XLSperl from John Allen which is a great tool. But it only allows to read excel files and not to change them on the fly. Since I often need this feature, I decided to write it with Win32::OLE, and it was not difficult. But unlike XLSperl, excelPerl only runs under Windows and needs an installed version of Excel. But starting with v0.10, it supports @c which is a list containing the cells of the actual row. You can query or modify elements of @c to get or set cell properties like format, alignment, ...
You can find XLSPerl at L<http://perl.jonallen.info/projects/xlstools>
=head1 Author
Martin Fabiani L<http://www.fabiani.net/>
=cut
Datei ExcelPerl.pm:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
package ExcelPerl; use warnings; use strict; use Carp qw(croak);
our $VERSION = 0.10;
# use Readonly; # better not, since Readonly is no standard module yet use Win32::OLE; use Win32::OLE::Const 'Microsoft Excel'; use Win32::OLE::Variant; Win32::OLE->Option( Warn => 3);
use vars qw( @ObjInterfaceMethods @F );
# which object interface methods shell be available as standard #Readonly::Array: Readonly is not yet a standard module :-( @ObjInterfaceMethods = qw( excelFile excel workBook worksheet colSeparator worksheetNumber currentRow lastRow maxCol changeCount );
# install object interface methods at startup foreach my $method ( @ObjInterfaceMethods ) { no strict 'refs'; # Sub::Install is no standard module :-( *{ $method } = sub { my( $self, @values ) = @_; $self->{ $method } = $values[0] if scalar @values; return $self->{ $method }; }; } # foreach
# ============================================================ sub new { my( $class, %params ) = @_;
my $self = bless {}, $class; foreach my $param (keys %params) { $self->$param( $params{$param} ); } # foreach
return $self; } # new # ------------------------------------------------------------ sub DESTROY { # my $self = shift; } # DESTROY # ------------------------------------------------------------ sub increaseCurrentRow { my $self = shift; $self->currentRow( 1 + $self->currentRow ); } # increaseCurrentRow # ------------------------------------------------------------ sub increaseChangeCount { my $self = shift; no warnings; $self->changeCount( 1 + $self->changeCount ); } # increaseChangeCount # ------------------------------------------------------------ sub open { my( $self ) = @_;
my $filename = $self->excelFile; defined $filename or croak "Error: no filename given"; -f $filename or croak( "Error: filename '$filename' doesn't exist");
my $excel; # try to re-use running instance of Excel eval { $excel = Win32::OLE->GetActiveObject( 'Excel.Application' ) }; die "Error: no Excel installed\n" if $@;
unless( defined $excel ) { # if not running, start excel $excel = Win32::OLE->new( 'Excel.Application', sub { $_[0]->Quit } ) or die "Error: can't start Excel\n"; } # unless $self->excel( $excel );
$self->workBook( $excel->Workbooks->Open( $filename ) );
$self->worksheet( $self->workBook->Worksheets( $self->worksheetNumber ) );
my $range = $self->worksheet->UsedRange->{Value}; $self->lastRow( $#$range + 1 ); $self->currentRow( 0 );
return $self; } # open # ------------------------------------------------------------ sub getNextRow { my( $self, $F ) = @_;
$self->increaseCurrentRow;
my $row = $self->currentRow; my $sheet = $self->worksheet; return if $row > $self->lastRow;
# thanks to pKai for this idea: my $bottomRight = ( split( /:/, $self->worksheet->UsedRange->address(0,0)) )[-1]; my( $colNameMax, $rowNumMax ) = $bottomRight =~ /^([A-Z]+)(\d+)$/; $self->lastRow( $rowNumMax ); $self->maxCol ( $colNameMax ); my $rowData = $sheet->Range("A$row:" . $self->maxCol . $row)->{Value};
# prepare ole objects for return representing array of cells my @cells = (); for my $cell ('A' .. $self->maxCol) { push( @cells, $sheet->Range("$cell$row") ); } # for
tie( @$F, 'ExcelPerl::RowArray', $self );
foreach my $value ( ref $rowData ? @{ $rowData->[0] } : $rowData ) { push( @$F, $value ); } # foreach
return @cells; } # getNextRow # ------------------------------------------------------------
# ============================================================ package ExcelPerl::RowArray; # ------------------------------------------------------------ use Tie::Array; use vars qw( @ISA ); @ISA = qw( Tie::Array ); # ------------------------------------------------------------ sub TIEARRAY { my( $class, $excelObj ) = @_;
my $self = bless( { data => [] }, $class ); $self->{excelObj} = $excelObj; $self->{row} = $excelObj->currentRow;
return $self; } # TIEARRAY # ------------------------------------------------------------ sub FETCH { my( $self, $index) = @_; return $self->{data}->[ $index ]; } # FETCH # ------------------------------------------------------------ sub STORE { my( $self, $index, $newValue ) = @_;
my $excelObj = $self->{excelObj}; my $sheet = $excelObj->worksheet;
my $row = $excelObj->currentRow; my $col = $index + 1; my $oldValue = $self->{data}->[$index];
if( ( not defined $oldValue and defined $newValue ) or ( defined $oldValue and not defined $newValue ) or ( $oldValue ne $newValue ) ) { $excelObj->increaseChangeCount; $sheet ->Cells($row, $col)->{'Value' } = $newValue; $self ->{'data'} ->[ $index ] = $newValue; } # if } # STORE # ------------------------------------------------------------ sub FETCHSIZE { my $self = shift; return $#{ $self->{data} }; } # FETCHSIZE # ------------------------------------------------------------ sub STORESIZE { my( $self, $newLength ) = @_; $#{ $self->{data} } = $newLength; } # STORESIZE # ------------------------------------------------------------ sub PUSH { my $self = shift; return push( @{ $self->{data} }, @_ ); } # PUSH # ------------------------------------------------------------ sub CLEAR { my( $self ) = @_; my $data = $self->{data}; for my $i (0..$#$data) { $self->STORE( $i, '' ); } # for } # CLEAR # ------------------------------------------------------------ sub DESTROY { # my $self = shift; } # DESTROY # ------------------------------------------------------------ 1; # modules have to return a true value
Viel Spass, und bitte um Kritik.
View full thread excelPerl
|