File Coverage

blib/lib/Finance/IIF.pm
Criterion Covered Total %
statement 79 148 53.3
branch 33 80 41.2
condition 8 14 57.1
subroutine 15 17 88.2
pod 7 7 100.0
total 142 266 53.3


line stmt bran cond sub pod time code
1             package Finance::IIF;
2              
3 1     1   51365 use 5.006;
  1         3  
  1         35  
4 1     1   4 use strict;
  1         2  
  1         23  
5 1     1   5 use warnings;
  1         6  
  1         58  
6 1     1   5 use Carp qw(carp croak);
  1         2  
  1         51  
7 1     1   763 use IO::File ();
  1         899  
  1         829  
8              
9             our $VERSION = '0.21_02';
10             $VERSION = eval $VERSION;
11              
12             sub new {
13 16     16 1 7093 my $class = shift;
14 16         41 my %opt = @_;
15 16         23 my $self = {};
16              
17 16   100     99 $self->{debug} = $opt{debug} || 0;
18 16   100     61 $self->{autodetect} = $opt{autodetect} || 0;
19 16   100     63 $self->{field_separator} = $opt{field_separator} || "\t";
20              
21 16         30 bless( $self, $class );
22              
23 16 100       32 if ( $opt{record_separator} ) {
24 1         4 $self->record_separator( $opt{record_separator} );
25             }
26              
27 16 100       31 if ( $opt{file} ) {
28 6         14 $self->file( $opt{file} );
29 6         13 $self->open;
30             }
31 16         85 return $self;
32             }
33              
34             sub file {
35 28     28 1 54 my $self = shift;
36 28 100       56 if (@_) {
37 10 100       36 my @file = ( ref( $_[0] ) eq "ARRAY" ? @{ shift @_ } : (), @_ );
  1         2  
38 10         33 $self->{file} = [@file];
39             }
40 28 100       59 if ( $self->{file} ) {
41 26 100       83 return wantarray ? @{ $self->{file} } : $self->{file}->[0];
  8         28  
42             }
43             else {
44 2         5 return undef;
45             }
46             }
47              
48             sub record_separator {
49 11     11 1 2021 my $self = shift;
50 11 100       26 if (@_) {
51 4 50       16 $self->{record_separator} = $_[0] if ( $_[0] );
52             }
53 11   66     54 return $self->{record_separator} || $/;
54             }
55              
56             sub _filehandle {
57 26     26   35 my $self = shift;
58 26 100       51 if (@_) {
59 7         10 my @args = @_;
60 7 50       33 $self->{_filehandle} = IO::File->new(@args)
61             or croak("Failed to open file '$args[0]': $!");
62 7         537 $self->{_linecount} = 0;
63 7         20 $self->{_filehandle}->binmode();
64             }
65 26 100       105 if ( !$self->{_filehandle} ) {
66 5         624 croak("No filehandle available");
67             }
68 21         66 return $self->{_filehandle};
69             }
70              
71             sub open {
72 8     8 1 18 my $self = shift;
73 8 100       15 if (@_) {
74 1         4 $self->file(@_);
75             }
76 8 100       14 if ( $self->file ) {
77 7         13 $self->_filehandle( $self->file );
78 7 100       20 if ( $self->{autodetect} ) {
79 4 100       15 if ( $self->_filehandle->seek( -2, 2 ) ) {
80 3         25 my $buffer = "";
81 3         8 $self->_filehandle->read( $buffer, 2 );
82 3 100       51 if ( $buffer eq "\015\012" ) {
    100          
    50          
83 1         3 $self->record_separator("\015\012");
84             }
85             elsif ( $buffer =~ /\012$/ ) {
86 1         2 $self->record_separator("\012");
87             }
88             elsif ( $buffer =~ /\015$/ ) {
89 1         23 $self->record_separator("\015");
90             }
91             }
92             }
93 7         28 $self->reset();
94             }
95             else {
96 1         83 croak("No file specified");
97             }
98             }
99              
100             sub next {
101 1     1 1 7 my $self = shift;
102 1         2 my %object;
103 1         1 my $continue = 1;
104 1 0       3 if ( $self->_filehandle->eof ) {
105 0         0 return undef;
106             }
107 0         0 my $line;
108 0   0     0 while ( !$self->_filehandle->eof && $continue ) {
109              
110             # IIF files sometimes contain embedded record_separators so if
111             # the previous line didn't have enough fields keep adding to it
112 0 0       0 if ( $self->{_need_more} ) {
113 0 0       0 if ( $self->{_need_more} > 8 ) {
114 0         0 $self->_warning("Giving up trying to get next record");
115 0         0 delete $self->{_need_more};
116 0         0 $continue = 0;
117             }
118             else {
119 0         0 $line .= $self->record_separator . $self->_getline;
120             }
121             }
122             else {
123 0         0 $line = $self->_getline;
124 0 0       0 next if ( $line =~ /^\s*$/ );
125             }
126              
127 0         0 my @data = $self->_parseline($line);
128              
129 0 0       0 if ( $self->{debug} > 1 ) {
130 0         0 warn("_getline: line($line)\n");
131 0         0 warn( "_parseline: data[" . scalar(@data) . "](@data)\n" );
132             }
133              
134 0 0       0 if ( $data[0] =~ /^!(.*)$/ ) {
    0          
135 0         0 delete( $self->{headerfields} );
136 0         0 shift(@data);
137 0         0 $self->{header} = $1;
138 0         0 $self->{headerfields} = \@data;
139             }
140             elsif ( $data[0] eq $self->{header} ) {
141 0         0 $object{header} = shift(@data);
142 0         0 my $num_hdr = scalar( @{ $self->{headerfields} } );
  0         0  
143 0         0 my $num_dat = scalar(@data);
144              
145             # have seen IIF timer data where last column (USEID) was
146             # missing but QuickBooks imports the data without error
147 0 0       0 if ( $num_dat < ( $num_hdr - 1 ) ) {
    0          
148 0         0 $self->{_need_more}++;
149             }
150             elsif ( $num_dat > $num_hdr ) {
151 0         0 $self->{_need_more} = $continue = 0;
152 1     1   7 no warnings 'uninitialized';
  1         1  
  1         717  
153 0         0 $self->_warning( "parse error: found $num_dat fields but"
154             . " expected $num_hdr." );
155 0         0 warn(
156             "error info: [header,data] "
157             . join(
158             ' ',
159             map( "$_" . '['
160             . $self->{headerfields}->[$_] . ','
161             . $data[$_] . ']',
162             0 .. ( $num_hdr - 1 ) )
163             )
164             );
165             }
166             else {
167 0         0 $self->{_need_more} = $continue = 0;
168 0         0 for ( my $i = 0 ; $i <= $#{ $self->{headerfields} } ; $i++ ) {
  0         0  
169 0 0       0 my $val = defined( $data[$i] ) ? $data[$i] : "";
170 0         0 $object{ $self->{headerfields}[$i] } = $val;
171             }
172             }
173             }
174             else {
175 0         0 $self->_warning("unable to parse line '$_'");
176             }
177             }
178              
179 0 0       0 if ($continue) {
180 0         0 return undef;
181             }
182             else {
183 0         0 return \%object;
184             }
185             }
186              
187             sub _parseline {
188 0     0   0 my $self = shift;
189 0         0 my $line = shift;
190 0   0     0 my $sep = $self->{field_separator} || "\t";
191 0         0 my @data;
192 0         0 while ( defined $line ) {
193 0 0       0 if ( $line =~ /^"(.*?)(?:[^\\]["])[$sep](.*)/s ) {
    0          
    0          
    0          
    0          
194 0 0       0 warn("parse1: data($1) line($2)\n") if ( $self->{debug} > 2 );
195 0         0 $line = $2;
196 0         0 push( @data, $1 );
197             }
198             elsif ( $line =~ /^([^$sep]+)[$sep](.*)/s ) {
199 0 0       0 warn("parse2: data($1) line($2)\n") if ( $self->{debug} > 2 );
200 0         0 $line = $2;
201 0         0 push( @data, $1 );
202             }
203             elsif ( $line =~ /^[$sep](.*)/s ) {
204 0 0       0 warn("parse3: data() line($1)\n") if ( $self->{debug} > 2 );
205 0         0 $line = $1;
206 0         0 push( @data, "" );
207             }
208             elsif ( $line =~ /^"(.*?)(?:[^\\]["])$/s ) {
209 0 0       0 warn("parse4: data($1) line()\n") if ( $self->{debug} > 2 );
210 0         0 $line = undef;
211 0         0 push( @data, $1 );
212             }
213             elsif ( $line =~ /^(.+)$/s ) {
214 0 0       0 warn("parse5: data($1) line()\n") if ( $self->{debug} > 2 );
215 0         0 $line = undef;
216 0         0 push( @data, $1 );
217             }
218             else {
219 0 0       0 warn("parse6: data() line($line)\n") if ( $self->{debug} > 2 );
220 0         0 $line = undef;
221 0         0 push( @data, "" );
222             }
223             }
224 0         0 return @data;
225             }
226              
227             sub _getline {
228 1     1   7 my $self = shift;
229 1         3 local $/ = $self->record_separator;
230 1         4 my $line = $self->_filehandle->getline;
231 0         0 $self->{_linecount}++;
232 0         0 chomp($line);
233 0         0 return $line;
234             }
235              
236             sub _warning {
237 0     0   0 my $self = shift;
238 0         0 my $message = shift;
239 0         0 carp( $message
240             . " in file '"
241             . $self->file
242             . "' line "
243             . $self->{_linecount} );
244             }
245              
246             sub reset {
247 8     8 1 12 my $self = shift;
248 8         16 $self->_filehandle->seek( 0, 0 );
249             }
250              
251             sub close {
252 1     1 1 6 my $self = shift;
253 1         3 $self->_filehandle->close;
254             }
255              
256             1;
257              
258             __END__