File Coverage

blib/lib/XBase/Memo.pm
Criterion Covered Total %
statement 127 182 69.7
branch 30 76 39.4
condition 5 18 27.7
subroutine 19 21 90.4
pod 1 4 25.0
total 182 301 60.4


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             XBase::Memo - Generic support for various memo formats
5              
6             =cut
7              
8             package XBase::Memo;
9              
10 11     11   176 use strict;
  11         19  
  11         565  
11 11     11   7277 use XBase::Base;
  11         34  
  11         447  
12              
13 11     11   218 use vars qw( $VERSION @ISA );
  11         104  
  11         10499  
14             @ISA = qw( XBase::Base );
15             $VERSION = '1.02';
16              
17             # Read header is called from open to fill the object structures
18             sub read_header {
19 7     7 0 11 my $self = shift;
20 7         17 my %options = @_;
21              
22 7         11 my $header;
23 7         41 my $read_h_len = $self->read($header, 512);
24 7 50 33     34 unless ($read_h_len == 512 or $read_h_len == 24) {
25 0         0 $self->Error("Error reading header of $self->{'filename'}: $!\n");
26 0         0 return;
27             };
28              
29 7         10 my ($next_for_append, $block_size, $version);
30 7         18 my $filename = $self->{'filename'};
31 7 100       46 if ($filename =~ /\.fpt$/i) {
    50          
32 1         4 ($next_for_append, $block_size) = unpack 'N@6n', $header;
33 1         2 $version = 5;
34 1         4 bless $self, 'XBase::Memo::Fox';
35             } elsif ($filename =~ /\.smt$/i) {
36 0         0 ($next_for_append, $block_size) = unpack 'VV', $header;
37 0         0 bless $self, 'XBase::Memo::Apollo';
38             } else {
39 6         20 ($next_for_append, $version, $block_size)
40             = unpack 'V @16C @20v', $header;
41 6         60 my $dbf_version = $options{'dbf_version'};
42 6 50       23 $dbf_version = 15 unless defined $dbf_version;
43 6 50 33     38 if ((($dbf_version & 15) == 3) or $version == 3) {
44 6         10 $block_size = 512;
45 6         10 $version = 3;
46 6         18 bless $self, 'XBase::Memo::dBaseIII';
47             } else {
48 0         0 $version = 4;
49 0         0 bless $self, 'XBase::Memo::dBaseIV';
50             }
51             }
52              
53 7 50       22 $block_size = 512 if int($block_size) == 0;
54              
55 7         124 $next_for_append = int ((((-s $self->{'filename'}) - 1) / $block_size) + 1);
56              
57 7         13 @{$self}{ qw( next_for_append header_len record_len version ) }
  7         30  
58             = ( $next_for_append, $block_size, $block_size, $version );
59              
60 7         14 $self->{'memosep'} = $options{'memosep'};
61              
62 7         54 1;
63             }
64              
65             sub write_record {
66 6     6 1 10 my ($self, $num) = (shift, shift);
67 6         13 my $length = length(join '', @_);
68 6         12 my $record_len = $self->{'record_len'};
69 6         24 my $num_of_blocks = int (($length + $record_len - 1) / $record_len);
70 6         29 $self->SUPER::write_record($num, @_);
71 6 50 33     22 if ($num < 0 or $num > $self->last_record()) {
72 6         19 my $packed = pack "V", $num + $num_of_blocks + 1;
73 6 100       25 if (ref $self eq 'XBase::Memo::Fox') {
74 2         5 $packed = pack "N", $num + $num_of_blocks + 1;
75             }
76 6         22 $self->SUPER::write_to(0, $packed);
77 6         14 $self->{'next_for_append'} = $num + $num_of_blocks + 1;
78             }
79 6         9 $num;
80             }
81              
82             sub last_record {
83 54     54 0 173 shift->{'next_for_append'} - 2;
84             }
85              
86             sub create {
87 1     1 0 2 my $self = shift;
88 1         4 my %options = @_;
89 1 50       8 $self->create_file($options{'name'}) or return;
90 1         2 my $version = $options{'version'};
91 1 50       7 if ($options{'name'} =~ /\.smt$/i) {
    50          
92             # For xmt file (whatever they are)
93 0 0       0 $self->write_to(0, pack 'VV a504', 1, 512, '') or return;
94             } elsif ($version == 5) {
95             # Fox fpt file
96 0 0       0 $self->write_to(0, pack 'N a2 n a504', 1, '', 512, '') or return;
97             } else {
98 1 50       3 $version = 3 unless defined $version;
99 1 50       4 $version = 0 if $version == 4;
100 1 50       10 $self->write_to(0, pack 'VVa8Ca3va490', 1, 0,
101             $options{'dbf_filename'}, $version, '', 512, '')
102             or return;
103             }
104 1         6 $self->close();
105 1         5 return $self;
106             }
107              
108              
109             # ################################
110             # dBase III+ specific memo methods
111              
112             package XBase::Memo::dBaseIII;
113              
114 11     11   72 use XBase::Base;
  11         18  
  11         554  
