| 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__ |