Removed /usr/local from CDPATH
[clearscm.git] / lib / SpreadSheet.pm
1 =pod
2
3 =head1 NAME $RCSfile: SpreadSheet.pm,v $
4
5 Object oriented interface to Excel Spreadsheets
6
7 =head1 VERSION
8
9 =over
10
11 =item Author
12
13 Andrew DeFaria <Andrew@ClearSCM.com>
14
15 =item Revision
16
17 $Revision: 1.1 $
18
19 =item Created
20
21 Tue Nov 20 10:40:53 PST 2012
22
23 =item Modified
24
25 $Date: 2012/11/21 02:53:06 $
26
27 =back
28
29 =head1 SYNOPSIS
30
31 Provides access to Excel Spreadsheets
32
33  # Create SpreadSheet object
34  my $ss = SpreadSheet->new ($file)
35
36  # Get data in a sheet
37  my @rows = $ss->getData ($sheetName);
38  
39  foreach (@rows) {
40    my %row = %$_;
41    
42    foreach (keys %row) {
43      display "$_: $row{$_}";
44    } # foreach
45  } # foreach
46  
47 =head1 DESCRIPTION
48
49 This module provides a simple, object oriented interface to a SpreadSheet.
50
51 =head1 ROUTINES
52
53 The following routines are exported:
54
55 =cut
56
57 package SpreadSheet;
58
59 use strict;
60 use warnings;
61
62 use File::Basename;
63
64 use Display;
65 use OSDep;
66 use TimeUtils;
67
68 use Win32::OLE;
69 use Win32::OLE::Const 'Microsoft Excel';
70
71 sub _setError ($$) {
72   my ($self, $errmsg, $error) = @_;
73   
74   $self->{errmsg} = $errmsg;
75   $self->{error}  = $error;
76   
77   return;
78 } # _setError
79
80 sub DESTROY {
81   my ($self) = @_;
82   
83   undef $self->{excel} if $self->{excel};
84 } # DESTROY
85
86 sub new (;$) {
87   my ($class, $filename) = @_;
88
89 =pod
90
91 =head2 new ()
92
93 Construct a new SpreadSheet object. 
94
95 Parameters:
96
97 =for html <blockquote>
98
99 =over
100
101 =item $filename
102
103 Pathname to the spreadsheet file
104
105 =back
106
107 =for html </blockquote>
108
109 Returns:
110
111 =for html <blockquote>
112
113 =over
114
115 =item SpreadSheet object
116
117 =back
118
119 =for html </blockquote>
120
121 =cut
122
123   my $self = bless {
124     filename => $filename,
125     excel    => Win32::OLE->new ('Excel.Application', 'Quit'),
126   }, $class;
127
128   # Excel needs a Windows based absolute path
129   if ($^O eq 'cygwin') {
130     my @output = `cygpath -wa $self->{filename}`;
131     chomp @output;
132
133     $self->{filename} = $output[0];
134   } else {
135     require Cwd;
136     
137     Cwd->import ('abs_path');
138
139     $self->{filename} = abs_path ($self->{filename});
140   } # if
141
142   $self->{book} = $self->{excel}->Workbooks->Open ($self->{filename});
143   
144   $self->_setError ("Unable to open spreadsheet $self->{filename}", 1)
145     unless $self->{book};
146
147   return $self;
148 } # new
149
150 sub getSheet (;$) {
151   my ($self, $sheet) = @_;
152   
153 =pod
154
155 =head2 getSheet ($)
156
157 Return the data in the sheet specified
158
159 Parameters:
160
161 =for html <blockquote>
162
163 =over
164
165 =item $sheet
166
167 The name of the sheet
168
169 =back
170
171 =for html </blockquote>
172
173 Returns:
174
175 =for html <blockquote>
176
177 =over
178
179 =item @records
180
181 Array of rows each represented by a hash. Note this assumes that the first row
182 are field headings and are used as the keys for the hash.
183
184 =back
185
186 =for html </blockquote>
187
188 =cut
189
190   my @data;
191   
192   unless ($self->{book}) {
193     $self->_setError ("Failed to open SpreadSheet ($self->{filename})", 1);
194     
195     return;
196   } # unless
197
198   if ($sheet) {
199     $self->{sheet} = $self->{book}->Worksheets->Item ($sheet);
200   } else {
201     $sheet = 1;
202     
203     $self->{sheet} = $self->{book}->Worksheets (1);
204   } # if
205   
206   unless ($self->{sheet}) {
207         $self->_setError ("Unable to get sheet $sheet from spreadsheet $self->{filename}", 1);
208         
209         return;
210   } # unless
211     
212   # Now parse the spreadsheet
213   my $lastRow = $self->{sheet}->UsedRange->Find ({
214                     What            => '*',
215                     SearchDirection => xlPrevious,
216                     SearchOrder     => xlByRows,
217                    })->{Row};
218   my $lastColumn = $self->{sheet}->UsedRange->Find ({
219                      What             => '*',
220                      SearchDirection  => xlPrevious,
221                      SearchOrder      => xlByColumns,
222                    })->{Column};
223
224   # Find columns by headings
225   my (@fields, $row, $column);
226
227   for ($column = 1; $column <= $lastColumn; $column++) {
228     $fields[$column - 1] = $self->{sheet}->Cells (1, $column)->{Value};
229   } # for
230   
231   # Get data
232   for ($row = 2; $row <= $lastRow; $row++) {
233     my %row;
234     
235     for ($column = 1; $column <= $lastColumn; $column++) {
236       $row{$fields[$column - 1]} = 
237         $self->{sheet}->Cells ($row, $column)->{Value};
238         
239       $row{$fields[$column - 1]} ||= '';
240     } # for
241     
242     push @data, \%row;
243   } # for
244     
245   return @data;
246 } # getSheet
247
248 1;