File Coverage

blib/lib/XBase.pm
Criterion Covered Total %
statement 410 616 66.5
branch 145 292 49.6
condition 23 60 38.3
subroutine 54 84 64.2
pod 21 41 51.2
total 653 1093 59.7


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