File Coverage

blib/lib/SQL/Translator/Parser/Excel.pm
Criterion Covered Total %
statement 76 77 98.7
branch 26 34 76.4
condition 19 32 59.3
subroutine 8 8 100.0
pod 0 2 0.0
total 129 153 84.3


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