File Coverage

blib/lib/CAM/DBF.pm
Criterion Covered Total %
statement 208 431 48.2
branch 42 154 27.2
condition 21 93 22.5
subroutine 20 41 48.7
pod 30 30 100.0
total 321 749 42.8


line stmt bran cond sub pod time code
1             package CAM::DBF;
2              
3             require 5.005_62;
4 1     1   98664 use warnings;
  1         3  
  1         33  
5 1     1   5 use strict;
  1         2  
  1         213  
6 1     1   6 use Carp;
  1         7  
  1         4480  
7              
8             our $VERSION = '1.02';
9              
10             ## Package globals
11              
12             # Performance tests showed that a rowcache of 100 is better than
13             # rowcaches of 10 or 1000 (presumably due to tradeoffs in overhead
14             # vs. processor data cache usage vs. memory allocation)
15              
16             our $ROWCACHE = 100; # how many rows to cache at a time
17             # Set that to 0 for debugging
18              
19              
20             =for stopwords Borland DBF XBase dBASE
21              
22             =head1 NAME
23              
24             CAM::DBF - Perl extension for reading and writing dBASE III DBF files
25              
26             =head1 LICENSE
27              
28             Copyright 2006 Clotho Advanced Media, Inc.,
29              
30             This library is free software; you can redistribute it and/or modify it
31             under the same terms as Perl itself.
32              
33             =head1 SEE ALSO
34              
35             Please see the L modules on CPAN for more complete
36             implementations of DBF file reading and writing. This module differs
37             from those in that it is designed to be error-correcting for corrupted
38             DBF files. If you already know how to use L, then L
39             will likely make you happier than this module.
40              
41             I don't do much DBF work any longer, so updates to this module will be
42             infrequent.
43              
44             =head1 SYNOPSIS
45              
46             use CAM::DBF;
47             my $dbf = CAM::DBF->new($filename);
48            
49             # Read routines:
50            
51             print join('|', $dbf->fieldnames()),"\n";
52             for my $row (0 .. $dbf->nrecords()-1) {
53             print join('|', $dbf->fetchrow_array($row)),"\n";
54             }
55            
56             my $row = 100;
57             my $hashref = $dbf->fetchrow_hashref($row);
58             my $arrayref = $dbf->fetchrow_hashref($row);
59            
60             # Write routines:
61            
62             $dbf->delete($row);
63             $dbf->undelete($row);
64              
65             =head1 DESCRIPTION
66              
67             This package facilitates reading and writing dBASE III PLUS DBF files.
68             This is made possible by documentation generously released by Borland
69             at L
70              
71             Currently, only version III PLUS files are readable. This module does
72             not support dBASE version IV or 5.0 files. See L for better
73             support.
74              
75             =head1 CLASS METHODS
76              
77             =over 4
78              
79             =cut
80              
81             #----------------
82              
83             # Internal function, called by new() or create()
84              
85             my %filemode_open_map = (
86             'r' => '<',
87             'r+' => '+<',
88             'w' => '>',
89             'w+' => '+>',
90             'a' => '>>',
91             'a+' => '+>>',
92             );
93              
94             sub _init
95             {
96 2     2   5 my $pkg = shift;
97 2         4 my $filename = shift;
98 2         12 my $filemode = shift;
99              
100 2         4 my %flags;
101 2 50       8 if (@_ % 2 == 0)
102             {
103 2         5 %flags = @_;
104             }
105              
106 2 100 66     13 if (!defined $filemode || $filemode eq q{})
107             {
108 1         2 $filemode = 'r';
109             }
110              
111 2 50       10 if (!$filemode_open_map{$filemode})
112             {
113 0         0 croak 'Invalid file mode';
114             }
115              
116 2         249 my @times = localtime;
117 2         6 my $year = $times[5]+1900;
118 2         3 my $month = $times[4]+1;
119 2         3 my $date = $times[3];
120              
121 2         31 my $self = bless {
122             filename => $filename,
123             filemode => $filemode,
124             fh => undef,
125             fields => [],
126             columns => [],
127              
128             valid => 0x03,
129             year => $year,
130             month => $month,
131             date => $date,
132             nrecords => 0,
133             nheaderbytes => 0,
134             nrecordbytes => 0,
135             packformat => 'C',
136              
137             flags => \%flags,
138             }, $pkg;
139              
140 2         11 $self->_open_fh();
141              
142 2         6 return $self;
143             }
144             sub _open_fh
145             {
146 2     2   3 my $self = shift;
147              
148 2 50       12 if ($self->{filename} eq q{-})
149             {
150             # This might be fragile, since seek won't work
151 0 0       0 if ($self->{filemode} =~ m/r/xms)
152             {
153 0         0 $self->{fh} = \*STDIN;
154             }
155             else
156             {
157 0         0 $self->{fh} = \*STDOUT;
158             }
159             }
160             else
161             {
162 2         2 my $fh;
163 2 50       119 if (open $fh, $filemode_open_map{$self->{filemode}}, $self->{filename})
164             {
165 2         6 $self->{fh} = $fh;
166             }
167             }
168 2 50       12 if (!$self->{fh})
169             {
170 0         0 croak "Cannot open DBF file $self->{filename}: $!";
171             }
172 2         7 binmode $self->{fh};
173              
174 2         4 return;
175             }
176              
177             #----------------
178              
179             =item $pkg->new($filename)
180              
181             =item $pkg->new($filename, $mode)
182              
183             =item $pkg->new($filename, $mode, $key => $value, ...)
184              
185             Open and read a dBASE file. The optional mode parameter defaults to
186             C for read-only. If you plan to alter the DBF, open it as C.
187              
188             Additional behavior flags can be passed after the file mode.
189             Available flags are:
190              
191             =over
192              
193             =item C<< ignoreHeaderBytes => 0|1 >>
194              
195             Looks for the 0x0D end-of-header marker instead of trusting the
196             stated header length. Default 0.
197              
198             =item C<< allowOffByOne => 0|1 >>
199              
200             Only matters if C is on. If the computed header size
201             differs from the declared header size by one byte, use the
202             latter. Default 0.
203              
204             =item C<< verbose => 0|1 >>
205              
206             Print warning messages about header problems, or stay quiet. Default
207             0.
208              
209             =back
210              
211             =cut
212              
213             sub new
214             {
215 1     1 1 468 my $pkg = shift;
216 1         3 my $filename = shift;
217 1         2 my $filemode = shift;
218              
219 1         6 my $self = $pkg->_init($filename, $filemode, @_);
220              
221             ## Parse the header
222              
223 1         2 my $header;
224 1         25 read $self->{fh}, $header, 32;
225 1         11 ($self->{valid},
226             $self->{year},
227             $self->{month},
228             $self->{date},
229             $self->{nrecords},
230             $self->{nheaderbytes},
231             $self->{nrecordbytes}) = unpack 'CCCCVvv', $header;
232            
233 1 50 33     23 if (!$self->{valid} || $self->{valid} != 0x03 && $self->{valid} != 0x83)
      33        
234             {
235 0         0 croak "This does not appear to be a dBASE III PLUS file ($filename)";
236             }
237              
238 1         3 my $filesize = ($self->{nheaderbytes} +
239             $self->{nrecords} * $self->{nrecordbytes});
240 1         19 $self->{filesize} = -s $filename;
241              
242 1 50       5 if ($self->{filesize} < $self->{nheaderbytes})
243             {
244 0 0       0 if (!$self->{flags}->{ignoreHeaderBytes})
245             {
246 0         0 croak "DBF file $filename appears to be severely truncated:\n" .
247             "Header says it should be $filesize bytes, but it's only $self->{filesize} bytes\n" .
248             " Records = $self->{nrecords}\n";
249             }
250             }
251            
252             # correct 2 digit year
253 1         3 $self->{year} += 1900;
254 1 50       4 if ($self->{year} < 1970)
255             {
256 1         4 $self->{year} += 100; # Y2K fix
257             }
258              
259 1         2 my $field;
260 1         2 my $pos = 64;
261 1         4 read $self->{fh}, $field, 1;
262              
263             # acording to the Borland spec 0x0D marks the end of the header block
264             # however we have seen this fail so $pos ensures we do not read beyond
265             # the header block for table columns
266             # We've also found flaky files which use 0x0A instead of 0x0D
267 1   66     31 while ($field && (0x0D != unpack 'C', $field) && (0x0A != unpack 'C', $field) &&
      66        
      33        
      66        
268             ($self->{flags}->{ignoreHeaderBytes} || $pos < $self->{nheaderbytes}))
269             {
270 6         14 read $self->{fh}, $field, 31, 1;
271 6         27 my ($name, $type, $len, $dec) = unpack 'a11a1xxxxCC', $field;
272              
273 6         27 $name =~ s/\A(\w+).*?\z/$1/xms;
274            
275 6         10 push @{$self->{fields}}, {
  6         21  
276             name => $name,
277             type => $type,
278             length => $len,
279             decimals => $dec,
280             };
281 6         8 push @{$self->{columns}}, $name;
  6         12  
282              
283 6         6 $pos += 32;
284 6         55 read $self->{fh}, $field, 1;
285             }
286              
287 1 50       5 if ($self->{flags}->{ignoreHeaderBytes})
288             {
289             # replace stated header size with the actual, computed value
290 0         0 my $oldvalue = $self->{nheaderbytes};
291 0         0 my $newvalue = (@{$self->{fields}} + 1) * 32 + 1;
  0         0  
292             # skip the replacement if the flags say to be lenient
293 0 0 0     0 unless ($self->{flags}->{allowOffByOne} && abs($oldvalue-$newvalue) <= 1) ## no critic
294             {
295 0         0 $self->{nheaderbytes} = $newvalue;
296 0 0 0     0 if ($self->{flags}->{verbose} && $oldvalue != $self->{nheaderbytes})
297             {
298 0         0 warn "Corrected header size from $oldvalue to $self->{nheaderbytes} for $self->{filename}\n";
299             }
300             }
301             }
302              
303 1         3 $self->{packformat} = 'C';
304 1         2 for my $field (@{$self->{fields}})
  1         4  
305             {
306 6 50       18 if ($field->{type} =~ m/\A[CLND]\z/xms)
307             {
308 6         18 $self->{packformat} .= 'a' . $field->{length};
309             }
310             else
311             {
312 0         0 croak 'unrecognized field type ' . $field->{type} . ' in field ' . $field->{name};
313             }
314             }
315 1         12 seek $self->{fh}, $self->{nheaderbytes}, 0;
316              
317 1         4 return $self;
318             }
319             #----------------
320              
321             =item $pkg->create($filename, [flags,] $column, $column, ...)
322              
323             =item $pkg->create($filename, $filemode, [flags,] $column, $column, ...)
324              
325             Create a new DBF file in C<$filename>, initially empty. The optional
326             C<$filemode> argument defaults to C. We can't think of any reason to
327             use any other mode, but if you can think of one, go for it.
328              
329             The column structure is specified as a list of hash references, each
330             containing the fields: name, type, length and decimals. The name
331             should be 11 characters or shorted. The type should be one of C, C,
332             C, or C (for character, number, date or logical).
333              
334             The optional flags are:
335              
336             -quick => 0|1 (default 0) -- skips column format checking if set
337              
338             Example:
339              
340             my $dbf = CAM::DBF->create('new.dbf',
341             {name=>'id',
342             type=>'N', length=>8, decimals=>0},
343             {name=>'lastedit',
344             type=>'D', length=>8, decimals=>0},
345             {name=>'firstname',
346             type=>'C', length=>15, decimals=>0},
347             {name=>'lastname',
348             type=>'C', length=>20, decimals=>0},
349             );
350              
351             =cut
352              
353             sub create
354             {
355 1     1 1 1242 my $pkg = shift;
356 1         2 my $filename = shift;
357              
358             # Optional args:
359 1         2 my $quick = 0;
360 1         3 my $filemode = 'w+';
361 1   33     13 while (@_ > 0 && $_[0] && (!ref $_[0]))
      33        
362             {
363 0 0       0 if ($_[0] eq '-quick')
    0          
364             {
365 0         0 shift;
366 0         0 $quick = shift;
367             }
368             elsif ($filemode_open_map{$_[0]})
369             {
370 0         0 $filemode = shift;
371             }
372             else
373             {
374 0         0 carp "Argument $_[0] not understood";
375 0         0 return;
376             }
377             }
378              
379             # The rest of the args are the data structure definition
380 1         4 my @columns = @_;
381              
382             # Validate the column structure
383 1 50       3 if ($quick)
384             {
385 0 0       0 if (!$pkg->validateColumns(@columns))
386             {
387 0         0 return;
388             }
389             }
390              
391 1         5 my $self = $pkg->_init($filename, $filemode);
392 1 50       4 return if (!$self);
393              
394 1         3 $self->{fields} = [@columns];
395 1         2 $self->{columns} = map {$_->{name}} @columns;
  6         10  
396 1         2 $self->{packformat} = 'C' . join q{}, map {'a'.$_->{length}} @columns;
  6         20  
397              
398 1 50       6 if (!$self->writeHeader())
399             {
400 0         0 return;
401             }
402              
403 1         4 return $self;
404             }
405             #----------------
406              
407             =item $pkg_or_self->validateColumns($column, $column, ...)
408              
409             =item $self->validateColumns()
410              
411             Check an array of DBF columns structures for validity. Emits warnings
412             and returns undef on failure.
413              
414             =cut
415              
416             sub validateColumns
417             {
418 0     0 1 0 my $pkg_or_self = shift;
419 0         0 my @columns = @_;
420              
421 0 0 0     0 if (@columns == 0 && ref $pkg_or_self)
422             {
423 0         0 my $self = $pkg_or_self;
424 0         0 @columns = @{$self->{fields}};
  0         0  
425             }
426              
427 0         0 my $n_columns = 0; # used solely for error messages
428 0         0 my %col_names; # used to detect duplicate column names
429 0         0 for my $column (@columns)
430             {
431 0         0 $n_columns++;
432 0 0 0     0 if (!$column || (!ref $column) || 'HASH' ne ref $column)
      0        
433             {
434 0         0 carp "Column $n_columns is not a hash reference";
435 0         0 return;
436             }
437 0         0 for my $key ('name', 'type', 'length', 'decimals')
438             {
439 0 0 0     0 if (!defined $column->{$key} || $column->{$key} =~ m/\A\s*\z/xms)
440             {
441 0         0 carp "No $key field in column $n_columns";
442 0         0 return;
443             }
444             }
445 0 0       0 if (11 < length $column->{name})
446             {
447 0         0 carp "Column name '$column->{name}' is too long (max 11 characters)";
448 0         0 return;
449             }
450 0 0       0 if ($col_names{$column->{name}}++)
451             {
452 0         0 carp "Duplicate column name '$column->{name}'";
453 0         0 return;
454             }
455 0 0       0 if ($column->{type} !~ m/\A[CNDL]\z/xms)
456             {
457 0         0 carp "Unknown column type '$column->{type}'";
458 0         0 return;
459             }
460 0 0       0 if ($column->{length} !~ m/\A\d+\z/xms)
461             {
462 0         0 carp "Column length must be an integer ('$column->{length}')";
463 0         0 return;
464             }
465 0 0       0 if ($column->{decimals} !~ m/\A\d+\z/xms)
466             {
467 0         0 carp "Column decimals must be an integer ('$column->{decimals}')";
468 0         0 return;
469             }
470 0 0 0     0 if ($column->{type} eq 'L' && $column->{length} != 1)
471             {
472 0         0 carp 'Columns of type L (logical) must have length 1';
473 0         0 return;
474             }
475 0 0 0     0 if ($column->{type} eq 'D' && $column->{length} != 8)
476             {
477 0         0 carp 'Columns of type D (date) must have length 8';
478 0         0 return;
479             }
480             }
481 0         0 return $pkg_or_self;
482             }
483             #----------------
484              
485             =back
486              
487             =head1 INSTANCE METHODS
488              
489             =over 4
490              
491             =cut
492              
493             #----------------
494              
495             =item $self->writeHeader()
496              
497             Write all of the DBF header data to the file. This truncates the file first.
498              
499             =cut
500              
501             sub writeHeader
502             {
503 1     1 1 1 my $self = shift;
504              
505 1         2 my $file_handle = $self->{fh};
506 1         2 my $fields = q{};
507 1         3 $self->{nrecordbytes} = 1; # allow one for the delete byte
508              
509 1         1 for my $column (@{$self->{fields}})
  1         3  
510             {
511 6         8 $self->{nrecordbytes} += $column->{length};
512 6         30 $fields .= pack 'a11a1CCCCCCCCCCCCCCCCCCCC',
513             $column->{name}, $column->{type}, (0) x 4,
514             $column->{length}, $column->{decimals}, (0) x 14;
515             }
516 1         3 $fields .= pack 'C', 0x0D;
517              
518 1         9 my $header
519             = pack 'CCCCVvvCCCCCCCCCCCCCCCCCCCC',
520             $self->{valid},
521             $self->{year}%100, $self->{month}, $self->{date},
522             $self->{nrecords}, length($fields)+32,
523             $self->{nrecordbytes}, (0)x20;
524              
525 1         25 truncate $file_handle, 0;
526 1         2 print {$file_handle} $header;
  1         13  
527 1         2 print {$file_handle} $fields;
  1         2  
528 1         4 return $self;
529             }
530             #----------------
531              
532             =item $self->appendrow_arrayref($data_arrayref)
533              
534             Add a new row to the end of the DBF file immediately. The argument
535             is treated as a reference of fields, in order. The DBF file is altered
536             as little as possible.
537              
538             The record count is incremented but is NOT written to the file until
539             the C method is called (for speed increase).
540              
541             =cut
542              
543             sub appendrow_arrayref
544             {
545 3777     3777 1 20075 my $self = shift;
546 3777         4624 my $row = shift;
547              
548 3777         8699 $self->appendrows_arrayref([$row]);
549 3777         12064 return;
550             }
551             #----------------
552              
553             =item $self->appendrows_arrayref($arrayref_data_arrayrefs)
554              
555             Add new rows to the end of the DBF file immediately. The argument
556             is treated as a reference of references of fields, in order. The DBF
557             file is altered as little as possible. The record count is incremented
558             but is NOT written until the C method is called (for speed increase).
559              
560             =cut
561              
562             sub appendrows_arrayref
563             {
564 7554     7554 1 11388 my $self = shift;
565 7554         7946 my $rows = shift;
566              
567 7554         10074 my $file_handle = $self->{fh};
568 7554         137564 seek $file_handle, 0, 2;
569              
570 7554         9247 for my $row (@{$rows})
  7554         14837  
571             {
572 7554 50       17819 if (defined $row)
573             {
574 7554         9316 $self->{nrecords}++;
575 7554         6923 print {$file_handle} $self->_packArrayRef($row);
  7554         17942  
576             }
577             }
578              
579 7554         12479 delete $self->{rowcache}; # wipe cache, just in case
580 7554         11567 return;
581             }
582             #----------------
583              
584             =item $self->appendrow_hashref($data_hashref)
585              
586             Just like C, except the incoming data is in a
587             hash. The DBF columns are used to reorder the data. Missing values
588             are converted to blanks.
589              
590             =cut
591              
592             sub appendrow_hashref
593             {
594 3777     3777 1 27305 my $self = shift;
595 3777         4284 my $row = shift;
596              
597 3777         8577 $self->appendrows_hashref([$row]);
598 3777         13013 return;
599             }
600             #----------------
601              
602             =item $self->appendrows_hashref($arrayref_data_hashref)
603              
604             Just like C, except the incoming data is in a
605             hash. The DBF columns are used to reorder the data. Missing values
606             are converted to blanks.
607              
608             =cut
609              
610             sub appendrows_hashref
611             {
612 3777     3777 1 3792 my $self = shift;
613 3777         3551 my $hashrows = shift;
614              
615             # Convert hashes to arrays
616 3777         4131 my @column_names = map {$_->{name}} @{$self->{fields}};
  22662         41227  
  3777         7099  
617 3777         5165 my @arrayrows;
618 3777         3874 for my $row (@{$hashrows})
  3777         6318  
619             {
620 3777         5011 push @arrayrows, [map {$row->{$_}} @column_names];
  22662         49281  
621             }
622              
623 3777         8770 $self->appendrows_arrayref(\@arrayrows);
624 3777         9593 return;
625             }
626             #----------------
627              
628             sub _packArrayRef
629             {
630 7554     7554   9371 my $self = shift;
631 7554         8442 my $A_row = shift;
632            
633 7554 50       14467 die 'Bad row' if (!$A_row);
634              
635 7554         8535 my $row = q{ }; # start with an undeleted flag
636 7554         7638 for my $i (0 .. @{$self->{fields}}-1)
  7554         18267  
637             {
638 45324         73959 my $column = $self->{fields}->[$i];
639 45324         56707 my $v = $A_row->[$i];
640              
641 45324 50       75346 if (defined $v)
642             {
643 45324         63573 $v = "$v"; # stringify
644             }
645             else
646             {
647 0         0 $v = q{};
648             }
649              
650 45324         46467 my $l = length $v;
651 45324 100       124201 if ($column->{type} eq 'N') ##no critic(ProhibitCascadingIfElse)
    100          
    100          
    50          
652             {
653 15108 50       48933 if ($v =~ m/\d/xms)
654             {
655 15108         91594 $v = sprintf "%$column->{length}.$column->{decimals}f", $v;
656             }
657             else
658             {
659 0         0 $v = q{ } x $column->{length};
660             }
661             }
662             elsif ($column->{type} eq 'C')
663             {
664 15108         38599 $v = sprintf "%-$column->{length}s", $v;
665             }
666             elsif ($column->{type} eq 'L')
667             {
668 7554 50 33     41932 $v = !$v || $v =~ m/[nNfF]/xms ? 'F' : 'T';
669             }
670             elsif ($column->{type} eq 'D')
671             {
672             # pass on OK
673             }
674             else
675             {
676 0         0 die "Unknown type $column->{type}";
677             }
678              
679 45324 50       100016 if ($l > $column->{length})
680             {
681 0         0 $v = substr $v, 0, $column->{length};
682             }
683 45324         110508 $row .= $v;
684             }
685 7554         34387 return $row;
686             }
687             #----------------
688              
689             =item $self->closeDB()
690              
691             Closes a DBF file after updating the record count.
692             This is only necessary if you append new rows.
693              
694             =cut
695              
696             sub closeDB
697             {
698 1     1 1 3 my $self = shift;
699              
700 1         4 $self->writeRecordNumber();
701 1         22 $self->{fh}->close();
702 1         78 return $self;
703             }
704             #----------------
705              
706             =item $self->writeRecordNumber()
707              
708             Edits the DBF file to record the current value of nrecords(). This is
709             useful after appending rows.
710              
711             =cut
712              
713             sub writeRecordNumber
714             {
715 1     1 1 3 my $self = shift;
716              
717 1         2 my $file_handle = $self->{fh};
718 1         21 seek $file_handle, 4, 0;
719 1         2 print {$file_handle} pack 'V', $self->{nrecords};
  1         8  
720 1         2 return $self;
721             }
722             #----------------
723              
724             sub _readrow
725             {
726 15108     15108   22325 my $self = shift;
727 15108         14994 my $rownum = shift;
728              
729 15108 50 100     100817 if ($ROWCACHE == 0)
    100 100        
730             {
731 0         0 my $A_rows = $self->_readrows($rownum, 1);
732 0 0       0 return $A_rows ? $A_rows->[0] : undef;
733             }
734             elsif ($self->{rowcache} && $rownum < $self->{rowcache2} && $rownum >= $self->{rowcache1})
735             {
736 14956         42799 return $self->{rowcache}->[$rownum - $self->{rowcache1}];
737             }
738             else
739             {
740 152         250 my $num = $ROWCACHE;
741 152 100       443 if ($rownum + $num >= $self->{nrecords})
742             {
743 2         6 $num = $self->{nrecords} - $rownum;
744             }
745 152         590 $self->{rowcache} = $self->_readrows($rownum, $num);
746 152         11240 $self->{rowcache1} = $rownum;
747 152         307 $self->{rowcache2} = $rownum + $num;
748              
749 152         799 return $self->{rowcache}->[0];
750             }
751             }
752             #----------------
753              
754             sub _readrows
755             {
756 152     152   254 my $self = shift;
757 152         409 my $row_start = shift;
758 152         239 my $row_count = shift;
759              
760 152         205 my @data_rows;
761              
762 152         586 my $offset = $self->{nheaderbytes} + $row_start * $self->{nrecordbytes};
763 152         2194 seek $self->{fh}, $offset, 0;
764              
765 152         483 for (my $r=1; $r<=$row_count; $r++)
766             {
767 15108         14903 my $datarow;
768 15108         40978 read $self->{fh}, $datarow, $self->{nrecordbytes};
769 15108         88371 my @records = unpack $self->{packformat}, $datarow;
770 15108         23834 my $delete = shift @records;
771 15108 50       37593 if ($delete != 32) # 32 is decimal ascii for " "
772             {
773             # This is a deleted row
774 0         0 push @data_rows, undef;
775 0         0 next;
776             }
777              
778 15108         15018 my $col = 0;
779 15108         22081 for (@records)
780             {
781 90648         163823 my $type = $self->{fields}->[$col++]->{type};
782 90648 100       208057 if ($type eq 'C')
    100          
    100          
783             {
784 30216         125870 s/[ ]*\z//xms;
785             }
786             elsif ($type eq 'N')
787             {
788 30216         103183 s/\A[ ]*//xms;
789             }
790             elsif ($type eq 'L')
791             {
792 15108         35431 tr/yYtTnNfF?/111100000/;
793             }
794             }
795 15108         57588 push @data_rows, \@records;
796             }
797              
798 152         594 return \@data_rows;
799             }
800             #----------------
801              
802             =item $self->nfields()
803              
804             Return the number of columns in the data table.
805              
806             =cut
807              
808             sub nfields
809             {
810 0     0 1 0 my $self = shift;
811              
812 0         0 return scalar @{$self->{fields}};
  0         0  
813             }
814             #----------------
815              
816             =item $self->fieldnames()
817              
818             Return a list of field header names.
819              
820             =cut
821              
822             sub fieldnames
823             {
824 0     0 1 0 my $self = shift;
825              
826 0         0 return @{$self->{columns}};
  0         0  
827             }
828              
829             # Retrieve header metadata for the column spcified by name or number
830             sub _getfield
831             {
832 0     0   0 my $self = shift;
833 0         0 my $col = shift;
834              
835 0 0       0 if ($col =~ m/\D/xms)
836             {
837 0         0 for my $field (@{$self->{fields}})
  0         0  
838             {
839 0 0       0 return $field if ($field->{name} eq $col);
840             }
841 0         0 return;
842             }
843             else
844             {
845 0         0 return $self->{fields}->[$col];
846             }
847             }
848             #----------------
849              
850             =item $self->fieldname($column)
851              
852             Return a the title of the specified column. C<$column> can be a column
853             name or number. Column numbers count from zero.
854              
855             =cut
856              
857             sub fieldname
858             {
859 0     0 1 0 my $self = shift;
860 0         0 my $col = shift;
861              
862 0         0 my $field = $self->_getfield($col);
863 0 0       0 return if (!$field);
864 0         0 return $field->{name};
865             }
866             #----------------
867              
868             =item $self->fieldtype($column)
869              
870             Return the dBASE field type for the specified column. C<$column> can be a
871             column name or number. Column numbers count from zero.
872              
873             =cut
874              
875             sub fieldtype
876             {
877 0     0 1 0 my $self = shift;
878 0         0 my $col = shift;
879              
880 0         0 my $field = $self->_getfield($col);
881 0 0       0 return if (!$field);
882 0         0 return $field->{type};
883             }
884             #----------------
885              
886             =item $self->fieldlength($column)
887              
888             Return the byte width for the specified column. C<$column> can be a
889             column name or number. Column numbers count from zero.
890              
891             =cut
892              
893             sub fieldlength
894             {
895 0     0 1 0 my $self = shift;
896 0         0 my $col = shift;
897              
898 0         0 my $field = $self->_getfield($col);
899 0 0       0 return if (!$field);
900 0         0 return $field->{length};
901             }
902             #----------------
903              
904             =item $self->fielddecimals($column)
905              
906             Return the decimals for the specified column. C<$column> can be a column
907             name or number. Column numbers count from zero.
908              
909             =cut
910              
911             sub fielddecimals
912             {
913 0     0 1 0 my $self = shift;
914 0         0 my $col = shift;
915              
916 0         0 my $field = $self->_getfield($col);
917 0 0       0 return if (!$field);
918 0         0 return $field->{decimals};
919             }
920             #----------------
921              
922             =item $self->nrecords()
923              
924             Return number of records in the file.
925              
926             =cut
927              
928             sub nrecords
929             {
930 7     7 1 9357 my $self = shift;
931              
932 7         79 return $self->{nrecords};
933             }
934             #----------------
935              
936             =item $self->fetchrow_arrayref($rownumber)
937              
938             Return a record as a reference to an array of fields. Row numbers
939             count from zero.
940              
941             =cut
942              
943             sub fetchrow_arrayref
944             {
945 15108     15108 1 55789 my $self = shift;
946 15108         15453 my $rownum = shift;
947              
948 15108 50 33     65179 if ($rownum < 0 || $rownum >= $self->{nrecords})
949             {
950 0         0 carp "Invalid DBF row: $rownum";
951 0         0 return;
952             }
953              
954 15108         26564 return $self->_readrow($rownum);
955             }
956             #----------------
957              
958             =item $self->fetchrows_arrayref($rownumber, $count)
959              
960             Return array reference of records as a reference to an array of fields.
961             Row numbers start from zero and count is trimmed if it exceeds table
962             limits.
963              
964             =cut
965              
966             sub fetchrows_arrayref
967             {
968 0     0 1 0 my $self = shift;
969 0         0 my $row_start = shift;
970 0         0 my $row_count = shift;
971              
972 0 0       0 if ($row_start + $row_count > $self->{nrecords})
973             {
974 0         0 $row_count = $self->{nrecords} - $row_start;
975             }
976              
977 0 0 0     0 if ($row_start < 0 || $row_start >= $self->{nrecords})
978             {
979 0 0       0 if ($row_start >= $self->{nrecords})
980             {
981 0         0 carp "Invalid DBF row: $row_start";
982             }
983 0         0 return;
984             }
985              
986 0         0 return $self->_readrows($row_start, $row_count);
987             }
988             #----------------
989              
990             =item $self->fetchrow_hashref($rownum)
991              
992             Return a record as a reference to a hash of C<(field name => field value)>
993             pairs. Row numbers count from zero.
994              
995             =cut
996              
997             sub fetchrow_hashref
998             {
999 7554     7554 1 54959 my $self = shift;
1000 7554         8455 my $rownum = shift;
1001              
1002 7554         13336 my $ref = $self->fetchrow_arrayref($rownum);
1003 7554 50       16495 return if (!$ref);
1004 7554         7768 my %hash;
1005 7554         7564 for my $col (0 .. $#{$ref})
  7554         17430  
1006             {
1007 45324         120295 $hash{$self->{columns}->[$col]} = $ref->[$col];
1008             }
1009 7554         22640 return \%hash;
1010             }
1011             #----------------
1012              
1013             =item $self->fetchrow_array($rownum)
1014              
1015             Return a record as an array of fields. Row numbers count from zero.
1016              
1017             =cut
1018              
1019             sub fetchrow_array
1020             {
1021 0     0 1   my $self = shift;
1022 0           my $rownum = shift;
1023              
1024 0           my $ref = $self->fetchrow_arrayref($rownum);
1025 0 0         return if (!$ref);
1026 0           return @{$ref};
  0            
1027             }
1028             #----------------
1029              
1030             =item $self->delete($rownum);
1031              
1032             Flags a row as deleted. This alters the DBF file immediately.
1033              
1034             =cut
1035              
1036             sub delete ##no critic(ProhibitBuiltinHomonyms)
1037             {
1038 0     0 1   my $self = shift;
1039 0           my $rownum = shift;
1040              
1041 0           return $self->_delete($rownum, q{*});
1042             }
1043             #----------------
1044              
1045             =item $self->undelete($rownum)
1046              
1047             Removes the deleted flag from a row. This alters the DBF file
1048             immediately.
1049              
1050             =cut
1051              
1052             sub undelete
1053             {
1054 0     0 1   my $self = shift;
1055 0           my $rownum = shift;
1056              
1057 0           return $self->_delete($rownum, q{ });
1058             }
1059              
1060             ## Internal method only. Use wrappers above.
1061             sub _delete
1062             {
1063 0     0     my $self = shift;
1064 0           my $rownum = shift;
1065 0           my $flag = shift;
1066              
1067 0 0         return if (!$rownum);
1068 0 0 0       return if ($rownum < 0 || $rownum >= $self->{nrecords});
1069              
1070 0           $self->{fh}->close();
1071 0           $self->{fh} = undef;
1072            
1073 0           my $fh;
1074             my $result;
1075 0 0         if (open $fh, '+<', $self->{filename})
1076             {
1077 0           binmode $fh;
1078 0           my $offset = $self->{nheaderbytes} + $rownum * $self->{nrecordbytes};
1079 0           seek $fh, $offset, 0;
1080 0           print {$fh} $flag;
  0            
1081 0           close $fh;
1082 0           $result = 1;
1083             }
1084              
1085             # Reopen main filehandle
1086 0           $self->_open_fh();
1087              
1088 0           delete $self->{rowcache}; # wipe cache, just in case
1089 0 0         return $result ? $self : ();
1090             }
1091             #----------------
1092              
1093             =item $self->toText([$startrow,] [$endrow,] [C<-arg> => $value, ...])
1094              
1095             Return the contents of the file in an ASCII character-separated
1096             representation. Possible arguments (with default values) are:
1097              
1098             -field => ','
1099             -enclose => '"'
1100             -escape => '\'
1101             -record => '\n'
1102             -showheader => 0
1103             -startrow => 0
1104             -endrow => nrecords()-1
1105              
1106             Alternatively, if the C<-arg> switches are not used, the first two
1107             arguments are interpreted as:
1108              
1109             $dbf->toText($startrow, $endrow)
1110              
1111             Additional C<-arg> switches are permitted after these. For example:
1112              
1113             print $dbf->toText(100, 100, -field => '\n', -record => '');
1114             print $dbf->toText(300, -field => '|');
1115              
1116             =cut
1117              
1118             sub toText
1119             {
1120 0     0 1   my $self = shift;
1121              
1122 0           my %args = (
1123             field => q{,},
1124             enclose => q{'},
1125             escape => q{\\},
1126             record => "\n",
1127             showheader => 0,
1128             startrow => 0,
1129             endrow => $self->nrecords()-1,
1130             );
1131              
1132 0           for my $arg (qw(startrow endrow))
1133             {
1134 0 0 0       if (@_ > 0 && $_[0] !~ m/\A\-/xms)
1135             {
1136 0           $args{$arg} = shift;
1137             }
1138             }
1139              
1140 0           while (@_ > 0)
1141             {
1142 0           my $key = shift;
1143 0 0 0       if ($key =~ m/\A\-(\w+)\z/xms && exists $args{$1} && @_ > 0)
      0        
1144             {
1145 0           $args{$1} = shift;
1146             }
1147             else
1148             {
1149 0           carp "Unexpected tag '$key' in argument list";
1150 0           return;
1151             }
1152             }
1153              
1154 0 0 0       if ($args{startrow} < 0 || $args{endrow} >= $self->nrecords())
1155             {
1156 0           carp 'Invalid start and/or end row';
1157 0           return;
1158             }
1159 0 0         return if ($args{startrow} > $args{endrow});
1160              
1161 0           my $out = q{};
1162 0 0         if ($args{showheader}) {
1163 0 0 0       my @names = map {$args{enclose} eq q{} && $args{escape} eq q{} ?
  0            
1164             $_ : _escape($_, $args{enclose}, $args{escape})} $self->fieldnames();
1165 0           $out .= join $args{field}, @names;
1166 0           $out .= $args{record};
1167             }
1168 0           for (my $row = $args{startrow}; $row <= $args{endrow}; $row++)
1169             {
1170 0           my $aref = $self->_readrow($row);
1171 0 0         next if (!$aref);
1172 0 0 0       if ($args{enclose} ne q{} || $args{escape} ne q{})
1173             {
1174 0           for (@{$aref})
  0            
1175             {
1176 0           $_ = _escape($_, $args{enclose}, $args{escape});
1177             }
1178             }
1179 0           $out .= join($args{field}, @{$aref}) . $args{record};
  0            
1180             }
1181 0           return $out;
1182             }
1183             #----------------
1184              
1185             =item $self->computeRecordBytes()
1186              
1187             Useful primarily for debugging. Recompute the number of bytes needed
1188             to store a record.
1189              
1190             =cut
1191              
1192             sub computeRecordBytes
1193             {
1194 0     0 1   my $self = shift;
1195              
1196 0           my $length = 1;
1197 0           for my $column (@{$self->{fields}})
  0            
1198             {
1199 0           $length += $column->{length};
1200             }
1201 0           return $length;
1202             }
1203             #----------------
1204              
1205             =item $self->computeHeaderBytes()
1206              
1207             Useful primarily for debugging. Recompute the number of bytes needed
1208             to store the header.
1209              
1210             =cut
1211              
1212             sub computeHeaderBytes
1213             {
1214 0     0 1   my $self = shift;
1215              
1216 0           my $fh = $self->{fh};
1217 0           my $length = 0;
1218 0           my ($buffer, $value);
1219             do
1220 0   0       {
      0        
1221 0           $length += 32;
1222 0           seek $fh, $length, 0;
1223 0           read $fh, $buffer, 1;
1224 0           $value = unpack 'C', $buffer;
1225             }
1226             while (defined $buffer && $value != 0x0D && $value != 0x0A); ##no critic(ProhibitPostfixControls)
1227 0           return $length + 1; # Add one for the terminator character
1228             }
1229             #----------------
1230              
1231             =item $self->computeNumRecords()
1232              
1233             Useful primarily for debugging. Recompute the number of records in
1234             the file, given the header size, file size and bytes needed to store a
1235             record.
1236              
1237             =cut
1238              
1239             sub computeNumRecords
1240             {
1241 0     0 1   my $self = shift;
1242              
1243 0           my $size = -s $self->{filename};
1244 0           my $num = ($size - $self->nHeaderBytes()) / $self->nRecordBytes();
1245 0           return int $num
1246             }
1247             #----------------
1248              
1249             =item $self->nHeaderBytes()
1250              
1251             Useful primarily for debugging. Returns the number of bytes for the
1252             file header. This date is read from the header itself, not computed.
1253              
1254             =cut
1255              
1256             sub nHeaderBytes
1257             {
1258 0     0 1   my $self = shift;
1259 0           return $self->{nheaderbytes};
1260             }
1261             #----------------
1262              
1263             =item $self->nRecordBytes()
1264              
1265             Useful primarily for debugging. Returns the number of bytes for a
1266             record. This date is read from the header itself, not computed.
1267              
1268             =cut
1269              
1270             sub nRecordBytes
1271             {
1272 0     0 1   my $self = shift;
1273 0           return $self->{nrecordbytes};
1274             }
1275             #----------------
1276              
1277             =item $self->repairHeaderData()
1278              
1279             Test and fix corruption of the C and C header
1280             fields. This does NOT alter the file, just the in-memory
1281             representation of the header metadata. Returns a boolean indicating
1282             whether header repairs were necessary.
1283              
1284             =cut
1285              
1286             sub repairHeaderData
1287             {
1288 0     0 1   my $self = shift;
1289              
1290 0           my $repairs = 0;
1291              
1292 0           my $row_size = $self->computeRecordBytes();
1293 0 0         if ($self->nRecordBytes() != $row_size)
1294             {
1295 0           $repairs++;
1296 0           $self->{nrecordbytes} = $row_size;
1297             }
1298              
1299 0           my $n_records = $self->computeNumRecords();
1300 0 0         if ($n_records != $self->nrecords())
1301             {
1302 0           $repairs++;
1303 0           $self->{nrecords} = $n_records;
1304             }
1305              
1306 0           return $repairs;
1307             }
1308             #----------------
1309              
1310             # Internal function
1311             sub _escape
1312             {
1313 0     0     my $string = shift;
1314 0           my $enclose = shift;
1315 0           my $escape = shift;
1316              
1317 0 0         if ($escape ne q{})
1318             {
1319 0           $string =~ s/\Q$escape\E/$escape$escape/gxms;
1320 0 0         if ($enclose ne q{})
1321             {
1322 0           $string =~ s/\Q$enclose\E/$escape$enclose/gxms;
1323             }
1324             }
1325 0           return $enclose . $string . $enclose;
1326             }
1327              
1328             1;
1329             __END__