File Coverage

blib/lib/XBase.pm
Criterion Covered Total %
statement 409 618 66.1
branch 145 292 49.6
condition 23 60 38.3
subroutine 54 86 62.7
pod 21 41 51.2
total 652 1097 59.4


line stmt bran cond sub pod time code
1              
2 11     11   12615 use XBase::Memo;
  11         24  
  11         298  
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   216 use 5.010;
  11         27  
14 11     11   36 use strict;
  11         11  
  11         214  
15 11     11   50 use XBase::Base; # will give us general methods
  11         12  
  11         201  
16              
17             # ##############
18             # General things
19              
20 11     11   34 use vars qw( $VERSION $errstr $CLEARNULLS @ISA );
  11         14  
  11         55034  
21              
22             @ISA = qw( XBase::Base );
23             $VERSION = '1.06';
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 31 my ($self) = shift;
35 19         19 my %options;
36 19 50       59 if (scalar(@_) % 2) { $options{'name'} = shift; }
  19         53  
37 19         105 $self->{'openoptions'} = { %options, @_ };
38              
39 19         23 my %locoptions;
40             @locoptions{ qw( name readonly ignorememo fh ) }
41 19         26 = @{$self->{'openoptions'}}{ qw( name readonly ignorememo fh ) };
  19         76  
