File Coverage

blib/lib/XML/Excel.pm
Criterion Covered Total %
statement 92 111 82.8
branch 38 68 55.8
condition 18 37 48.6
subroutine 8 8 100.0
pod 0 5 0.0
total 156 229 68.1


line stmt bran cond sub pod time code
1             package XML::Excel;
2            
3 1     1   2548 use Spreadsheet::ParseExcel;
  1         100266  
  1         35  
4 1     1   14 use Carp;
  1         3  
  1         95  
5            
6             #use strict;
7 1     1   6 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  1         7  
  1         3100  
8            
9             require 5.004;
10             require Exporter;
11             #require DynaLoader;
12             require AutoLoader;
13            
14             @ISA = qw(Exporter);# DynaLoader);
15            
16             @EXPORT = qw(
17            
18             );
19             $VERSION = '0.01';
20            
21             #bootstrap XML::Excel $VERSION;
22            
23             my $ParseExcel_obj; #Declared for global usage
24             my $_error; #Error container
25            
26            
27             sub new($;$)
28             {
29            
30 4     4 0 422 my $class = shift;
31 4   100     24 my $attr = shift || {};
32            
33 4         27 my $self = { 'error_out' => 0,
34             'column_headings' => 0,
35             'column_data' => 0,
36             'ParseExcel' => 0,
37             %$attr
38             };
39            
40 4         11 bless $self, $class;
41            
42 4 50       13 if ($attr->{ParseExcel}) ### if custom Spreadsheet::ParseExcel object is provided use it
43             {
44 0         0 $ParseExcel_obj = $attr->{ParseExcel};
45 0         0 $attr->{ParseExcel} = undef;
46             } else { ### else create our own Spreadsheet::ParseExcel object with it's defaults
47 4         26 $ParseExcel_obj = Spreadsheet::ParseExcel->new();
48             }
49            
50 4         5363 return $self;
51            
52             }
53            
54             sub parse_doc
55             {
56 3     3 0 31 my $class = shift;
57 3   33     12 my $file_name = shift || croak "Usage: parse_doc(file_name, [\%attr])";
58 3         6 my $attr = shift; # %attr (headings, sub_char)
59            
60 3 50       18 my $workbook = $ParseExcel_obj->Parse($file_name) or die "Error: $!\n";
61 3         67133 my $worksheet = $workbook->{Worksheet}[0];
62            
63 3 50       14 $_error = "Problems parsing the file: $file_name " unless defined($workbook);
64            
65 3 0 33     27 croak "$_error" if ($class->{'error_out'} == 1 && $@);
66            
67 3         5 my @col_headings;
68            
69 3 100       28 $attr->{headings} = 0 unless (exists($attr->{headings})); ### default headings to 0
70            
71 3 100       12 if ($attr->{headings} == 0) ### No headings to be used from file
72             {
73 1 50       5 if ($class->{column_headings})
74             {
75 1         2 @col_headings = @{$class->{column_headings}}; ### if column_heading are provided
  1         7  
76             } ### by user, use them
77            
78             }
79             #my $line; ### declare $line outside of scope to be use later
80            
81 3 100       16 if ($attr->{headings} != 0)
82             {
83 2 100       22 my $cols_returned = $get_header->($worksheet, \@col_headings, defined($attr->{sub_char})? $attr->{sub_char}:undef );
84 2 50       7 $_error = "There were no columns returned for headers, please check your excel file" if (!$cols_returned);
85 2 50       13 croak "$_error" if ($class->{'error_out'} == 1);
86            
87 2 50       8 return 0 if (!$cols_returned);
88             }
89            
90 3         4 my @arr_cols_data; ### declare @arr_cols_data to be used for stacking data
91            
92            
93             #######
94            
95 3         5 my ($cell, $row_count, $cell_count);
96            
97 3   66     24 for($row_count = $attr->{headings}; defined $worksheet->{MaxRow} && $row_count <= $worksheet->{MaxRow}; $row_count++)
98             {
99 25         31 my @cols_data;
100 25   66     125 for(my $cell_count = $worksheet->{MinCol}; defined $worksheet->{MaxCol} && $cell_count <= $worksheet->{MaxCol} ; $cell_count++)
101             {
102 275         2048 $cell = $worksheet->{Cells}[$row_count][$cell_count];
103 275 100       889 defined($cell) ? push(@cols_data, $cell->Value) : push(@cols_data, undef);
104             }
105 25         121 $escape_char->(\@cols_data);
106 25         304 push(@arr_cols_data, \@cols_data);
107             }
108            
109 3         6 $class->{'column_headings'} = \@col_headings; ### assign reference of @col_headings (xml headers) to object
110 3         7 $class->{'column_data'} = \@arr_cols_data; ### assign reference of @arr_cols_data (xml data) to object
111            
112 3         23 return 1;
113             }
114            
115             sub print_xml
116             {
117 4     4 0 37 my $class = shift;
118 4   50     11 my $file_out = shift || 0;
119 4   100     15 my $args = shift || {}; # %attr (file_tag, parent_tag, format)
120            
121 4 100       13 $args->{file_tag} = "records" unless $args->{file_tag}; #default {parent_tag} to record if not supplied
122 4 50       16 $args->{parent_tag} = "record" unless $args->{parent_tag};
123 4 100       9 $args->{format} = "\t" unless $args->{format}; #default {format} to tab if not supplied
124            
125 4         10 $class->{'document_element'} = $args->{file_tag}; ### Used later for declare_doctype() method
126            
127 4 50 33     20 if ($class->{'column_data'} == 0 || ($class->{'column_headings'} == 0 && $class->{'headings'}))
      33        
128             {
129 0         0 croak "There is no data to print, make sure that you parsed the document before printing";
130             }
131            
132             ###Open file $file_out for output or output to STDOUT
133 4 50       17 if ($file_out)
134             {
135 4         497 open FILE_OUT, ">$file_out";
136             } else {
137 0         0 *FILE_OUT = *STDOUT;
138             }
139            
140 4 100       42 print FILE_OUT $class->{'declare_xml'}."\n" if $class->{'declare_xml'};
141             ###This will replace the non-interpolated $class->{'document_element'} inside the $class->{'declare_doctype'} to get the real value
142             ###Should be replace with something more practical in the future...
143 4 100       18 $class->{'declare_doctype'} =~ s/\$class\-\>\{\'document_element\'\}/$class->{'document_element'}/ if $class->{'declare_doctype'};
144            
145 4 100       12 print FILE_OUT $class->{'declare_doctype'}."\n" if $class->{'declare_doctype'};
146 4         56 print FILE_OUT "<$args->{file_tag}>", "\n"; ### print initial document tag
147            
148             ### declare the $tag for <$tag> and $loop_num for headers and data index tracking
149 4         5 my $tag;
150             my $loop_num;
151            
152 4 50       5 if ($#{$class->{'column_headings'}} > 0) ### if column headings are provided
  4         12  
153             {
154            
155 4         5 foreach $loop_num (0..$#{$class->{'column_data'}})
  4         10  
156             {
157 25         55 print FILE_OUT $args->{format}, "<$args->{parent_tag}>", "\n";
158 25         22 foreach $tag (0..$#{$class->{'column_headings'}})
  25         50  
159             {
160 275         663 print FILE_OUT $args->{format}, $args->{format}, "<$class->{'column_headings'}[$tag]>$class->{'column_data'}[$loop_num][$tag]{'column_headings'}[$tag]>\n";
161             }
162 25         59 print FILE_OUT $args->{format}, "{parent_tag}>", "\n";
163             }
164            
165             } else { ### if column headings are not provided we default to
166            
167 0         0 foreach $loop_num (0..$#{$class->{'column_data'}})
  0         0  
168             {
169 0         0 print FILE_OUT $args->{format}, "<$args->{parent_tag}>", "\n";
170 0         0 foreach $tag (0..$#{$class->{'column_data'}->[$loop_num]})
  0         0  
171             {
172 0         0 print FILE_OUT $args->{format}, $args->{format}, "$class->{'column_data'}[$loop_num][$tag]\n";
173             }
174 0         0 print FILE_OUT $args->{format}, "{parent_tag}>", "\n";
175             }
176             }
177            
178 4         13 print FILE_OUT "{file_tag}>", "\n"; ### print the final document tag
179            
180 4         278 close FILE_OUT;
181            
182             }
183            
184             sub declare_xml
185             {
186            
187 1     1 0 19 my $class = shift;
188 1   50     6 my $attr = shift || {};
189            
190             ### Attributes: version, encoding, standalone
191            
192 1 50       4 if (exists $attr->{'version'})
193             {
194 1         12 $class->{'declare_xml'} = "{'version'}\""
195             }
196             else
197             {
198 0         0 $csvxml_error = "The version attribute must be specified for declare_xml()\n
199             Usage: declare_xml\({version=>1.0, [encoding=>..., standalone=>yes/no]}\)";
200 0 0       0 croak "$csvxml_error" if ($class->{'error_out'} == 1);
201             }
202            
203 1 50       7 $class->{'declare_xml'} .= " encoding=\"$attr->{'encoding'}\"" if exists $attr->{'encoding'};
204 1 50 33     10 if (exists $attr->{'standalone'} && ($attr->{'standalone'} =~ /[yes|no]/))
    0          
205             {
206 1         5 $class->{'declare_xml'} .= " standalone=\"$attr->{'standalone'}\"";
207             }
208             elsif (!($attr->{'standalone'} =~ /[yes|no]/))
209             {
210 0         0 $csvxml_error = "The standalone attribute must be yes|no for declare_xml()\n
211             Usage: declare_xml\({version=>1.0, [encoding=>..., standalone=>yes/no]}\)";
212 0 0       0 croak "$csvxml_error" if ($class->{'error_out'} == 1);
213             }
214            
215 1         2 $class->{'declare_xml'} .= "?>";
216            
217 1         2 return $class->{'declare_xml'};
218            
219             }
220            
221             sub declare_doctype
222             {
223            
224 1     1 0 9 my $class = shift;
225 1   50     4 my $attr = shift || {};
226            
227             ### Attributes: source, location1, location2, subset
228            
229 1         3 $class->{'declare_doctype'} = '{\'document_element\'}';
230 1 50 33     7 if ($attr->{source} eq "SYSTEM" || $attr->{source} eq "PUBLIC")
231             {
232 1         6 $class->{'declare_doctype'} .= " $attr->{'source'}";
233             }
234             else
235             {
236 0         0 $csvxml_error = "The source attribute is not set correctly";
237 0 0       0 croak "$csvxml_error" if ($class->{'error_out'} == 1);
238             }
239            
240 1 50 33     8 if (exists $attr->{location1} && !(exists $attr->{subset}))
241             {
242 1         5 $class->{'declare_doctype'} .= " \"$attr->{'location1'}\"";
243             }
244             else
245             {
246 0         0 $csvxml_error = "$attr->{'source'} location1 must be specified";
247 0 0       0 croak "$csvxml_error" if ($class->{'error_out'} == 1);
248             }
249            
250 1 50       6 $class->{'declare_doctype'} .= " \"$attr->{'location2'}\"" if exists $attr->{'location2'};
251 1 50       4 $class->{'declare_doctype'} .= " [$attr->{'subset'}]" if exists $attr->{'subset'};
252            
253            
254 1         1 $class->{'declare_doctype'} .= ">";
255            
256 1         2 return $class->{'declare_doctype'};
257            
258             }
259            
260             $get_header = sub()
261             {
262             my $worksheet = shift;
263             my $ref_col = shift;
264             my $sub_char = shift;
265            
266             my ($cell, $row_count, $cell_count);
267            
268             for(my $row_count = 0; defined $worksheet->{MaxRow} && $row_count < 1; $row_count++)
269             {
270             for(my $cell_count = $worksheet->{MinCol}; defined $worksheet->{MaxCol} && $cell_count <= $worksheet->{MaxCol} ; $cell_count++)
271             {
272             $cell = $worksheet->{Cells}[$row_count][$cell_count];
273             defined($cell) ? push(@$ref_col, $cell->Value) : push(@$ref_col, undef);
274             }
275             }
276            
277            
278             if (defined($sub_char))
279             {
280             map {s/^([^a-zA-Z|_|:]|((x|X)(m|M)(l|L)))/$sub_char/g;} @$ref_col; #convert all beginning \n or \t or \s to '_'
281             map {s/[^a-zA-Z|^-|^.|^0-9|^:]/$sub_char/g;} @$ref_col;
282             }
283            
284             #print __LINE__.": $ref_col->[0]\n";
285            
286             if ($ref_col) {return $#$ref_col;}else{return 0;}
287             };
288            
289             $escape_char = sub() ### Escape char per XML 1.0 specifications
290             { ### Needs to be optimized for faster processing
291            
292             my $arg = shift;
293             if (ref($arg) eq 'ARRAY')
294             {
295             my $arr_index;
296             foreach $arr_index (0..$#{$arg})
297             {
298             @{$arg}[$arr_index] =~ s/\&/\&\;/g;
299             @{$arg}[$arr_index] =~ s/\
300             @{$arg}[$arr_index] =~ s/\>/\>\;/g;
301             @{$arg}[$arr_index] =~ s/\'/\&apos\;/g;
302             @{$arg}[$arr_index] =~ s/\"/\"\;/g;
303             @{$arg}[$arr_index] =~ s/([\x80-\xFF])/$XmlUtf8Encode->(ord($1))/ge;
304             }
305             }
306             elsif (ref($arg) eq 'SCALAR')
307             {
308             ${$arg} =~ s/\&/\&\;/g;
309             ${$arg} =~ s/\
310             ${$arg} =~ s/\>/\>\;/g;
311             ${$arg} =~ s/\'/\&apos\;/g;
312             ${$arg} =~ s/\"/\"\;/g;
313             ${$arg} =~ s/([\x80-\xFF])/$XmlUtf8Encode->(ord($1))/ge;
314             }
315             else
316             {
317             croak "Usage: $escape_char->(\@cols_data) or $escape_char->(\$foo)\n";
318             }
319            
320             };
321            
322             $XmlUtf8Encode = sub() {
323            
324             my $n = shift;
325             if ($n < 0x80) {
326             return chr ($n);
327             } elsif ($n < 0x800) {
328             return pack ("CC", (($n >> 6) | 0xc0), (($n & 0x3f) | 0x80));
329             } elsif ($n < 0x10000) {
330             return pack ("CCC", (($n >> 12) | 0xe0), ((($n >> 6) & 0x3f) | 0x80),
331             (($n & 0x3f) | 0x80));
332             } elsif ($n < 0x110000) {
333             return pack ("CCCC", (($n >> 18) | 0xf0), ((($n >> 12) & 0x3f) | 0x80),
334             ((($n >> 6) & 0x3f) | 0x80), (($n & 0x3f) | 0x80));
335             }
336             return $n;
337             };
338            
339            
340             # Preloaded methods go here.
341            
342             # Autoload methods go after =cut, and are processed by the autosplit program.
343            
344             1;
345             __END__