ac9955515ef116704f20379137ab2f411c1cd1a6
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / Excel.pm
1 package SQL::Translator::Parser::Excel;
2
3 =head1 NAME
4
5 SQL::Translator::Parser::Excel - parser for Excel
6
7 =head1 SYNOPSIS
8
9   use SQL::Translator;
10
11   my $translator = SQL::Translator->new;
12   $translator->parser('Excel');
13
14 =head1 DESCRIPTION
15
16 Parses an Excel spreadsheet file using Spreadsheet::ParseExcel.
17
18 =head1 OPTIONS
19
20 =over
21
22 =item * scan_fields
23
24 Indicates that the columns should be scanned to determine data types
25 and field sizes.  True by default.
26
27 =back
28
29 =cut
30
31 use strict;
32 use vars qw($DEBUG $VERSION @EXPORT_OK);
33 $DEBUG = 0 unless defined $DEBUG;
34 $VERSION = '1.59';
35
36 use Spreadsheet::ParseExcel;
37 use Exporter;
38 use SQL::Translator::Utils qw(debug normalize_name);
39
40 use base qw(Exporter);
41
42 @EXPORT_OK = qw(parse);
43
44 my %ET_to_ST  = (
45     'Text'    => 'VARCHAR',
46     'Date'    => 'DATETIME',
47     'Numeric' => 'DOUBLE',
48 );
49
50 # -------------------------------------------------------------------
51 # parse($tr, $data)
52 #
53 # Note that $data, in the case of this parser, is unuseful.
54 # Spreadsheet::ParseExcel works on files, not data streams.
55 # -------------------------------------------------------------------
56 sub parse {
57     my ($tr, $data) = @_;
58     my $args        = $tr->parser_args;
59     my $filename    = $tr->filename || return;
60     my $wb          = Spreadsheet::ParseExcel::Workbook->Parse( $filename );
61     my $schema      = $tr->schema;
62     my $table_no    = 0;
63
64     my $wb_count = $wb->{'SheetCount'} || 0;
65     for my $num ( 0 .. $wb_count - 1 ) {
66         $table_no++;
67         my $ws         = $wb->Worksheet( $num );
68         my $table_name = normalize_name( $ws->{'Name'} || "Table$table_no" );
69
70         my @cols = $ws->ColRange;
71         next unless $cols[1] > 0;
72
73         my $table = $schema->add_table( name => $table_name );
74
75         my @field_names = ();
76         for my $col ( $cols[0] .. $cols[1] ) {
77             my $cell      = $ws->Cell(0, $col);
78             my $col_name  = normalize_name( $cell->{'Val'} );
79             my $data_type = ET_to_ST( $cell->{'Type'} );
80             push @field_names, $col_name;
81
82             my $field = $table->add_field(
83                 name              => $col_name,
84                 data_type         => $data_type,
85                 default_value     => '',
86                 size              => 255,
87                 is_nullable       => 1,
88                 is_auto_increment => undef,
89             ) or die $table->error;
90
91             if ( $col == 0 ) {
92                 $table->primary_key( $field->name );
93                 $field->is_primary_key(1);
94             }
95         }
96
97         #
98         # If directed, look at every field's values to guess size and type.
99         #
100         unless (
101             defined $args->{'scan_fields'} &&
102             $args->{'scan_fields'} == 0
103         ) {
104             my %field_info = map { $_, {} } @field_names;
105
106             for(
107                 my $iR = $ws->{'MinRow'} == 0 ? 1 : $ws->{'MinRow'};
108                 defined $ws->{'MaxRow'} && $iR <= $ws->{'MaxRow'};
109                 $iR++
110             ) {
111                for (
112                     my $iC = $ws->{'MinCol'};
113                     defined $ws->{'MaxCol'} && $iC <= $ws->{'MaxCol'};
114                     $iC++
115                 ) {
116                     my $field = $field_names[ $iC ];
117                     my $data  = $ws->{'Cells'}[ $iR ][ $iC ]->{'_Value'};
118                     next if !defined $data || $data eq '';
119                     my $size  = [ length $data ];
120                     my $type;
121
122                     if ( $data =~ /^-?\d+$/ ) {
123                         $type = 'integer';
124                     }
125                     elsif (
126                         $data =~ /^-?[,\d]+\.[\d+]?$/
127                         ||
128                         $data =~ /^-?[,\d]+?\.\d+$/
129                         ||
130                         $data =~ /^-?\.\d+$/
131                     ) {
132                         $type = 'float';
133                         my ( $w, $d ) =
134                             map { s/,//g; length $_ || 1 }
135                             split( /\./, $data )
136                         ;
137                         $size = [ $w + $d, $d ];
138                     }
139                     else {
140                         $type = 'char';
141                     }
142
143                     for my $i ( 0, 1 ) {
144                         next unless defined $size->[ $i ];
145                         my $fsize = $field_info{ $field }{'size'}[ $i ] || 0;
146                         if ( $size->[ $i ] > $fsize ) {
147                             $field_info{ $field }{'size'}[ $i ] = $size->[ $i ];
148                         }
149                     }
150
151                     $field_info{ $field }{ $type }++;
152                 }
153             }
154
155             for my $field ( keys %field_info ) {
156                 my $size      = $field_info{ $field }{'size'} || [ 1 ];
157                 my $data_type =
158                     $field_info{ $field }{'char'}    ? 'char'    :
159                     $field_info{ $field }{'float'}   ? 'float'   :
160                     $field_info{ $field }{'integer'} ? 'integer' : 'char';
161
162                 if ( $data_type eq 'char' && scalar @$size == 2 ) {
163                     $size = [ $size->[0] + $size->[1] ];
164                 }
165
166                 my $field = $table->get_field( $field );
167                 $field->size( $size ) if $size;
168                 $field->data_type( $data_type );
169             }
170         }
171     }
172
173     return 1;
174 }
175
176 sub ET_to_ST {
177     my $et = shift;
178     $ET_to_ST{$et} || $ET_to_ST{'Text'};
179 }
180
181 1;
182
183 # -------------------------------------------------------------------
184 # Education is an admirable thing,
185 # but it is as well to remember that
186 # nothing that is worth knowing can be taught.
187 # Oscar Wilde
188 # -------------------------------------------------------------------
189
190 =pod
191
192 =head1 AUTHORS
193
194 Mike Mellilo <mmelillo@users.sourceforge.net>,
195 darren chamberlain E<lt>dlc@users.sourceforge.netE<gt>,
196 Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
197
198 =head1 SEE ALSO
199
200 Spreadsheet::ParseExcel, SQL::Translator.
201
202 =cut