42 19         30 my $filename = $locoptions{'name'};
43 19 100       57 if ($filename eq '-') {
44 1         7 return $self->SUPER::open(%locoptions);
45             }
46 18         38 for my $ext ('', '.dbf', '.DBF') {
47 30 100       405 if (-f $filename.$ext) {
48 17         42 $locoptions{'name'} = $filename.$ext;
49 17         66 $self->NullError();
50 17         122 return $self->SUPER::open(%locoptions);
51             }
52             }
53 1         2 $locoptions{'name'} = $filename;
54 1         4 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         18 my $header; # read the header
63 18 50       87 $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         108 @{$self}{ qw( version last_update num_rec
  18         88  
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         45 my ($names, $types, $lengths, $decimals) = ( [], [], [], [] );
78 18         33 my ($unpacks, $readproc, $writeproc) = ( [], [], [] );
79 18         24 my $lastoffset = 1;
80              
81 18         72 while ($self->tell() < $header_len - 1) { # read the field desc's
82 60         257 my $field_def;
83 60         95 $self->read($field_def, 1);
84 60 100       133 last if $field_def eq "\r"; # we have found the terminator
85 59         98 my $read = $self->read($field_def, 31, 1);
86 59 50       100 if ($read != 31) {
87 0         0 __PACKAGE__->Error("Error reading field description: $!\n");
88 0         0 return;
89             }
90              
91 59         156 my ($name, $type, $length, $decimal)
92             = unpack 'A11a1 @16CC', $field_def;
93 59         48 my ($rproc, $wproc);
94              
95 59 100       231 if ($type eq 'C') { # char
    100          
    100          
    50          
    50          
    50          
    0          
    0          
    0          
96             # fixup for char length > 256
97 21 50 33     50 if ($decimal and not $self->{'openoptions'}{'nolongchars'}) {
98 0         0 $length += 256 * $decimal; $decimal = 0;
  0         0  
99             }
100 1861     1861   1396 $rproc = sub { my $value = $_[0];
101 1861 50       2394 if ($self->{'ChopBlanks'}) {
102 1861         4770 $value =~ s/\s+$//;
103             }
104 1861         2710 return $value;
105 0 0       0 ( $value eq '' ? undef : $value );
106 21         59 };
107 16     16   21 $wproc = sub { my $value = shift;
108 16 50       68 sprintf '%-*.*s', $length, $length,
109             (defined $value ? $value : '');
110 21         56 };
111             }
112             elsif ($type eq 'L') { # logical (boolean)
113 14     14   11 $rproc = sub { my $value = shift;
114 14 100       35 if ($value =~ /^[YyTt]$/) { return 1; }
  4         8  
115 10 100       26 if ($value =~ /^[NnFf]$/) { return 0; }
  3         6  
116 7         9 undef;
117 8         25 };
118 4     4   4 $wproc = sub { my $value = shift;
119 4 100       21 sprintf '%-*.*s', $length, $length,
    100          
120             (defined $value ? ( $value ? 'T' : 'F') : '?');
121 8         22 };
122             }
123             elsif ($type =~ /^[NFD]$/) { # numbers, dates
124 60     60   44 $rproc = sub { my $value = shift;
125 60 50       231 ($value =~ /\d/) ? $value + 0 : undef;
126 20         63 };
127 17     17   12 $wproc = sub { my $value = shift;
128 17 50       22 if (defined $value) {
129 17         157 substr(sprintf('%*.*f', $length, $decimal, ($value + 0)), -$length);
130             } else {
131 0         0 ' ' x $length;
132             }
133 20         73 };
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') { # 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 =~ /^[WMGP]$/) { # memo fields
149 10         16 my $memo = $self->{'memo'};
150 10 100 66     40 if (not defined $memo and not $self->{'openoptions'}{'ignorememo'}) {
151 7 50       18 $memo = $self->{'memo'} = $self->init_memo_field() or return;
152             }
153 10 100 100     64 if (defined $memo and $length == 10) {
    100 66        
154 6 50       21 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       32 $memo->read_record($value - 1) if defined $memo;
162 6         24 };
163             $wproc = sub {
164 4 50 33 4   38 my $value = $memo->write_record(-1, $type, $_[0]) if defined $memo and defined $_[0] and $_[0] ne '';
      33        
165 4 50       27 sprintf '%*.*s', $length, $length,
166 6         24 (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       15 $memo->read_record($val) if defined $memo;
174 2         6 };
175             $wproc = sub {
176 2 50   2   15 my $value = $memo->write_record(-1, $type, shift) if defined $memo;
177 2 50       11 pack 'V', (defined $value ? $value + 1: 0); };
  2         13  
178             } else {
179 2     6   7 $rproc = sub { undef; };
  6         7  
180 2     0   9 $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         103 $name =~ s/[\000 ].*$//s;
243 59         72 $name = uc $name; # no locale yet
244 59         71 push @$names, $name;
245 59         78 push @$types, $type;
246 59         57 push @$lengths, $length;
247 59         58 push @$decimals, $decimal;
248 59         113 push @$unpacks, '@' . $lastoffset . 'a' . $length;
249 59         52 push @$readproc, $rproc;
250 59         50 push @$writeproc, $wproc;
251 59         142 $lastoffset += $length;
252             }
253              
254 18 50 33     137 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     82 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       47 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         34 my $hashnames = {}; # create name-to-num_of_field hash
272 18         49 @{$hashnames}{ reverse @$names } = reverse ( 0 .. $#$names );
  18         57  
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         102  
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         122 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 12 my $self = shift;
289 7 50       17 return $self->{'memo'} if defined $self->{'memo'};
290 7         39 require XBase::Memo;
291             my %options = ( 'dbf_version' => $self->{'version'},
292 7         26 'memosep' => $self->{'openoptions'}{'memosep'} );
293            
294 7 50       22 if (defined $self->{'openoptions'}{'memofile'}) {
295 0         0 return XBase::Memo->new($self->{'openoptions'}{'memofile'}, %options);
296             }
297            
298 7         18 for (qw( dbt DBT fpt FPT smt SMT dbt )) {
299 10         7 my $memo;
300 10         68 my $memoname = $self->{'filename'};
301 10 100 33     157 ($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 220 my $self = shift;
311 5 100       19 if (defined $self->{'memo'}) {
312 2         17 $self->{'memo'}->close(); delete $self->{'memo'};
  2         3  
313             }
314 5         47 $self->SUPER::close();
315             }
316              
317             # ###############
318             # Little decoding
319 5     5 0 43 sub version { shift->{'version'}; }
320 3270     3270 1 9601 sub last_record { shift->{'num_rec'} - 1; }
321 105     105 1 196 sub last_field { shift->{'last_field'}; }
322              
323             # List of field names, types, lengths and decimals
324 9     9 1 16 sub field_names { @{shift->{'field_names'}}; }
  9         32  
325 14     14 1 9 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 48 my ($self, $name) = @_; $self->{'hash_names'}{uc $name};
  52         142  
332             }
333             sub field_type {
334 14     14 1 19 my ($self, $name) = @_;
335 14 50       25 defined (my $num = $self->field_name_to_num($name)) or return;
336 14         35 ($self->field_types)[$num];
337             }
338             sub field_length {
339 2     2 1 2 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 231 my $self = shift;
357 2         5 my $hexversion = sprintf '0x%02x', $self->version;
358 2         7 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         16 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         12  
373             (0 .. $self->last_field);
374             }
375             # Return info about field in dbf file
376             sub get_field_info {
377 10     10 0 8 my ($self, $num) = @_;
378             sprintf "%d.\t%-16.16s%-8.8s%-8.8s%s\n", $num + 1,
379 10         10 map { $self->{$_}[$num] }
  40         71  
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         2 my $date = $self;
386 2 50       7 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 3 my $version = shift;
394 2 50       7 $version = $version->version() if ref $version;
395 2         7 my $result = {};
396 2         6 $result->{'vbits'} = $version & 0x07;
397 2 50 33     24 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         5 $result->{'dbt'} = 1;
403             }
404              
405 2         8 my $string = "ver. $result->{'vbits'}";
406 2 50       4 if (exists $result->{'foxpro'}) {
407 0         0 $string .= " (FoxPro)";
408             }
409 2 50       9 if (exists $result->{'memo'}) {
    50          
410 0         0 $string .= " with memo file";
411             } elsif (exists $result->{'dbt'}) {
412 2         3 $string .= " with DBT file";
413             }
414 2         3 $result->{'string'} = $string;
415              
416 2         7 $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 1275 my ($self, $num) = (shift, shift);
519 117         195 $self->NullError();
520 117         152 $self->get_record_nf( $num, map { $self->field_name_to_num($_); } @_);
  16         22  
521             }
522             *get_record_as_hash = \&get_record_hash;
523             sub get_record_hash {
524 1     1 0 241 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       9 return %$hash if wantarray;
529 0         0 $hash;
530             }
531             sub get_record_nf {
532 1545     1545 1 1514 my ($self, $num, @fieldnums) = @_;
533 1545 100       2310 my $data = $self->read_record($num) or return;
534 1543 100       2227 if (not @fieldnums) {
535 100         154 @fieldnums = ( 0 .. $self->last_field );
536             }
537             my $unpack = join ' ', '@0a1',
538 1543         1590 map { my $e;
  1953         1153  
539 1953 50       2801 defined $_ and $e = $self->{'field_unpacks'}[$_];
540 1953 50       3821 defined $e ? $e : '@0a0'; } @fieldnums;
541            
542 1543         1409 my $rproc = $self->{'field_rproc'};
543 1543 50 33 0   1629 my @fns = (\&_read_deleted, map { (defined $_ and defined $rproc->[$_]) ? $rproc->[$_] : sub { undef; }; } @fieldnums);
  1953         6035  
  0         0  
544              
545 1543         3855 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         1880 for (@out) { $_ = &{ shift @fns }($_); }
  3496         2234  
  3496         3908  
558              
559 1543         3004 @out;
560             }
561              
562             # Processing on read
563             sub _read_deleted {
564 1543     1543   1116 my $value = shift;
565 1543 100       2878 if ($value eq '*') { return 1; } elsif ($value eq ' ') { return 0; }
  3 50       5  
  1540         2061  
566 0         0 undef;
567             }
568              
569             sub get_all_records {
570 1     1 0 361 my $self = shift;
571 1         5 my $cursor = $self->prepare_select(@_);
572              
573 1         3 my $result = [];
574 1         1 my @record;
575 1         5 while (@record = $cursor->fetch())
576 42         124 { push @$result, [ @record ]; }
577 1         16 $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 1637 my ($self, $num, @data) = @_;
587 14         28 $self->NullError();
588 14         20 my $wproc = $self->{'field_wproc'};
589              
590 14 100       34 if (defined $self->{'attached_index_columns'}) {
591 1         1 my @nfs = keys %{$self->{'attached_index_columns'}};
  1         5  
592 1         6 my ($del, @old_data) = $self->get_record_nf($num, @nfs);
593              
594 1         4 local $^W = 0;
595 1         3 for my $nf (@nfs) {
596 2 50       18 if ($old_data[$nf] ne $data[$nf]) {
597 2         3 for my $idx (@{$self->{'attached_index_columns'}{$nf}}) {
  2         7  
598 5         22 $idx->delete($old_data[$nf], $num + 1);
599 5         30 $idx->insert($data[$nf], $num + 1);
600             }
601             }
602             }
603             }
604              
605 14         39 for (my $i = 0; $i <= $#$wproc; $i++) {
606 43         34 $data[$i] = &{ $wproc->[$i] }($data[$i]);
  43         63  
607             }
608 14         20 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         32 $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 20 my ($self, $num) = (shift, shift);
645 16 50       45 my $ret = $self->SUPER::write_record($num, @_) or return;
646              
647 16 100       30 if ($num > $self->last_record) {
648 13         25 $self->SUPER::write_record($num + 1, "\x1a"); # add EOF
649 13 50       24 $self->update_last_record($num) or return;
650             }
651 16 50       26 $self->update_last_change or return;
652 16         36 $ret;
653             }
654              
655             # Delete and undelete record
656             sub delete_record {
657 1     1 0 407 my ($self, $num) = @_;
658 1         4 $self->NullError();
659 1         2 $self->write_record($num, "*");
660             }
661             sub undelete_record {
662 1     1 0 233 my ($self, $num) = @_;
663 1         6 $self->NullError();
664 1         4 $self->write_record($num, " ");
665             }
666              
667             # Update the last change date
668             sub update_last_change {
669 18     18 0 16 my $self = shift;
670 18 100       47 return 1 if defined $self->{'updated_today'};
671 7 50       417 my ($y, $m, $d) = (localtime)[5, 4, 3]; $m++; $y -= 100 if $y >= 100;
  7         12  
  7         24  
672 7 50       36 $self->write_to(1, pack "C3", ($y, $m, $d)) or return;
673 7         29 $self->{'updated_today'} = 1;
674             }
675             # Update the number of records
676             sub update_last_record {
677 13     13 0 14 my ($self, $last) = @_;
678 13         11 $last++;
679 13         46 $self->write_to(4, pack "V", $last);
680 13         30 $self->{'num_rec'} = $last;
681             }
682              
683             # Creating new dbf file
684             sub create {
685 2     2 1 973 XBase->NullError();
686 2         3 my $class = shift;
687 2         10 my %options = @_;
688 2 50       8 if (ref $class) {
689 0         0 %options = ( %$class, %options ); $class = ref $class;
  0         0  
690             }
691              
692 2         4 my $version = $options{'version'};
693 2 50       9 if (not defined $version) {
694 2 50 33     12 if (defined $options{'memofile'}
695             and $options{'memofile'} =~ /\.fpt$/i) {
696 0         0 $version = 0xf5;
697             } else {
698 2         5 $version = 3;
699             }
700             }
701              
702 2         2 my $key;
703 2         5 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         2 my $needmemo = 0;
711              
712 2         3 my $fieldspack = '';
713 2         2 my $record_len = 1;
714 2         4 my $i;
715 2         3 for $i (0 .. $#{$options{'field_names'}}) {
  2         7  
716 7         13 my $name = uc $options{'field_names'}[$i];
717 7 50       11 $name = "FIELD$i" unless defined $name;
718 7         6 $name .= "\0";
719 7         6 my $type = $options{'field_types'}[$i];
720 7 50       10 $type = 'C' unless defined $type;
721              
722 7         12 my $length = $options{'field_lengths'}[$i];
723 7         7 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       28 if ($type =~ /^[MBGP]$/) { $length = 10; $decimal = 0; }
  1 100       1  
  1 50       2  
732 1         1 elsif ($type eq 'L') { $length = 1; $decimal = 0; }
  1         2  
733 0         0 elsif ($type eq 'Y') { $length = 8; $decimal = 4; }
  0         0  
734              
735 7 100       10 if (not defined $decimal) {
736 3         2 $decimal = 0;
737             }
738            
739 7         6 $record_len += $length;
740 7         4 my $offset = $record_len;
741 7 100       12 if ($type eq 'C') {
742 2         4 $decimal = int($length / 256);
743 2         3 $length %= 256;
744             }
745 7         26 $fieldspack .= pack 'a11a1VCCvCvCa7C', $name, $type, $offset,
746             $length, $decimal, 0, 0, 0, 0, '', 0;
747 7 100       12 if ($type eq 'M') {
748 1         2 $needmemo = 1;
749 1 50       2 if ($version != 0x30) {
750 1         2 $version |= 0x80;
751             }
752             }
753             }
754 2         4 $fieldspack .= "\x0d";
755              
756             {
757 2         5 local $^W = 0;
  2         8  
758 2         10 $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         9 0, $options{'codepage'},
768             0;
769 2         3 $header .= $fieldspack;
770 2         2 $header .= "\x1a";
771              
772 2         12 my $tmp = $class->new();
773 2         2 my $basename = $options{'name'};
774 2         8 $basename =~ s/\.dbf$//i;
775 2         3 my $newname = $options{'name'};
776 2 100 66     21 if (defined $newname and not $newname =~ /\.dbf$/) {
777 1         2 $newname .= '.dbf';
778             }
779 2 50       11 $tmp->create_file($newname, 0700) or return;
780 2 50       12 $tmp->write_to(0, $header) or return;
781 2         9 $tmp->update_last_change();
782 2         7 $tmp->close();
783              
784 2 100       5 if ($needmemo) {
785 1         6 require XBase::Memo;
786 1         2 my $dbtname = $options{'memofile'};
787 1 50       2 if (not defined $dbtname) {
788 1         1 $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       8 $dbtname =~ s/\.DBF$/.DBT/ or $dbtname =~ s/(\.dbf)?$/.dbt/;
793             }
794             }
795 1         7 my $dbttmp = XBase::Memo->new();
796 1         2 my $memoversion = ($version & 15);
797 1 50       3 $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         16 return $class->new($options{'name'});
804             }
805             # Drop the table
806             sub drop {
807 1     1 1 209 my $self = shift;
808 1         2 my $filename = $self;
809 1 50       3 if (ref $self) {
810 1 50       2 if (defined $self->{'memo'}) {
811 1         12 $self->{'memo'}->drop();
812 1         2 delete $self->{'memo'};
813             }
814 1         5 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 197 my ($self, $indexfile) = @_;
853 1         621 require XBase::Index;
854              
855 1 50       5 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         5 my @tags = $index->tags;
862 1         1 my @indexes;
863 1 50       2 if (@tags) {
864 1         2 for my $tag (@tags) {
865             my $index = $self->XBase::Index::new($indexfile,
866             'tag' => $tag)
867 5 50       15 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         5 my $key = $idx->{'key_string'};
879 5         6 my $num = $self->field_name_to_num($key);
880              
881 5 50       7 print "Got key string $key -> $num\n" if $XBase::Index::VERBOSE;
882            
883             $self->{'attached_index'} = []
884 5 100       9 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         9  
887             }
888 1         4 1;
889             }
890              
891             #
892             # Cursory select
893             #
894              
895             sub prepare_select {
896 2     2 1 204 my $self = shift;
897 2         6 my $fieldnames = [ @_ ];
898 2 100       47 if (not @_) { $fieldnames = [ $self->field_names ] };
  1         4  
899 2         5 my $fieldnums = [ map { $self->field_name_to_num($_); } @$fieldnames ];
  3         9  
900 2         13 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 1633 my ($self, $file) = ( shift, shift );
913 8         17 my @tagopts = ();
914 8 100       79 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         15 my $fieldnames = [ @_ ];
923 8 100       19 if (not @_) { $fieldnames = [ $self->field_names ] };
  6         23  
924 8         16 my $fieldnums = [ map { $self->field_name_to_num($_); } @$fieldnames ];
  12         43  
925 8         1955 require XBase::Index;
926             my $index = new XBase::Index $file, 'dbf' => $self, @tagopts or
927 8 50       49 do { $self->Error(XBase->errstr); return; };
  0         0  
  0         0  
928             $index->prepare_select or
929 8 50       32 do { $self->Error($index->errstr); return; };
  0         0  
  0         0  
930 8         44 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   79 use vars qw( @ISA );
  11         13  
  11         2755  
937             @ISA = qw( XBase::Base );
938              
939             sub fetch {
940 112     112   400 my $self = shift;
941 112         107 my ($xbase, $recno, $fieldnums, $fieldnames) = @$self;
942 112 100       119 if (defined $recno) { $recno++; }
  110         72  
943 2         4 else { $recno = 0; }
944 112         135 my $lastrec = $xbase->last_record;
945 112         164 while ($recno <= $lastrec) {
946 110         128 my ($del, @result) = $xbase->get_record_nf($recno, @$fieldnums);
947 110 50 33     328 if (@result and not $del) {
948 110         97 $self->[1] = $recno;
949 110         231 return @result;
950             }
951 0         0 $recno++;
952             }
953 2         7 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   52 use vars qw( @ISA );
  11         12  
  11         1678  
986             @ISA = qw( XBase::Cursor );
987              
988             sub find_eq {
989 42     42   13188 my $self = shift;
990 42         157 $self->[4]->prepare_select_eq(shift);
991             }
992             sub fetch {
993 1360     1360   11620 my $self = shift;
994 1360         1263 my ($xbase, $recno, $fieldnums, $fieldnames, $index) = @$self;
995 1360         934 my ($key, $val);
996 1360         2219 while (($key, $val) = $index->fetch) {
997 1317         2249 my ($del, @result) = $xbase->get_record_nf($val - 1, @$fieldnums);
998 1317 50       1770 unless ($del) {
999 1317         1033 $self->[1] = $val;
1000 1317         2531 return @result;
1001             }
1002             }
1003 43         66 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__