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   7 use strict;
  1         3  
  1         33  
32 1     1   6 use warnings;
  1         3  
  1         64  
33             our ($DEBUG, @EXPORT_OK);
34             $DEBUG = 0 unless defined $DEBUG;
35             our $VERSION = '1.63';
36              
37 1     1   957 use Spreadsheet::ParseExcel;
  1         51031  
  1         36  
38 1     1   9 use Exporter;
  1         3  
  1         41  
39 1     1   7 use SQL::Translator::Utils qw(debug normalize_name);
  1         4  
  1         57  
40              
41 1     1   10 use base qw(Exporter);
  1         2  
  1         1086  
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         25 my $args = $tr->parser_args;
60 1   50     19 my $filename = $tr->filename || return;
61 1         33 my $wb = Spreadsheet::ParseExcel::Workbook->Parse( $filename );
62 1         25358 my $schema = $tr->schema;
63 1         86 my $table_no = 0;
64              
65 1   50     6 my $wb_count = $wb->{'SheetCount'} || 0;
66 1         5 for my $num ( 0 .. $wb_count - 1 ) {
67 3         6 $table_no++;
68 3         12 my $ws = $wb->Worksheet( $num );
69 3   33     76 my $table_name = normalize_name( $ws->{'Name'} || "Table$table_no" );
70              
71 3         10 my @cols = $ws->ColRange;
72 3 100       34 next unless $cols[1] > 0;
73              
74 1         6 my $table = $schema->add_table( name => $table_name );
75              
76 1         5 my @field_names = ();
77 1         5 for my $col ( $cols[0] .. $cols[1] ) {
78 7         48 my $cell = $ws->Cell(0, $col);
79 7         87 my $col_name = normalize_name( $cell->{'Val'} );
80 7         52 my $data_type = ET_to_ST( $cell->{'Type'} );
81 7         18 push @field_names, $col_name;
82              
83 7 50       28 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       144 if ( $col == 0 ) {
93 1         20 $table->primary_key( $field->name );
94 1         16 $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     9 unless (
102             defined $args->{'scan_fields'} &&
103             $args->{'scan_fields'} == 0
104             ) {
105 1         5 my %field_info = map { $_, {} } @field_names;
  7         16  
106              
107 1 50 66     19 for(
108             my $iR = $ws->{'MinRow'} == 0 ? 1 : $ws->{'MinRow'};
109             defined $ws->{'MaxRow'} && $iR <= $ws->{'MaxRow'};
110             $iR++
111             ) {
112 4   66     17 for (
113             my $iC = $ws->{'MinCol'};
114             defined $ws->{'MaxCol'} && $iC <= $ws->{'MaxCol'};
115             $iC++
116             ) {
117 28         43 my $field = $field_names[ $iC ];
118 28         66 my $data = $ws->{'Cells'}[ $iR ][ $iC ]->{'_Value'};
119 28 100 66     103 next if !defined $data || $data eq '';
120 6         33 my $size = [ length $data ];
121 6         11 my $type;
122              
123 6 100 66     48 if ( $data =~ /^-?\d+$/ ) {
    100 66        
124 2         5 $type = 'integer';
125             }
126             elsif (
127             $data =~ /^-?[,\d]+\.[\d+]?$/
128             ||
129             $data =~ /^-?[,\d]+?\.\d+$/
130             ||
131             $data =~ /^-?\.\d+$/
132             ) {
133 1         12 $type = 'float';
134             my ( $w, $d ) =
135 1 50       7 map { s/,//g; length $_ || 1 }
  2         4  
  2         8  
136             split( /\./, $data )
137             ;
138 1         4 $size = [ $w + $d, $d ];
139             }
140             else {
141 3         6 $type = 'char';
142             }
143              
144 6         11 for my $i ( 0, 1 ) {
145 12 100       28 next unless defined $size->[ $i ];
146 7   50     33 my $fsize = $field_info{ $field }{'size'}[ $i ] || 0;
147 7 50       14 if ( $size->[ $i ] > $fsize ) {
148 7         16 $field_info{ $field }{'size'}[ $i ] = $size->[ $i ];
149             }
150             }
151              
152 6         32 $field_info{ $field }{ $type }++;
153             }
154             }
155              
156 1         5 for my $field ( keys %field_info ) {
157 7   100     20 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       26 $field_info{ $field }{'integer'} ? 'integer' : 'char';
    100          
    100          
162              
163 7 50 66     26 if ( $data_type eq 'char' && scalar @$size == 2 ) {
164 0         0 $size = [ $size->[0] + $size->[1] ];
165             }
166              
167 7         20 my $field = $table->get_field( $field );
168 7 50       178 $field->size( $size ) if $size;
169 7         78 $field->data_type( $data_type );
170             }
171             }
172             }
173              
174 1         9 return 1;
175             }
176              
177             sub ET_to_ST {
178 7     7 0 13 my $et = shift;
179 7 50       33 $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