115 11     11   52 use vars qw( @ISA );
  11         18  
  11         5760  
116             @ISA = qw( XBase::Memo );
117              
118             sub read_record {
119 10     10   15 my ($self, $num) = @_;
120 10         14 my $result = '';
121 10         27 my $last = $self->last_record();
122 10 100       31 if (not defined $self->{'memosep'}) {
123 2         12 $self->{'memosep'} = "\x1a\x1a";
124 2 50       11 if (not defined $self->read_record($last)) {
125 0         0 $self->{'memosep'} = "\x1a";
126 0 0       0 if (not defined $self->read_record($last)) {
127 0         0 $self->{'memosep'} = "\x1a\x1a";
128             }
129             }
130             }
131              
132 10         20 while ($num <= $last) {
133 10 50       48 my $buffer = $self->SUPER::read_record($num, -1) or return;
134 10         23 my $index = index($buffer, $self->{'memosep'});
135 10 50       22 if ($index >= 0) {
136 10         59 return $result . substr($buffer, 0, $index);
137             }
138 0         0 $result .= $buffer;
139 0         0 $num++;
140             }
141 0         0 return;
142             }
143              
144             sub write_record {
145 4     4   9 my ($self, $num) = (shift, shift);
146 4         6 my $type = shift;
147 4         12 my $data = join "", @_, "\x1a\x1a";
148 4 50 33     15 if ($num >= 0 and $num <= $self->last_record()) {
149 0         0 my $buffer = $self->read_record($num);
150 0 0       0 if (defined $buffer) {
151 0         0 my $length = length $buffer;
152 0         0 my $record_len = $self->{'record_len'};
153 0         0 my $space_in_blocks =
154             int (($length + $record_len - 3) / $record_len);
155 0         0 my $len_in_blocks =
156             int ((length($data) + $record_len - 1) / $record_len);
157 0 0       0 if ($len_in_blocks > $space_in_blocks) {
158 0         0 $num = $self->last_record() + 1;
159             }
160             }
161             } else {
162 4         18 $num = $self->last_record() + 1;
163             }
164 4         19 $self->SUPER::write_record($num, $data);
165 4         10 $num;
166             }
167              
168             # ################################
169             # dBase IV specific memo methods
170              
171             package XBase::Memo::dBaseIV;
172              
173 11     11   68 use XBase::Base;
  11         20  
  11         318  
174 11     11   49 use vars qw( @ISA );
  11         34  
  11         8905  
