File Coverage

blib/lib/XBase.pm
Criterion Covered Total %
statement 409 618 66.1
branch 145 292 49.6
condition 24 63 38.1
subroutine 54 86 62.7
pod 21 41 51.2
total 653 1100 59.3


line stmt bran cond sub pod time code
1              
2 11     11   9549 use XBase::Memo;
  11         19  
  11         259  
3              
4             =head1 NAME
5              
6             XBase - Perl module for reading and writing the dbf files
7              
8             =cut
9              
10             # ############
11             package XBase;
12              
13 11     11   193 use 5.010;
  11         26  
14 11     11   33 use strict;
  11         8  
  11         178  
15 11     11   40 use XBase::Base; # will give us general methods
  11         11  
  11         176  
16              
17             # ##############
18             # General things
19              
20 11     11   28 use vars qw( $VERSION $errstr $CLEARNULLS @ISA );
  11         11  
  11         49055  
21              
22             @ISA = qw( XBase::Base );
23             $VERSION = '1.08';
24             $CLEARNULLS = 1; # Cut off white spaces from ends of char fields
25              
26             *errstr = \$XBase::Base::errstr;
27              
28              
29             # #########################################
30             # Open, read_header, init_memo_field, close
31              
32             # Open the specified file or try to append the .dbf suffix.
33             sub open {
34 19     19 1 29 my ($self) = shift;
35 19         20 my %options;
36 19 50       50 if (scalar(@_) % 2) { $options{'name'} = shift; }
  19         47  
37 19         87 $self->{'openoptions'} = { %options, @_ };
38              
39 19         20 my %locoptions;
40             @locoptions{ qw( name readonly ignorememo fh ) }
41 19         25 = @{$self->{'openoptions'}}{ qw( name readonly ignorememo fh ) };
  19         72  
42 19         53 my $filename = $locoptions{'name'};
43 19 100       53 if ($filename eq '-') {
44 1         7 return $self->SUPER::open(%locoptions);
45             }
46 18         34 for my $ext ('', '.dbf', '.DBF') {
47 30 100       369 if (-f $filename.$ext) {
48 17         36 $locoptions{'name'} = $filename.$ext;
49 17         59 $self->NullError();
50 17         125 return $self->SUPER::open(%locoptions);
51             }
52             }
53 1         2 $locoptions{'name'} = $filename;
54 1         3 return $self->SUPER::open(%locoptions); # for nice error message
55             }
56              
57             # We have to provide way to fill up the object upon open
58             sub read_header {
59 18     18 0 20 my $self = shift;
60 18         31 my $fh = $self->{'fh'};
61              
62 18         29 my $header; # read the header
63 18 50       71 $self->read($header, 32) == 32 or do {
64 0         0 __PACKAGE__->Error("Error reading header of $self->{'filename'}: $!\n");
65 0         0 return;
66             };
67              
68 18         95 @{$self}{ qw( version last_update num_rec
  18         85  
69             header_len record_len encrypted ) }
70             = unpack 'Ca3Vvv@15a1', $header; # parse the data
71              
72             ### if (0 and $self->{'encrypted'} ne "\000")
73             ### { __PACKAGE__->Error("We don't support encrypted files, sorry.\n"); return; };
74              
75 18         34 my $header_len = $self->{'header_len'};
76              
77 18         40 my ($names, $types, $lengths, $decimals) = ( [], [], [], [] );
78 18         36 my ($unpacks, $readproc, $writeproc) = ( [], [], [] );
79 18         20 my $lastoffset = 1;
80              
81 18         65 while ($self->tell() < $header_len - 1) { # read the field desc's
82 60         286 my $field_def;
83 60         98 $self->read($field_def, 1);
84 60 100       115 last if $field_def eq "\r"; # we have found the terminator
85 59         102 my $read = $self->read($field_def, 31, 1);
86 59 50       93 if ($read != 31) {
87 0         0 __PACKAGE__->Error("Error reading field description: $!\n");
88 0         0 return;
89             }
90              
91 59         159 my ($name, $type, $length, $decimal)
92             = unpack 'A11a1 @16CC', $field_def;
93 59         52 my ($rproc, $wproc);
94              
95 59 100 33     217 if ($type eq 'C') { # char
    100          
    100          
    50          
    50          
    50          
    0          
    0          
    0          
96             # fixup for char length > 256
97 21 50 33     48 if ($decimal and not $self->{'openoptions'}{'nolongchars'}) {
98 0         0 $length += 256 * $decimal; $decimal = 0;
  0         0  
99             }
100 1861     1861   1405 $rproc = sub { my $value = $_[0];
101 1861 50       2390 if ($self->{'ChopBlanks'}) {
102 1861         4965 $value =~ s/\s+$//;
103             }
104 1861         2692 return $value;
105 0 0       0 ( $value eq '' ? undef : $value );
106 21         59 };
107 16     16   25 $wproc = sub { my $value = shift;
108 16 50       77 sprintf '%-*.*s', $length, $length,
109             (defined $value ? $value : '');
110 21         49 };
111             }
112             elsif ($type eq 'L') { # logical (boolean)
113 14     14   13 $rproc = sub { my $value = shift;
114 14 100       31 if ($value =~ /^[YyTt]$/) { return 1; }
  4         7  
115 10 100       16 if ($value =~ /^[NnFf]$/) { return 0; }
  3         6  
116 7         11 undef;
117 8         18 };
118 4     4   15 $wproc = sub { my $value = shift;
119 4 100       30 sprintf '%-*.*s', $length, $length,
    100          
120             (defined $value ? ( $value ? 'T' : 'F') : '?');
121 8         23 };
122             }
123             elsif ($type =~ /^[NFD]$/) { # numbers, dates
124 60     60   47 $rproc = sub { my $value = shift;
125 60 50       216 ($value =~ /\d/) ? $value + 0 : undef;
126 20         59 };
127 17     17   15 $wproc = sub { my $value = shift;
128 17 50       21 if (defined $value) {
129 17         155 substr(sprintf('%*.*f', $length, $decimal, ($value + 0)), -$length);
130             } else {
131 0         0 ' ' x $length;
132             }
133 20         59 };
134             }
135             elsif ($type eq 'I') { # Fox integer
136 0     0   0 $rproc = sub { unpack 'V', shift; };
  0         0  
137 0     0   0 $wproc = sub { pack 'V', shift; };
  0         0  
138             }
139             elsif ($type eq 'B' and $length == 8) { # Fox double
140 0         0 if (pack("L", 1) eq pack("V", 1)) {
141 0     0   0 $rproc = sub { unpack 'd', scalar shift; };
  0         0  
142 0     0   0 $wproc = sub { scalar pack 'd', shift; };
  0         0  
143             } else {
144 0     0   0 $rproc = sub { unpack 'd', reverse scalar shift; };
145 0     0   0 $wproc = sub { reverse scalar pack 'd', shift; };
146             }
147             }
148             elsif ($type =~ /^[WMGPB]$/) { # memo fields
149 10         13 my $memo = $self->{'memo'};
150 10 100 66     34 if (not defined $memo and not $self->{'openoptions'}{'ignorememo'}) {
151 7 50       12 $memo = $self->{'memo'} = $self->init_memo_field() or return;
152             }
153 10 100 100     62 if (defined $memo and $length == 10) {
    100 66        
154 6 50       18 if (ref $memo eq 'XBase::Memo::Apollo') {
155 0     0   0 $rproc = sub { $memo->read_record(shift); };
  0         0  
156 0     0   0 $wproc = sub { $memo->write_record(shift); };
  0         0  
157             } else {
158             $rproc = sub {
159 8     8   8 my $value = shift;
160 8 50 33     43 return if not $value =~ /\d/ or $value < 0;
161 8 50       26 $memo->read_record($value - 1) if defined $memo;
162 6         24 };
163             $wproc = sub {
164 4 50 33 4   37 my $value = $memo->write_record(-1, $type, $_[0]) if defined $memo and defined $_[0] and $_[0] ne '';
      33        
165 4 50       25 sprintf '%*.*s', $length, $length,
166 6         20 (defined $value ? $value + 1: ''); };
167             }
168             }
169             elsif (defined $memo and $length == 4) {
170             $rproc = sub {
171 4     4   6 my $val = unpack('V', $_[0]) - 1;
172 4 50       7 return if $val < 0;
173 4 50       14 $memo->read_record($val) if defined $memo;
174 2         5 };
175             $wproc = sub {
176 2 50   2   11 my $value = $memo->write_record(-1, $type, shift) if defined $memo;
177 2 50       7 pack 'V', (defined $value ? $value + 1: 0); };
  2         18  
178             } else {
179 2     6   7 $rproc = sub { undef; };
  6         7  
180 2     0   4 $wproc = sub { ' ' x $length; };
  0         0  
181             }
182             }
183             elsif ($type eq 'T') { # time fields
184             # datetime is stored internally as two
185             # four-byte numbers; the first is the day under
186             # the Julian Day System (JDS) and the second is
187             # the number of milliseconds since midnight
188             $rproc = sub {
189 0     0   0 my ($day, $time) = unpack 'VV', $_[0];
190              
191              
192 0         0 my $localday = $day - 2440588;
193 0         0 my $localtime = $localday * 24 * 3600;
194 0         0 $localtime += $time / 1000;
195             ### print STDERR "day,time: ($day,$time -> $localtime)\n";
196 0         0 return $localtime;
197              
198 0         0 my $localdata = "[$localday] $localtime: @{[localtime($localtime)]}";
  0         0  
199              
200 0         0 my $usec = $time % 1000;
201 0         0 my $hour = int($time / 3600000);
202 0         0 my $min = int(($time % 3600000) / 60000);
203 0         0 my $sec = int(($time % 60000) / 1000);
204 0         0 return "$day($localdata)-$hour:$min:$sec.$usec";
205 0         0 };
206             $wproc = sub {
207 0     0   0 my $localtime = shift;
208 0         0 my $day = int($localtime / (24 * 3600)) + 2440588;
209 0         0 my $time = int(($localtime % (3600 * 24)) * 1000);
210              
211             ### print STDERR "day,time: ($localtime -> $day,$time)\n";
212              
213 0         0 return pack 'VV', $day, $time;
214             }
215 0         0 }
216             elsif ($type eq '0') { # SNa : field "_NULLFLAGS"
217 0     0   0 $rproc = $wproc = sub { '' };
  0         0  
218             } elsif ($type eq 'Y') { # Fox money
219             $rproc = sub {
220 0     0   0 my ($x, $y) = unpack 'VV', scalar shift;
221 0 0       0 if ($y & 0x80000000) {
222 0         0 - ($y ^ 0xffffffff) * (2**32 / 10**$decimal) - (($x - 1) ^ 0xffffffff) / 10**$decimal;
223             } else {
224 0         0 $y * (2**32 / 10**$decimal) + $x / 10**$decimal;
225             }
226 0         0 };
227             $wproc = sub {
228 0     0   0 my $value = shift;
229 0 0       0 if ($value < 0) {
230 0         0 pack 'VV',
231             (-$value * 10**$decimal + 1) ^ 0xffffffff,
232             (-$value * 10**$decimal / 2**32) ^ 0xffffffff;
233             } else {
234 0         0 pack 'VV',
235             ($value * 10**$decimal) % 2**32,
236             (($value * 10**$decimal) >> 32);
237             }
238 0         0 };
239             }
240              
241              
242 59         91 $name =~ s/[\000 ].*$//s;
243 59         69 $name = uc $name; # no locale yet
244 59         81 push @$names, $name;
245 59         69 push @$types, $type;
246 59         52 push @$lengths, $length;
247 59         52 push @$decimals, $decimal;
248 59         108 push @$unpacks, '@' . $lastoffset . 'a' . $length;
249 59         55 push @$readproc, $rproc;
250 59         45 push @$writeproc, $wproc;
251 59         138 $lastoffset += $length;
252             }
253              
254 18 50 33     134 if ($lastoffset > $self->{'record_len'}
255             and not defined $self->{'openoptions'}{'nolongchars'}) {
256 0         0 $self->seek_to(0);
257 0         0 $self->{'openoptions'}{'nolongchars'} = 1;
258 0         0 return $self->read_header;
259             }
260              
261 18 50 33     66 if ($lastoffset != $self->{'record_len'}
262             and not defined $self->{'openoptions'}{'ignorebadheader'}) {
263 0         0 __PACKAGE__->Error("Missmatch in header of $self->{'filename'}: record_len $self->{'record_len'} but offset $lastoffset\n");
264 0         0 return;
265             }
266 18 100       43 if ($self->{'openoptions'}{'recompute_lastrecno'}) {
267             $self->{num_rec} = int(((-s $self->{'fh'}) - $self->{header_len})
268 1         9 / $self->{record_len});
269             }
270              
271 18         26 my $hashnames = {}; # create name-to-num_of_field hash
272 18         44 @{$hashnames}{ reverse @$names } = reverse ( 0 .. $#$names );
  18         49  
273              
274             # now it's the time to store the values to the object
275 18         40 @{$self}{ qw( field_names field_types field_lengths field_decimals
  18         140  
276             hash_names last_field field_unpacks
277             field_rproc field_wproc ChopBlanks) } =
278             ( $names, $types, $lengths, $decimals,
279             $hashnames, $#$names, $unpacks,
280             $readproc, $writeproc, $CLEARNULLS );
281              
282              
283 18         119 1; # return true since everything went fine
284             }
285              
286             # When there is a memo field in dbf, try to open the memo file
287             sub init_memo_field {
288 7     7 0 9 my $self = shift;
289 7 50       15 return $self->{'memo'} if defined $self->{'memo'};
290 7         36 require XBase::Memo;
291             my %options = ( 'dbf_version' => $self->{'version'},
292 7         25 'memosep' => $self->{'openoptions'}{'memosep'} );
293            
294 7 50       19 if (defined $self->{'openoptions'}{'memofile'}) {
295 0         0 return XBase::Memo->new($self->{'openoptions'}{'memofile'}, %options);
296             }
297            
298 7         14 for (qw( dbt DBT fpt FPT smt SMT dbt )) {
299 10         7 my $memo;
300 10         49 my $memoname = $self->{'filename'};
301 10 100 33     121 ($memoname =~ s/\.dbf$/.$_/i or $memoname =~ s/(\.dbf)?$/.$_/i)
      66        
302             and $memo = XBase::Memo->new($memoname, %options)
303             and return $memo;
304             }
305 0         0 return;
306             }
307              
308             # Close the file (and memo)
309             sub close {
310 5     5 1 203 my $self = shift;
311 5 100       14 if (defined $self->{'memo'}) {
312 2         18 $self->{'memo'}->close(); delete $self->{'memo'};
  2         3  
313             }
314 5         42 $self->SUPER::close();
315             }
316              
317             # ###############
318             # Little decoding
319 5     5 0 44 sub version { shift->{'version'}; }
320 3270     3270 1 8992 sub last_record { shift->{'num_rec'} - 1; }
321 105     105 1 190 sub last_field { shift->{'last_field'}; }
322              
323             # List of field names, types, lengths and decimals
324 9     9 1 19 sub field_names { @{shift->{'field_names'}}; }
  9         27  
325 14     14 1 11 sub field_types { @{shift->{'field_types'}}; }
  14         48  
326 2     2 1 2 sub field_lengths { @{shift->{'field_lengths'}}; }
  2         5  
327 0     0 1 0 sub field_decimals { @{shift->{'field_decimals'}}; }
  0         0  
328              
329             # Return field number for field name
330             sub field_name_to_num {
331 52     52 0 47 my ($self, $name) = @_; $self->{'hash_names'}{uc $name};
  52         162  
332             }
333             sub field_type {
334 14     14 1 18 my ($self, $name) = @_;
335 14 50       25 defined (my $num = $self->field_name_to_num($name)) or return;
336 14         42 ($self->field_types)[$num];
337             }
338             sub field_length {
339 2     2 1 3 my ($self, $name) = @_;
340 2 50       3 defined (my $num = $self->field_name_to_num($name)) or return;
341 2         4 ($self->field_lengths)[$num];
342             }
343             sub field_decimal {
344 0     0 1 0 my ($self, $name) = @_;
345 0 0       0 defined (my $num = $self->field_name_to_num($name)) or return;
346 0         0 ($self->field_decimals)[$num];
347             }
348              
349              
350             # #############################
351             # Header, field and record info
352              
353             # Returns (not prints!) the info about the header of the object
354             *header_info = \&get_header_info;
355             sub get_header_info {
356 2     2 0 187 my $self = shift;
357 2         5 my $hexversion = sprintf '0x%02x', $self->version;
358 2         6 my $longversion = $self->get_version_info()->{'string'};
359 2         6 my $printdate = $self->get_last_change;
360 2         4 my $numfields = $self->last_field() + 1;
361 2         15 my $result = sprintf <<"EOF";
362             Filename: $self->{'filename'}
363             Version: $hexversion ($longversion)
364             Num of records: $self->{'num_rec'}
365             Header length: $self->{'header_len'}
366             Record length: $self->{'record_len'}
367             Last change: $printdate
368             Num fields: $numfields
369             Field info:
370             Num Name Type Len Decimal
371             EOF
372 2         4 return join '', $result, map { $self->get_field_info($_) }
  10         15  
373             (0 .. $self->last_field);
374             }
375             # Return info about field in dbf file
376             sub get_field_info {
377 10     10 0 9 my ($self, $num) = @_;
378             sprintf "%d.\t%-16.16s%-8.8s%-8.8s%s\n", $num + 1,
379 10         10 map { $self->{$_}[$num] }
  40         62  
380             qw( field_names field_types field_lengths field_decimals );
381             }
382             # Return last_change item as printable string
383             sub get_last_change {
384 2     2 0 3 my $self = shift;
385 2         4 my $date = $self;
386 2 50       4 if (ref $self) { $date = $self->{'last_update'}; }
  2         3  
387 2         6 my ($year, $mon, $day) = unpack 'C3', $date;
388 2 100       5 $year += ($year >= 70) ? 1900 : 2000;
389 2         6 return "$year/$mon/$day";
390             }
391             # Return text description of the version value
392             sub get_version_info {
393 2     2 0 2 my $version = shift;
394 2 50       6 $version = $version->version() if ref $version;
395 2         8 my $result = {};
396 2         4 $result->{'vbits'} = $version & 0x07;
397 2 50 33     19 if ($version == 0x30 or $version == 0xf5) {
    50          
    50          
398 0         0 $result->{'vbits'} = 5; $result->{'foxpro'} = 1;
  0         0  
399             } elsif ($version & 0x08) {
400 0         0 $result->{'vbits'} = 4; $result->{'memo'} = 1;
  0         0  
401             } elsif ($version & 0x80) {
402 2         4 $result->{'dbt'} = 1;
403             }
404              
405 2         7 my $string = "ver. $result->{'vbits'}";
406 2 50       5 if (exists $result->{'foxpro'}) {
407 0         0 $string .= " (FoxPro)";
408             }
409 2 50       10 if (exists $result->{'memo'}) {
    50          
410 0         0 $string .= " with memo file";
411             } elsif (exists $result->{'dbt'}) {
412 2         5 $string .= " with DBT file";
413             }
414 2         5 $result->{'string'} = $string;
415              
416 2         3 $result;
417             }
418              
419              
420             # Print the records as colon separated fields
421             sub dump_records {
422 0     0 0 0 my $self = shift;
423 0         0 my %options = ( 'rs' => "\n", 'fs' => ':', 'undef' => '' );
424 0         0 my %inoptions = @_;
425 0         0 for my $key (keys %inoptions) {
426 0         0 my $value = $inoptions{$key};
427 0         0 my $outkey = lc $key;
428 0         0 $outkey =~ s/[^a-z]//g;
429 0         0 $options{$outkey} = $value;
430             }
431             my ($rs, $fs, $undef, $fields, $table)
432 0         0 = @options{ qw( rs fs undef fields table ) };
433 0 0       0 if (defined $table) {
434 0         0 eval 'use Data::ShowTable';
435 0 0       0 if ($@) {
436 0         0 warn "You requested table output format but the module Data::ShowTable doesn't\nseem to be installed correctly. Falling back to standard\n";
437 0         0 $table = undef;
438             } else {
439 0         0 delete $options{'rs'};
440 0         0 delete $options{'fs'};
441             }
442             }
443              
444 0         0 my @fields = ();
445 0         0 my @unknown_fields;
446 0 0       0 if (defined $fields) {
447 0 0       0 if (ref $fields eq 'ARRAY') {
448 0         0 @fields = @$fields;
449             } else {
450 0         0 @fields = split /\s*,\s*/, $fields;
451 0         0 my $i = 0;
452 0         0 while ($i < @fields) {
453 0 0       0 if (defined $self->field_name_to_num($fields[$i])) {
    0          
454 0         0 $i++;
455             } elsif ($fields[$i] =~ /^(.*)-(.*)/) {
456 0         0 local $^W = 0;
457 0         0 my @allfields = $self->field_names;
458 0         0 my ($start, $end) = ($1, $2);
459 0 0       0 if ($start eq '') {
460 0         0 $start = $allfields[0];
461             }
462 0 0       0 if ($end eq '') {
463 0         0 $end = $allfields[$#allfields];
464             }
465 0         0 my $start_num = $self->field_name_to_num($start);
466 0         0 my $end_num = $self->field_name_to_num($end);
467 0 0 0     0 if ($start ne '' and not defined $start_num) {
468 0         0 push @unknown_fields, $start;
469             }
470 0 0 0     0 if ($end ne '' and not defined $end_num) {
471 0         0 push @unknown_fields, $end;
472             }
473 0 0 0     0 unless (defined $start and defined $end) {
474 0         0 $start = 0; $end = -1;
  0         0  
475             }
476            
477 0         0 splice @fields, $i, 1, @allfields[$start_num .. $end_num];
478             } else {
479 0         0 push @unknown_fields, $fields[$i];
480 0         0 $i++;
481             }
482             }
483             }
484             }
485              
486 0 0       0 if (@unknown_fields) {
487 0         0 $self->Error("There have been unknown fields `@unknown_fields' specified.\n");
488 0         0 return 0;
489             }
490 0         0 my $cursor = $self->prepare_select(@fields);
491 0         0 my @record;
492 0 0       0 if (defined $table) {
493 0         0 local $^W = 0;
494             &ShowBoxTable( $cursor->names(), [], [],
495             sub {
496 0 0   0   0 if ($_[0]) { $cursor->rewind(); }
  0         0  
497 0         0 else { $cursor->fetch() }
498 0         0 });
499             } else {
500 0         0 while (@record = $cursor->fetch) {
501 0 0       0 print join($fs, map { defined $_ ? $_ : $undef } @record), $rs;
  0         0  
502             }
503             }
504 0         0 1;
505             }
506              
507              
508             # ###################
509             # Reading the records
510              
511             # Returns fields of the specified record; parameters and number of the
512             # record (starting from 0) and optionally names of the required
513             # fields. If no names are specified, all fields are returned. The
514             # first value in the returned list if always 1/0 deleted flag. Returns
515             # empty list on error.
516              
517             sub get_record {
518 117     117 1 486 my ($self, $num) = (shift, shift);
519 117         197 $self->NullError();
520 117         157 $self->get_record_nf( $num, map { $self->field_name_to_num($_); } @_);
  16         17  
521             }
522             *get_record_as_hash = \&get_record_hash;
523             sub get_record_hash {
524 1     1 0 18 my ($self, $num) = @_;
525 1 50       2 my @list = $self->get_record($num) or return;
526 1         2 my $hash = {};
527 1         3 @{$hash}{ '_DELETED', $self->field_names() } = @list;
  1         3  
528 1 50       8 return %$hash if wantarray;
529 0         0 $hash;
530             }
531             sub get_record_nf {
532 1545     1545 1 1451 my ($self, $num, @fieldnums) = @_;
533 1545 100       2317 my $data = $self->read_record($num) or return;
534 1543 100       2202 if (not @fieldnums) {
535 100         137 @fieldnums = ( 0 .. $self->last_field );
536             }
537             my $unpack = join ' ', '@0a1',
538 1543         1569 map { my $e;
  1953         1188  
539 1953 50       2763 defined $_ and $e = $self->{'field_unpacks'}[$_];
540 1953 50       3849 defined $e ? $e : '@0a0'; } @fieldnums;
541            
542 1543         1454 my $rproc = $self->{'field_rproc'};
543 1543 50 33 0   1567 my @fns = (\&_read_deleted, map { (defined $_ and defined $rproc->[$_]) ? $rproc->[$_] : sub { undef; }; } @fieldnums);
  1953         6051  
  0         0  
544              
545 1543         3696 my @out = unpack $unpack, $data;
546             ### if ($self->{'encrypted'} ne "\000") {
547             ### for my $data (@out) {
548             ### for (my $i = 0; $i < length($data); $i++) {
549             ### ## my $num = unpack 'C', substr($data, $i, 1);
550             ### ## substr($data, $i, 1) = pack 'C', (($num >> 3) | ($num << 5) ^ 020);
551             ### my $num = unpack 'C', substr($data, $i, 1);
552             ### substr($data, $i, 1) = pack 'C', (($num >> 1) | ($num << 7) ^ 052);
553             ### }
554             ### }
555             ### }
556              
557 1543         1802 for (@out) { $_ = &{ shift @fns }($_); }
  3496         2077  
  3496         3607  
558              
559 1543         2719 @out;
560             }
561              
562             # Processing on read
563             sub _read_deleted {
564 1543     1543   1148 my $value = shift;
565 1543 100       2686 if ($value eq '*') { return 1; } elsif ($value eq ' ') { return 0; }
  3 50       5  
  1540         1957  
566 0         0 undef;
567             }
568              
569             sub get_all_records {
570 1     1 0 48 my $self = shift;
571 1         4 my $cursor = $self->prepare_select(@_);
572              
573 1         2 my $result = [];
574 1         1 my @record;
575 1         3 while (@record = $cursor->fetch())
576 42         103 { push @$result, [ @record ]; }
577 1         11 $result;
578             }
579              
580             # #############
581             # Write records
582              
583             # Write record, values of the fields are in the argument list.
584             # Record is always undeleted
585             sub set_record {
586 14     14 1 1454 my ($self, $num, @data) = @_;
587 14         34 $self->NullError();
588 14         14 my $wproc = $self->{'field_wproc'};
589              
590 14 100       27 if (defined $self->{'attached_index_columns'}) {
591 1         2 my @nfs = keys %{$self->{'attached_index_columns'}};
  1         4  
592 1         3 my ($del, @old_data) = $self->get_record_nf($num, @nfs);
593              
594 1         2 local $^W = 0;
595 1         3 for my $nf (@nfs) {
596 2 50       5 if ($old_data[$nf] ne $data[$nf]) {
597 2         2 for my $idx (@{$self->{'attached_index_columns'}{$nf}}) {
  2         4  
598 5         18 $idx->delete($old_data[$nf], $num + 1);
599 5         16 $idx->insert($data[$nf], $num + 1);
600             }
601             }
602             }
603             }
604              
605 14         34 for (my $i = 0; $i <= $#$wproc; $i++) {
606 43         38 $data[$i] = &{ $wproc->[$i] }($data[$i]);
  43         104  
607             }
608 14         31 unshift @data, ' ';
609              
610             ### if ($self->{'encrypted'} ne "\000") {
611             ### for my $data (@data) {
612             ### for (my $i = 0; $i < length($data); $i++) {
613             ### my $num = unpack 'C', substr($data, $i, 1);
614             ### substr($data, $i, 1) = pack 'C', (($num << 3) | ($num >> 5) ^ 020);
615             ### }
616             ### }
617             ### }
618              
619 14         25 $self->write_record($num, @data);
620             }
621              
622             # Write record, fields are specified as hash, unspecified are set to
623             # undef/empty
624             sub set_record_hash {
625 0     0 1 0 my ($self, $num, %data) = @_;
626 0         0 $self->NullError();
627 0         0 $self->set_record($num, map { $data{$_} } $self->field_names );
  0         0  
628             }
629              
630             # Write record, fields specified as hash, unspecified will be
631             # unchanged
632             sub update_record_hash {
633 0     0 1 0 my ($self, $num) = ( shift, shift );
634 0         0 $self->NullError();
635              
636 0         0 my %olddata = $self->get_record_hash($num);
637 0 0       0 return unless %olddata;
638 0         0 $self->set_record_hash($num, %olddata, @_);
639             }
640              
641             # Actually write the data (calling XBase::Base::write_record) and keep
642             # the overall structure of the file correct;
643             sub write_record {
644 16     16 1 19 my ($self, $num) = (shift, shift);
645 16 50       46 my $ret = $self->SUPER::write_record($num, @_) or return;
646              
647 16 100       28 if ($num > $self->last_record) {
648 13         26 $self->SUPER::write_record($num + 1, "\x1a"); # add EOF
649 13 50       25 $self->update_last_record($num) or return;
650             }
651 16 50       26 $self->update_last_change or return;
652 16         34 $ret;
653             }
654              
655             # Delete and undelete record
656             sub delete_record {
657 1     1 0 29 my ($self, $num) = @_;
658 1         3 $self->NullError();
659 1         3 $self->write_record($num, "*");
660             }
661             sub undelete_record {
662 1     1 0 24 my ($self, $num) = @_;
663 1         2 $self->NullError();
664 1         6 $self->write_record($num, " ");
665             }
666              
667             # Update the last change date
668             sub update_last_change {
669 18     18 0 18 my $self = shift;
670 18 100       42 return 1 if defined $self->{'updated_today'};
671 7 50       412 my ($y, $m, $d) = (localtime)[5, 4, 3]; $m++; $y -= 100 if $y >= 100;
  7         13  
  7         23  
672 7 50       34 $self->write_to(1, pack "C3", ($y, $m, $d)) or return;
673 7         27 $self->{'updated_today'} = 1;
674             }
675             # Update the number of records
676             sub update_last_record {
677 13     13 0 15 my ($self, $last) = @_;
678 13         10 $last++;
679 13         38 $self->write_to(4, pack "V", $last);
680 13         31 $self->{'num_rec'} = $last;
681             }
682              
683             # Creating new dbf file
684             sub create {
685 2     2 1 748 XBase->NullError();
686 2         3 my $class = shift;
687 2         8 my %options = @_;
688 2 50       7 if (ref $class) {
689 0         0 %options = ( %$class, %options ); $class = ref $class;
  0         0  
690             }
691              
692 2         3 my $version = $options{'version'};
693 2 50       6 if (not defined $version) {
694 2 50 33     9 if (defined $options{'memofile'}
695             and $options{'memofile'} =~ /\.fpt$/i) {
696 0         0 $version = 0xf5;
697             } else {
698 2         4 $version = 3;
699             }
700             }
701              
702 2         2 my $key;
703 2         3 for $key ( qw( field_names field_types field_lengths field_decimals ) ) {
704 8 50       16 if (not defined $options{$key}) {
705 0         0 __PACKAGE__->Error("Tag $key must be specified when creating new table\n");
706 0         0 return;
707             }
708             }
709              
710 2         4 my $needmemo = 0;
711              
712 2         2 my $fieldspack = '';
713 2         2 my $record_len = 1;
714 2         4 my $i;
715 2         2 for $i (0 .. $#{$options{'field_names'}}) {
  2         7  
716 7         11 my $name = uc $options{'field_names'}[$i];
717 7 50       10 $name = "FIELD$i" unless defined $name;
718 7         6 $name .= "\0";
719 7         6 my $type = $options{'field_types'}[$i];
720 7 50       11 $type = 'C' unless defined $type;
721              
722 7         10 my $length = $options{'field_lengths'}[$i];
723 7         5 my $decimal = $options{'field_decimals'}[$i];
724              
725 7 50       9 if (not defined $length) { # defaults
726 0 0       0 if ($type eq 'C') { $length = 64; }
  0 0       0  
    0          
727 0         0 elsif ($type =~ /^[TD]$/) { $length = 8; }
728 0         0 elsif ($type =~ /^[NF]$/) { $length = 8; }
729             }
730             # force correct lengths
731 7 100       26 if ($type =~ /^[MBGP]$/) { $length = 10; $decimal = 0; }
  1 100       2  
  1 50       1  
732 1         2 elsif ($type eq 'L') { $length = 1; $decimal = 0; }
  1         1  
733 0         0 elsif ($type eq 'Y') { $length = 8; $decimal = 4; }
  0         0  
734              
735 7 100       9 if (not defined $decimal) {
736 3         4 $decimal = 0;
737             }
738            
739 7         5 $record_len += $length;
740 7         5 my $offset = $record_len;
741 7 100       10 if ($type eq 'C') {
742 2         5 $decimal = int($length / 256);
743 2         2 $length %= 256;
744             }
745 7         26 $fieldspack .= pack 'a11a1VCCvCvCa7C', $name, $type, $offset,
746             $length, $decimal, 0, 0, 0, 0, '', 0;
747 7 100       13 if ($type eq 'M') {
748 1         1 $needmemo = 1;
749 1 50       3 if ($version != 0x30) {
750 1         1 $version |= 0x80;
751             }
752             }
753             }
754 2         4 $fieldspack .= "\x0d";
755              
756             {
757 2         2 local $^W = 0;
  2         10  
758 2         5 $options{'codepage'} += 0;
759             }
760             my $header = pack 'C CCC V vvv CC a12 CC v',
761             $version,
762             0, 0, 0,
763             0,
764             (32 + length $fieldspack), $record_len, 0,
765             0, 0,
766             '',
767 2         8 0, $options{'codepage'},
768             0;
769 2         10 $header .= $fieldspack;
770 2         2 $header .= "\x1a";
771              
772 2         9 my $tmp = $class->new();
773 2         4 my $basename = $options{'name'};
774 2         5 $basename =~ s/\.dbf$//i;
775 2         4 my $newname = $options{'name'};
776 2 100 66     19 if (defined $newname and not $newname =~ /\.dbf$/) {
777 1         2 $newname .= '.dbf';
778             }
779 2 50       10 $tmp->create_file($newname, 0700) or return;
780 2 50       11 $tmp->write_to(0, $header) or return;
781 2         8 $tmp->update_last_change();
782 2         9 $tmp->close();
783              
784 2 100       6 if ($needmemo) {
785 1         5 require XBase::Memo;
786 1         2 my $dbtname = $options{'memofile'};
787 1 50       2 if (not defined $dbtname) {
788 1         2 $dbtname = $options{'name'};
789 1 50 33     6 if ($version == 0x30 or $version == 0xf5) {
790 0 0       0 $dbtname =~ s/\.DBF$/.FPT/ or $dbtname =~ s/(\.dbf)?$/.fpt/;
791             } else {
792 1 50       9 $dbtname =~ s/\.DBF$/.DBT/ or $dbtname =~ s/(\.dbf)?$/.dbt/;
793             }
794             }
795 1         5 my $dbttmp = XBase::Memo->new();
796 1         2 my $memoversion = ($version & 15);
797 1 50       2 $memoversion = 5 if $version == 0x30;
798 1 50       3 $dbttmp->create('name' => $dbtname,
799             'version' => $memoversion,
800             'dbf_filename' => $basename) or return;
801             }
802              
803 2         14 return $class->new($options{'name'});
804             }
805             # Drop the table
806             sub drop {
807 1     1 1 202 my $self = shift;
808 1         1 my $filename = $self;
809 1 50       4 if (ref $self) {
810 1 50       2 if (defined $self->{'memo'}) {
811 1         11 $self->{'memo'}->drop();
812 1         2 delete $self->{'memo'};
813             }
814 1         4 return $self->SUPER::drop();
815             }
816 0         0 XBase::Base::drop($filename);
817             }
818             # Lock and unlock
819             sub locksh {
820 0     0 0 0 my $self = shift;
821 0 0       0 my $ret = $self->SUPER::locksh or return;
822 0 0       0 if (defined $self->{'memo'}) {
823 0 0       0 unless ($self->{'memo'}->locksh()) {
824 0         0 $self->SUPER::unlock;
825 0         0 return;
826             }
827             }
828 0         0 $ret;
829             }
830             sub lockex {
831 0     0 0 0 my $self = shift;
832 0 0       0 my $ret = $self->SUPER::lockex or return;
833 0 0       0 if (defined $self->{'memo'}) {
834 0 0       0 unless ($self->{'memo'}->lockex()) {
835 0         0 $self->SUPER::unlock;
836 0         0 return;
837             }
838             }
839 0         0 $ret;
840             }
841             sub unlock {
842 0     0 0 0 my $self = shift;
843 0 0       0 $self->{'memo'}->unlock() if defined $self->{'memo'};
844 0         0 $self->SUPER::unlock;
845             }
846              
847             #
848             # Attaching index file
849             #
850              
851             sub attach_index {
852 1     1 0 28 my ($self, $indexfile) = @_;
853 1         512 require XBase::Index;
854              
855 1 50       4 my $index = $self->XBase::Index::new($indexfile) or do {
856 0         0 print STDERR XBase->errstr, "\n";
857 0         0 $self->Error(XBase->errstr);
858 0         0 return;
859             };
860 1 50       3 print "Got index $index\n" if $XBase::Index::VERBOSE;
861 1         4 my @tags = $index->tags;
862 1         1 my @indexes;
863 1 50       3 if (@tags) {
864 1         2 for my $tag (@tags) {
865             my $index = $self->XBase::Index::new($indexfile,
866             'tag' => $tag)
867 5 50       13 or do {
868 0         0 print STDERR XBase->errstr, "\n";
869 0         0 $self->Error(XBase->errstr);
870 0         0 return;
871             };
872 5         9 push @indexes, $index;
873             }
874             } else {
875 0         0 @indexes = ( $index );
876             }
877 1         3 for my $idx (@indexes) {
878 5         4 my $key = $idx->{'key_string'};
879 5         6 my $num = $self->field_name_to_num($key);
880              
881 5 50       8 print "Got key string $key -> $num\n" if $XBase::Index::VERBOSE;
882            
883             $self->{'attached_index'} = []
884 5 100       8 unless defined $self->{'attached_index'};
885 5         3 push @{$self->{'attached_index'}}, $idx;
  5         5  
886 5         5 push @{$self->{'attached_index_columns'}{$num}}, $idx;
  5         8  
887             }
888 1         4 1;
889             }
890              
891             #
892             # Cursory select
893             #
894              
895             sub prepare_select {
896 2     2 1 27 my $self = shift;
897 2         6 my $fieldnames = [ @_ ];
898 2 100       27 if (not @_) { $fieldnames = [ $self->field_names ] };
  1         3  
899 2         6 my $fieldnums = [ map { $self->field_name_to_num($_); } @$fieldnames ];
  3         8  
900 2         8 return bless [ $self, undef, $fieldnums, $fieldnames ], 'XBase::Cursor';
901             # object, recno, field numbers, field names
902             }
903              
904             sub prepare_select_nf {
905 0     0 0 0 my $self = shift;
906 0         0 my @fieldnames = $self->field_names;
907 0 0       0 if (@_) { @fieldnames = @fieldnames[ @_ ] }
  0         0  
908 0         0 return $self->prepare_select(@fieldnames);
909             }
910              
911             sub prepare_select_with_index {
912 8     8 1 259 my ($self, $file) = ( shift, shift );
913 8         14 my @tagopts = ();
914 8 100       67 if (ref $file eq 'ARRAY') { ### this is suboptimal
915             ### interface but should suffice for the moment
916 4         10 @tagopts = ('tag' => $file->[1]);
917 4 50       10 if (defined $file->[2]) {
918 0         0 push @tagopts, ('type' => $file->[2]);
919             }
920 4         6 $file = $file->[0];
921             }
922 8         14 my $fieldnames = [ @_ ];
923 8 100       18 if (not @_) { $fieldnames = [ $self->field_names ] };
  6         18  
924 8         16 my $fieldnums = [ map { $self->field_name_to_num($_); } @$fieldnames ];
  12         25  
925 8         1870 require XBase::Index;
926             my $index = new XBase::Index $file, 'dbf' => $self, @tagopts or
927 8 50       51 do { $self->Error(XBase->errstr); return; };
  0         0  
  0         0  
928             $index->prepare_select or
929 8 50       30 do { $self->Error($index->errstr); return; };
  0         0  
  0         0  
930 8         39 return bless [ $self, undef, $fieldnums, $fieldnames, $index ],
931             'XBase::IndexCursor';
932             # object, recno, field numbers, field names, index file
933             }
934              
935             package XBase::Cursor;
936 11     11   66 use vars qw( @ISA );
  11         8  
  11         2589  
937             @ISA = qw( XBase::Base );
938              
939             sub fetch {
940 112     112   234 my $self = shift;
941 112         112 my ($xbase, $recno, $fieldnums, $fieldnames) = @$self;
942 112 100       115 if (defined $recno) { $recno++; }
  110         74  
943 2         3 else { $recno = 0; }
944 112         122 my $lastrec = $xbase->last_record;
945 112         144 while ($recno <= $lastrec) {
946 110         128 my ($del, @result) = $xbase->get_record_nf($recno, @$fieldnums);
947 110 50 33     310 if (@result and not $del) {
948 110         81 $self->[1] = $recno;
949 110         229 return @result;
950             }
951 0         0 $recno++;
952             }
953 2         5 return;
954             }
955             sub fetch_hashref {
956 0     0   0 my $self = shift;
957 0         0 my @data = $self->fetch;
958 0         0 my $hashref = {};
959 0 0       0 if (@data) {
960 0         0 @{$hashref}{ @{$self->[3]} } = @data;
  0         0  
  0         0  
961 0         0 return $hashref;
962             }
963 0         0 return;
964             }
965             sub last_fetched {
966 0     0   0 shift->[1];
967             }
968             sub table {
969 0     0   0 shift->[0];
970             }
971             sub names {
972 0     0   0 shift->[3];
973             }
974             sub rewind {
975 0     0   0 shift->[1] = undef; '0E0';
  0         0  
976             }
977              
978             sub attach_index {
979 0     0   0 my $self = shift;
980 0         0 require XBase::Index;
981              
982             }
983              
984             package XBase::IndexCursor;
985 11     11   44 use vars qw( @ISA );
  11         10  
  11         1518  
986             @ISA = qw( XBase::Cursor );
987              
988             sub find_eq {
989 42     42   1591 my $self = shift;
990 42         95 $self->[4]->prepare_select_eq(shift);
991             }
992             sub fetch {
993 1360     1360   5370 my $self = shift;
994 1360         1287 my ($xbase, $recno, $fieldnums, $fieldnames, $index) = @$self;
995 1360         840 my ($key, $val);
996 1360         2228 while (($key, $val) = $index->fetch) {
997 1317         2330 my ($del, @result) = $xbase->get_record_nf($val - 1, @$fieldnums);
998 1317 50       1794 unless ($del) {
999 1317         1105 $self->[1] = $val;
1000 1317         2516 return @result;
1001             }
1002             }
1003 43         57 return;
1004             }
1005              
1006             # Indexed number the records starting from one, not zero.
1007             sub last_fetched {
1008 0     0     shift->[1] - 1;
1009             }
1010              
1011             1;
1012              
1013             __END__