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   9 use strict;
  1         4  
  1         130  
32 1     1   9 use warnings;
  1         2  
  1         181  
33             our ($DEBUG, @EXPORT_OK);
34             $DEBUG = 0 unless defined $DEBUG;
35             our $VERSION = '1.66';
36              
37 1     1   3133 use Spreadsheet::ParseExcel;
  1         135570  
  1         85  
38 1     1   31 use Exporter;
  1         3  
  1         80  
39 1     1   7 use SQL::Translator::Utils qw(debug normalize_name);
  1         2  
  1         95  
40              
41 1     1   7 use base qw(Exporter);
  1         3  
  1         1497  
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 4 my ($tr, $data) = @_;
59 1         34 my $args = $tr->parser_args;
60 1   50     33 my $filename = $tr->filename || return;
61 1         42 my $wb = Spreadsheet::ParseExcel::Workbook->Parse($filename);
62 1         21034 my $schema = $tr->schema;
63 1         61 my $table_no = 0;
64              
65 1   50     6 my $wb_count = $wb->{'SheetCount'} || 0;
66 1         4 for my $num (0 .. $wb_count - 1) {
67 3         6 $table_no++;
68 3         21 my $ws = $wb->Worksheet($num);
69 3   33     331 my $table_name = normalize_name($ws->{'Name'} || "Table$table_no");
70              
71 3         15 my @cols = $ws->ColRange;
72 3 100       35 next unless $cols[1] > 0;
73              
74 1         8 my $table = $schema->add_table(name => $table_name);
75              
76 1         3 my @field_names = ();
77 1         3 for my $col ($cols[0] .. $cols[1]) {
78 7         73 my $cell = $ws->Cell(0, $col);
79 7         133 my $col_name = normalize_name($cell->{'Val'});
80 7         39 my $data_type = ET_to_ST($cell->{'Type'});
81 7         17 push @field_names, $col_name;
82              
83 7 50       40 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       178 if ($col == 0) {
93 1         42 $table->primary_key($field->name);
94 1         28 $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     26 unless (defined $args->{'scan_fields'}
102             && $args->{'scan_fields'} == 0) {
103 1         5 my %field_info = map { $_, {} } @field_names;
  7         24  
104              
105 1 50 66     29 for (
106             my $iR = $ws->{'MinRow'} == 0 ? 1 : $ws->{'MinRow'};
107             defined $ws->{'MaxRow'} && $iR <= $ws->{'MaxRow'};
108             $iR++
109             ) {
110 4   66     17 for (my $iC = $ws->{'MinCol'}; defined $ws->{'MaxCol'} && $iC <= $ws->{'MaxCol'}; $iC++) {
111 28         32 my $field = $field_names[$iC];
112 28         85 my $data = $ws->{'Cells'}[$iR][$iC]->{'_Value'};
113 28 100 66     97 next if !defined $data || $data eq '';
114 6         14 my $size = [ length $data ];
115 6         11 my $type;
116              
117 6 100 66     47 if ($data =~ /^-?\d+$/) {
    100 66        
118 2         5 $type = 'integer';
119             } elsif ($data =~ /^-?[,\d]+\.[\d+]?$/
120             || $data =~ /^-?[,\d]+?\.\d+$/
121             || $data =~ /^-?\.\d+$/) {
122 1         2 $type = 'float';
123 1 50       5 my ($w, $d) = map { s/,//g; length $_ || 1 }
  2         3  
  2         21  
124             split(/\./, $data);
125 1         3 $size = [ $w + $d, $d ];
126             } else {
127 3         7 $type = 'char';
128             }
129              
130 6         10 for my $i (0, 1) {
131 12 100       24 next unless defined $size->[$i];
132 7   50     27 my $fsize = $field_info{$field}{'size'}[$i] || 0;
133 7 50       12 if ($size->[$i] > $fsize) {
134 7         16 $field_info{$field}{'size'}[$i] = $size->[$i];
135             }
136             }
137              
138 6         36 $field_info{$field}{$type}++;
139             }
140             }
141              
142 1         3 for my $field (keys %field_info) {
143 7   100     19 my $size = $field_info{$field}{'size'} || [1];
144             my $data_type
145             = $field_info{$field}{'char'} ? 'char'
146             : $field_info{$field}{'float'} ? 'float'
147 7 100       22 : $field_info{$field}{'integer'} ? 'integer'
    100          
    100          
148             : 'char';
149              
150 7 50 66     24 if ($data_type eq 'char' && scalar @$size == 2) {
151 0         0 $size = [ $size->[0] + $size->[1] ];
152             }
153              
154 7         19 my $field = $table->get_field($field);
155 7 50       322 $field->size($size) if $size;
156 7         81 $field->data_type($data_type);
157             }
158             }
159             }
160              
161 1         11 return 1;
162             }
163              
164             sub ET_to_ST {
165 7     7 0 17 my $et = shift;
166 7 50       34 $ET_to_ST{$et} || $ET_to_ST{'Text'};
167             }
168              
169             1;
170              
171             # -------------------------------------------------------------------
172             # Education is an admirable thing,
173             # but it is as well to remember that
174             # nothing that is worth knowing can be taught.
175             # Oscar Wilde
176             # -------------------------------------------------------------------
177              
178             =pod
179              
180             =head1 AUTHORS
181              
182             Mike Mellilo ,
183             darren chamberlain Edlc@users.sourceforge.netE,
184             Ken Y. Clark Ekclark@cpan.orgE.
185              
186             =head1 SEE ALSO
187              
188             Spreadsheet::ParseExcel, SQL::Translator.
189              
190             =cut