175             @ISA = qw( XBase::Memo );
176              
177             sub read_record {
178 4     4   5 my ($self, $num) = @_;
179 4         4 my $result = '';
180 4         6 my $last = $self->last_record;
181              
182 4         14 my $buffer = $self->SUPER::read_record($num, -1);
183 4 50       10 if (not defined $buffer) { return; }
  0         0  
184 4         5 my $unpackstr;
185 4 50       9 if (ref $self eq 'XBase::Memo::Fox') {
186 4         5 $unpackstr = 'NN';
187             } else {
188 0         0 $unpackstr = 'VV';
189 0 0       0 return unless substr($buffer, 0, 4) eq "\xff\xff\x08\x00";
190             }
191 4         7 my ($unused_id, $length) = unpack $unpackstr, $buffer;
192 4 50       9 $length += 8 if ref $self eq 'XBase::Memo::Fox';
193              
194 4         6 my $block_size = $self->{'record_len'};
195 4 50       8 if ($length < $block_size) {
196 4         22 return substr $buffer, 8, $length - 8;
197             }
198 0         0 my $rest_length = $length - $block_size;
199 0         0 my $rest_data = $self->SUPER::read_record($num + 1, $rest_length);
200 0 0       0 if (not defined $rest_data) { return; }
  0         0  
201 0         0 return substr($buffer, 8) . $rest_data;
202             }
203              
204             sub write_record {
205 2     2   3 my ($self, $num) = (shift, shift);
206 2         8 my $type = shift;
207 2         5 my $data = join "", @_;
208 2         3 my $length = (length $data) + 8;
209              
210 2         6 my $startfield = "\xff\xff\x08\x00" . pack('V', $length);
211 2 50       6 if (ref $self eq 'XBase::Memo::Fox') {
212 2 50       8 if ($type eq 'P') { $startfield = pack 'N', 0; }
  0 50       0  
213 2         3 elsif ($type eq 'M') { $startfield = pack 'N', 1; }
214 0         0 else { $startfield = pack 'N', 2; }
215 2         5 $startfield .= pack 'N', ($length - 8);
216             }
217             ### $data = $startfield . $data . "\x1a\x1a";
218 2         4 $data = $startfield . $data;
219              
220 2 50 33     11 if ($num >= 0 and $num <= $self->last_record()) {
221 0         0 my $buffer = $self->read_record($num);
222 0 0       0 if (defined $buffer) {
223 0         0 my $length = (length $buffer) - 8;
224 0         0 my $record_len = $self->{'record_len'};
225 0         0 my $space_in_blocks =
226             int (($length + $record_len - 11) / $record_len);
227 0         0 my $len_in_blocks =
228             int ((length($data) + $record_len - 1) / $record_len);
229 0 0       0 if ($len_in_blocks > $space_in_blocks) {
230 0         0 $num = $self->last_record() + 1;
231             }
232             } else {
233 0         0 $num = $self->last_record() + 1;
234             }
235             } else {
236 2         14 $num = $self->last_record() + 1;
237             }
238 2         7 my $fill = $self->{'record_len'} - (( length $data ) % $self->{'record_len'});
239 2         6 $data .= "\000" x $fill;
240 2         9 $self->SUPER::write_record($num, $data);
241 2         5 $num;
242             }
243              
244              
245             # #######################################
246             # FoxPro specific memo methods (fpt file)
247              
248             package XBase::Memo::Fox;
249              
250 11     11   143 use XBase::Base;
  11         19  
  11         234  
251 11     11   54 use vars qw( @ISA );
  11         27  
  11         853  
252             @ISA = qw( XBase::Memo::dBaseIV );
253              
254             # #######################################
255             # Apollo specific memo methods (smt file)
256             #
257             # This is a real hack! No documentation used but it works for all files
258             # i have tested with.
259             # Dirk Tostmann (tostmann@tiss.com)
260              
261             package XBase::Memo::Apollo;
262              
263 11     11   65 use XBase::Base;
  11         21  
  11         278  
264 11     11   56 use vars qw( @ISA );
  11         28  
  11         3662  
265             @ISA = qw( XBase::Memo::dBaseIV );
266              
267             sub read_record {
268 0     0     my ($self, $num) = @_;
269 0           my $result = '';
270              
271 0 0         return if $num =~ /^\s+$/;
272              
273 0           my ($block, $len, $offset) = unpack('vVV', $num);
274 0           $block *= 8;
275              
276 0           $result = $self->read_from($offset * $block, $len);
277              
278 0           $result;
279             }
280              
281             sub write_record {
282 0     0     my ($self, $num, $type) = (shift, shift, shift);
283 0           my $data = join "", @_;
284 0           my $length = length $data;
285 0           $num = $self->SUPER::write_record($self->{'next_for_append'}, $data);
286 0 0 0       if (defined $num and $num) {
287 0           pack 'vVV', $self->{'block_length'} / 8 , $length, $num;
288             } else {
289 0           ' ' x 10;
290             }
291             }
292             1;
293              
294             __END__