File Coverage

blib/lib/BTRIEVE/SAVE.pm
Criterion Covered Total %
statement 252 337 74.7
branch 46 96 47.9
condition 9 25 36.0
subroutine 21 28 75.0
pod 12 12 100.0
total 340 498 68.2


line stmt bran cond sub pod time code
1             package BTRIEVE::SAVE;
2              
3 1     1   7549 use Carp;
  1         80  
  1         131  
4 1     1   9 use strict;
  1         4  
  1         49  
5 1         6981 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $DEBUG $TEST
6 1     1   5 );
  1         7  
7             $VERSION = '0.35';
8             $DEBUG = 0;
9              
10             require Exporter;
11             require 5.004;
12              
13             @ISA = qw(Exporter);
14             @EXPORT= qw();
15             @EXPORT_OK= qw();
16              
17              
18             # Preloaded methods go here.
19              
20             ####################################################################
21              
22             # This is the constructor method that creates the BTRIEVE object.
23             # It will attempt to set up info from the config file.
24              
25             ####################################################################
26             sub new {
27 2     2 1 625 my $proto = shift;
28 2   33     17 my $class = ref($proto) || $proto;
29 2         4 my $config_file= shift ;
30 2   100     8 my $file = shift ||undef;
31              
32 2         10 my $save_btr = {opt=>{}, array=>[]};
33 2         6 $save_btr->{'opt'}{'config'}=$config_file;
34              
35 2         4 bless $save_btr, $class;
36 2 50       30 if (-e $config_file) {
37 2         10 $save_btr->config($config_file);
38             } else {
39 0         0 return $save_btr;
40             }
41 2 100       5 if (!$file) {return $save_btr};
  1         7  
42 1         4 $save_btr->{'opt'}{'file'}=$file;
43              
44              
45 1         4 $save_btr->_initbtrieve();
46 1         3 return $save_btr;
47             }
48              
49             ###################################################################
50             # _initbtrieve() sets up config and filehandle
51             ###################################################################
52             sub _initbtrieve {
53 1     1   2 my $save_btr = shift;
54 1         3 my $config_file = $save_btr->{'opt'}{'config'};
55 1         3 $save_btr->{'opt'}{'increment'} = -1;
56              
57 1         2 my $file = $save_btr->{'opt'}{'file'};
58 1 50       20 if (not(-e $file)) {carp "File \"$file\" doesn't exist"; return}
  0         0  
  0         0  
59 1         33 open (*file, $file);
60 1         3 binmode *file;
61 1         4 $save_btr->{'opt'}{'handle'}=\*file; #store filehandle in object
62             }
63              
64             ###################################################################
65             # config() looks for a config file which tells us where the
66             # offsets are in the fixed part of the record, their types and
67             # what to call them locally.
68             ###################################################################
69              
70             sub config {
71 2     2 1 5 my $save_btr = shift;
72 2         20 my $proto_rec = BTRIEVE::SAVE::REC->newconfig($save_btr->{'opt'}{'config'});
73 2         8 $save_btr->{'opt'}{'proto_rec'} = $proto_rec;
74             }
75              
76             ###################################################################
77             # parse_file() reads from a BTRIEVE SAVE file. Can do so
78             # incrementally.
79             ###################################################################
80             sub parse_file {
81 1     1 1 4 my $save_btr = shift;
82 1         3 my $increment = $save_btr->{'opt'}{'increment'}; #pick out increment from the object
83 1         2 my $recordcount = 0;
84              
85 1   33     6 while ($increment==-1 or $recordcount<$increment) {
86 3         10 my $curr_rec = $save_btr->next_rec;
87 3 100       6 last unless $curr_rec;
88 2         3 push @{$save_btr->{'array'}},$curr_rec;
  2         5  
89 2         5 $recordcount++;
90             } #end reading this record
91 1         2 return $recordcount;
92             }
93              
94             ####################################################################
95              
96             # Returns a new BTRIEVE::SAVE::REC based on the next bits.
97             # Returns undef if we have reached the end.
98              
99             ####################################################################
100             sub next_rec {
101 3     3 1 4 my $save_btr=shift;
102 3         9 my ($rec,$eor) = $save_btr->next_recbits;
103 3 100       12 return undef if $eor eq "\cZ";
104 2 50       6 return undef unless defined($rec);
105 2         4 my $proto_rec = $save_btr->{'opt'}{'proto_rec'};
106 2         6 my $curr_rec = $proto_rec->copy_struct();
107              
108 2         9 $curr_rec->parse_string($rec);
109 2         7 return $curr_rec;
110             }
111              
112             ####################################################################
113              
114             # Reads thru the handle looking for the bits forming the $rec
115             # and for the bits that should be $eor (end-of-record).
116             # Returns ($rec,$eor). $eor is undef if the read is at EOF.
117             # $eor is undef if we are at the DOS EOF ("\cZ") at the
118             # appropriate defined place.
119              
120             ####################################################################
121             sub next_recbits {
122 3     3 1 4 my $save_btr = shift;
123 3         7 my $handle = $save_btr->{'opt'}{'handle'};
124             #need to use read to get the right bytes. Bummer.
125 3         4 my $pos= tell($handle);
126 3         4 my $info="";
127 3         23 my $rc= read($handle,$info,14); #assumes that no record is more than 10 gigabytes
128 3 50       7 return (undef,undef) unless $rc; #EOF...
129 3 100       13 return (undef,"\cZ") if $info=~/^\cZ/;
130 2         8 my ($length)= $info=~/^(\d+)[, ]/; #definition of btrieve save file format.
131             #error check here to see if $length is defined.
132 2         20 seek($handle,$pos,0); #go back to where we were.
133 2         4 my $rec="";
134 2         20 $rc= read($handle,$rec,$length +length($length)+1); #definition of btrieve save file format.
135 2         7 $rec = substr($rec,length($length)+1); #kills the \d+[, ], keeps the rest.
136 2         5 my $eor="";
137 2         4 $rc = read($handle,$eor,2); #skip over \r\n, or find \cZ
138 2         48 return ($rec,$eor);
139             }
140             ####################################################################
141             # openbtr() reads in a BTRIEVE SAVE file. It takes a hashref
142             # with key 'file' (name of the btrieve file). Increment
143             # defines how many records to read in and is taken from the object.
144             ####################################################################
145             sub openbtr {
146 0     0 1 0 my $save_btr=shift;
147 0         0 my $params=shift;
148 0         0 my $file=$params->{'file'};
149 0 0       0 if (not(-e $file)) {carp "File \"$file\" doesn't exist"; return}
  0         0  
  0         0  
150 0         0 my $totalrecord;
151 0   0     0 $save_btr->{'opt'}{'increment'}
      0        
152             ||= $params->{'increment'}
153             ||= 1;
154             #store increment in the object, default is 1
155 0         0 open (*file, $file);
156 0         0 binmode *file;
157 0         0 $save_btr->{'opt'}{'handle'}=\*file; #store filehandle in object
158            
159 0 0       0 print "read in $totalrecord records\n" if $DEBUG;
160 0 0       0 if ($totalrecord==0) {$totalrecord="0 but true"}
  0         0  
161 0         0 return $totalrecord;
162             }
163              
164             ####################################################################
165             # closebtr() will close a file-handle that was opened with
166             # openbtr()
167             ####################################################################
168             sub closebtr {
169 0     0 1 0 my $marc = shift;
170 0         0 $marc->{'opt'}{'increment'}=0;
171 0 0       0 if (not($marc->{'opt'}{'handle'})) {carp "There isn't a BTRIEVE SAVE file to close"; return}
  0         0  
  0         0  
172 0         0 my $ok = close $marc->{'opt'}{'handle'};
173 0         0 $marc->{'opt'}{'handle'}=undef;
174 0         0 return $ok;
175             }
176              
177             ####################################################################
178             # nextbtr() will read in more records from a file that has
179             # already been opened with openbtr(). the increment can be
180             # adjusted if necessary by passing a new value as a parameter. the
181             # new records will be APPENDED to the BTRIEVE object
182             ####################################################################
183             sub nextbtr {
184 0     0 1 0 my $save_btr=shift;
185 0         0 my $increment=shift;
186 0         0 my $totalrecord;
187 0 0       0 if (not($save_btr->{'opt'}{'handle'})) {carp "There isn't a BTRIEVE SAVE file open"; return}
  0         0  
  0         0  
188 0 0       0 if ($increment) {$save_btr->{'opt'}{'increment'}=$increment}
  0         0  
189              
190 0         0 $totalrecord = $save_btr->parse_file();
191            
192 0         0 return $totalrecord;
193             }
194            
195             ####################################################################
196             # output() actually writes the file with a string version of
197             # $save_btr unless no file is given, in which case it returns the string
198             ####################################################################
199             sub output {
200 0     0 1 0 my $save_btr=shift;
201 0         0 my $output = "";
202 0         0 my $outfile = shift;
203              
204              
205 0         0 $output = $save_btr->as_string();
206 0 0       0 if ($outfile) {
207 0 0       0 if ($outfile !~ /^>/) {carp "Don't forget to use > or >>: $!"}
  0         0  
208 0         0 local(*OUT);
209 0 0       0 open (OUT, "$outfile") || carp "Couldn't open file: $!";
210 0         0 binmode OUT;
211 0         0 print OUT $output;
212 0 0       0 close OUT || carp "Couldn't close file: $!";
213 0         0 return 1;
214             }
215             #if no filename was specified return the output so it can be grabbed
216             else {
217 0         0 return $output;
218             }
219             }
220              
221            
222             ####################################################################
223              
224             # as_string() returns a string version of $save_btr. Handles packing the
225             # easily updateable version of %fixed.
226              
227             ####################################################################
228             sub as_string {
229 0     0 1 0 my $output = "";
230 0         0 my $save_btr=shift;
231              
232 0         0 for (@{$save_btr->{'array'}}) {
  0         0  
233 0         0 my $data = $_->data;
234 0         0 my $packed_rec = $_->counted_rec($data);
235 0         0 $output .=$packed_rec;
236             }
237 0         0 $output .="\cZ";
238 0         0 return $output;
239             }
240              
241             ####################################################################
242              
243             # Takes an rdb filename, save filename, error file name, and config
244             # file name. Also takes the field name for unindexed fixed info and
245             # var info, and strings to translate to tab and newline. Writes
246             # an rdb file with that information; warns and writes to the error file
247             # if there are problems in the data.
248              
249             ####################################################################
250              
251             sub rdb_to_save {
252 1     1 1 6 my $save_btr = shift;
253 1         5 my ($rdb,$save,$errs,
254             $zzname,$varname,$tabtrans,$rettrans) = @_;
255              
256 1         2 local *RDB;
257 1         3 local *SAVE;
258 1         3 local *ERRS;
259              
260 1 50       37 open RDB,"$rdb" or die "Could not open $rdb:$!\n";
261 1         3 binmode RDB;
262 1 50       101 open SAVE,">$save" or die "Could not open $save:$!\n";
263 1         2 binmode SAVE;
264 1 50       60 open ERRS,">$errs" or die "Could not open $errs:$!\n";
265 1         2 binmode ERRS;
266              
267 1         22 my $fieldnames = ;
268 1         15 print ERRS $fieldnames;
269              
270 1         3 chomp $fieldnames;
271              
272 1         5 my @rdbnames = split(/\t/,$fieldnames);
273              
274 1         4 my $proto_rec = $save_btr->{opt}{proto_rec};
275              
276 1         2 my @names = @{$proto_rec->{opt}{names}};
  1         9  
277              
278 1         2 my %fieldlen = (); #Gonna use this for lookup.
279 1         3 my @fixed_defs = @{$proto_rec->{opt}{fixed_defs}};
  1         3  
280 1         2 for (@fixed_defs) {
281 3         11 $fieldlen{ $_->{name}} = $_->{len};
282             };
283              
284 1         4 my $dashline = ;
285 1         2 print ERRS $dashline;
286              
287              
288             REC:
289 1         5 while () {
290 2         4 chomp;
291 2 50       10 next unless /\S/;
292 2         9 my @fields= split(/\t/);
293              
294 2         2 my $var;
295             my $rhfixed;
296 2         6 my @fieldnames = @rdbnames;
297 2 50       6 if ($#fields != $#rdbnames) {
298 0         0 warn "Paranoia 1: Number of fields do not match rdb spec at line $..\n";
299 0         0 print ERRS $_;
300 0         0 next REC;
301             }
302 2         3 my $err_skip =0;
303             FIELD:
304 2         6 while (@fields) {
305 8         10 my $field = shift @fields;
306 8         28 $field=~s/$tabtrans/\t/;
307 8         14 $field=~s/$rettrans/\n/;
308 8         13 my $name = shift @fieldnames;
309              
310 8 100       16 if ($name eq $zzname) {
311 2         4 $rhfixed->{'ZZ'} = $field;
312 2         12 next FIELD;
313             }
314 6 100       19 if ($name eq $varname) {
315 2         3 $var = $field;
316 2         11 next FIELD;
317             }
318 4 50       14 if ($fieldlen{$name} != length($field)) {
319 0         0 warn "Paranoia 2: Lengths do not match for $name at line $..\n";
320 0         0 print ERRS $_;
321 0         0 next REC;
322             }
323 4         12 $rhfixed->{$name}=$field;
324             }
325            
326 2         9 my $curr_rec = $proto_rec->copy_struct();
327 2         3 @{$curr_rec->{values}} = ($rhfixed,\"",\$var);
  2         7  
328 2         6 my $counted_rec = $curr_rec->counted_rec_hash();
329 2         55 print SAVE $counted_rec;
330             }
331            
332 1         3 print SAVE "\cZ";
333              
334 1 50       17 close RDB or carp "Could not close $rdb:$!\n";
335 1 50       57 close ERRS or carp "Could not close $errs:$!\n";
336 1 50       42 close SAVE or carp "Could not close $save:$!\n";
337             }
338              
339             ####################################################################
340              
341             # Takes an rdb filename, save filename, error file name, and config
342             # file name. Also takes the field name for unindexed fixed info and
343             # var info, and strings to translate to tab and newline. Writes
344             # a rdb file with that information; warns and writes to the error file
345             # if there are problems in the data.
346              
347             ####################################################################
348             sub save_to_rdb {
349 1     1 1 945 my $save_btr = shift;
350 1         8 my ($rdb,$save,$errs,
351             $zzname,$varname,$tabtrans,$rettrans) = @_;
352              
353 1         7 local *RDB;
354 1         3 local *ERRS;
355              
356 1         10 my $btr = BTRIEVE::SAVE->new($save_btr->{opt}{config},$save);
357 1         6 $btr->parse_file();
358 1         2 my $proto_rec = $btr->{'opt'}{'proto_rec'};
359 1         2 my @names = @{$proto_rec->{'opt'}{'names'}};
  1         3  
360 1         2 my @rdbnames = @names;
361 1 100       2 grep {$_ = $zzname if $_ eq 'ZZ'} @rdbnames;
  3         15  
362 1 50       105 open RDB,">$rdb" or die "Could not open $rdb for write: $!\n";
363 1         3 binmode RDB;
364 1 50       68 open ERRS,">$errs" or die "Could not open $errs for write: $!\n";
365 1         2 binmode ERRS;
366              
367              
368 1         3 foreach my $name (@names) {
369 3         11 print RDB "$name\t"; # in /rdb systems, deleting an extra column is trivial.
370             }
371 1         3 print RDB "$varname\n";
372            
373 1         3 for (@names) {
374 3         5 my $name =$_;
375 3         15 $name=~s/./-/g;
376 3         8 print RDB $name."\t";
377             }
378 1         3 my $dashvar = $varname;
379 1         4 $dashvar=~s/./-/g;
380 1         3 print RDB $dashvar."\n";
381              
382 1         3 REC:
383 1         2 foreach my $rec (@{$btr->{array}}) {
384 2         3 my $rdbline = "";
385 2         3 foreach my $name (@names) {
386 6         13 my $field = $rec->{values}[0]{$name};
387 6 50       56 if ($field=~/$tabtrans|$rettrans/) {
388 0         0 print ERRS $rec->counted_rec($rec->fixed.$rec->var);
389 0         0 next REC;
390             }
391 6         10 $field =~s/\t/$tabtrans/g;
392 6         7 $field =~s/\n/$rettrans/g;
393 6         13 $rdbline .= $field."\t";
394             }
395 2         8 my $var = $rec->var();
396 2         3 $var =~s/\t/$tabtrans/;
397 2         3 $var =~s/\n/$rettrans/;
398 2         4 $rdbline.= $var."\n";
399 2         5 print RDB $rdbline;
400             }
401 1 50       49 close RDB or carp "Could not close $rdb:$!\n";
402 1         6 print ERRS "\cZ";
403 1 50       57 close ERRS or carp "Could not close $errs:$!\n";;
404             }
405              
406             ####################################################################
407              
408             # BTRIEVE::SAVE::REC is responsible for internal representation of btrieve
409             # records. It knows enough to parse the %fixed information from
410             # a string and can generate string representations of data and
411             # counted string.
412              
413             ####################################################################
414              
415             package BTRIEVE::SAVE::REC;
416              
417 1     1   19 use vars qw( %TYPEMAP %TYPE_SCALE $VERSION);
  1         1  
  1         3627  
418             %TYPEMAP = (Zstring => 'a', Integer => 'V', RAW => 'a');
419             %TYPE_SCALE = (Zstring => 1, Integer => 0.25, RAW => 1 ); # Btrieve standard has byte counts.
420              
421             $VERSION = '0.35';
422             ####################################################################
423              
424             # This roughly specifies what real recs know: a template to pack and
425             # unpack strings and a list of names for %fixed keys. The arrayref
426             # stores [$rhfixed,$rfixed,$rrest] information.
427              
428             ####################################################################
429             sub new {
430 2     2   5 my $proto = shift;
431 2   33     11 my $class = ref($proto) || $proto;
432 2         5 my ($ranames,$rtemplate,$packed_length,$rhfixed_defs) = @_;
433 2         14 my $save_rec = {opt=>{names =>$ranames,template=>$rtemplate,
434             len=>$packed_length,fixed_defs=>$rhfixed_defs },
435             values=>[]};
436 2         16 return bless $save_rec,$class;
437             }
438              
439             sub newconfig {
440 2     2   5 my $proto = shift;
441 2   33     11 my $class = ref($proto) || $proto;
442 2         3 my $handle = shift;
443 2         12 local $/="\n";
444 2         4 local(*F);
445 2 50       82 open F,$handle or die "Could not open $handle:$!\n";
446 2         7 binmode *F;
447 2         4 my $fixed_length = 0;
448            
449 2         3 my $langname_preamble;
450             my $lang_len;
451 2         4 my $rhfixed_defs = [];
452 2         49 while () {
453 88 100       173 if (/langname/i) {
454 2         10 ($langname_preamble) =/^(.*)langname/i ;
455 2         5 $lang_len = length($langname_preamble) ;
456             }
457 88 100       164 ($fixed_length) = /(\d+)/ if /Record Length/;
458 88 100       306 if (/^\s+\d+/) {
459 4         18 my ($len,$type) = /^\s+\d+\s+\d+\s+\d+\s+(\d+)\s+(\S+)/o;
460 4 50       13 die "Type $type not understood" unless $TYPEMAP{$type};
461 4         41 my ($langname) = /^.{$lang_len}(.*)/o;
462 4         30 $langname=~s/\W//og;
463 4 50       9 die '"ZZ" is a reserved fieldname\n' if $langname eq "ZZ";
464 4         6 push @{$rhfixed_defs},{len=>$len,type=>$type,name=>$langname};
  4         29  
465             }
466             }
467             # We define the unmatched as "ZZ"
468              
469              
470              
471 2         4 my $template="";
472              
473 2         5 my $hashed_len = 0;
474 2         11 for (@{$rhfixed_defs}) {
  2         5  
475 4         21 $template.= $TYPEMAP{$_->{'type'}}.($_->{'len'}*$TYPE_SCALE{$_->{'type'}})." "; #works for ZString and Integer
476 4         18 $hashed_len += $_->{'len'};
477             }
478 2         6 my $ZZ_len = $fixed_length-$hashed_len;
479 2 50       9 die "Sum of field lengths exceeds fixed length by ".-$ZZ_len." bytes\n" if $ZZ_len < 0;
480 2         2 push @{$rhfixed_defs},{len=>$ZZ_len,type=>"RAW",name=>"ZZ"};
  2         9  
481 2         5 $template .= $TYPEMAP{'RAW'}.$ZZ_len;
482 2         4 my @names = map {$_->{'name'} } @{$rhfixed_defs};
  6         36  
  2         17  
483             # Templates with "a0" in them return empty strings, so ok
484             # to have $fixed_length= $hashed_len. Perl rules OK?
485              
486 2         6 $template=~s/(\D)1 /$1 /g;
487 2 50       27 close F or die "Could not close config file:$!\n";
488 2         14 return $class->new(\@names,$template,$fixed_length,$rhfixed_defs);
489             }
490              
491             ####################################################################
492              
493             # All_index returns true iff the structure of the record implies that
494             # there are no extra bytes in the fixed portion that are not indexed.
495              
496             ####################################################################
497             sub all_indexed {
498 0     0   0 my $save_rec = shift;
499 0         0 my $rhfixed_defs = $save_rec->{'opt'}{'fixed_defs'};
500 0         0 my $hashed_len = 0;
501 0         0 for (@{$rhfixed_defs}) {
  0         0  
502 0         0 $hashed_len += $_->{'len'};
503             }
504 0 0       0 return 1 if $hashed_len == $save_rec->{opt}{len};
505 0         0 return 0;
506             }
507              
508             ####################################################################
509              
510             # This produces a clone with the same structure but no data.
511              
512             ####################################################################
513             sub copy_struct {
514 4     4   8 my $proto = shift;
515 4   33     13 my $class = ref($proto) || $proto;
516 4         6 my ($btr) = @_;
517              
518 4         19 my $save_rec = {opt=>$proto->{'opt'}, values=>[{},\"",\""]};
519              
520 4         14 return bless $save_rec,$class;
521             }
522              
523             ####################################################################
524              
525             # This uses the {opt} information to fill in {values} from the string
526             # passed as a parameter. {values} will look like
527             # [$rhfixed,$rfixed,$rrest].
528              
529             ####################################################################
530             sub parse_string {
531 2     2   3 my $save_rec = shift;
532 2         3 my $string = shift;
533 2         4 my $fixed_len = $save_rec->{'opt'}{'len'};
534             # my($fixed,$rest)= $string=~/^(.{$save_rec->{'opt'}{'len'}})(.*)/os;
535 2         6 my $fixed= substr($string,0,$fixed_len);
536 2         4 my $rest = substr($string,$fixed_len);
537              
538 2         16 my @fixed=unpack($save_rec->{'opt'}{'template'},$fixed);
539 2         3 my %fixed;
540 2         4 for (@{$save_rec->{'opt'}{'names'}}) {
  2         653  
541             #remove warnings about use of undefined value.
542 6 50 66     23 if ($_ eq 'ZZ' and !defined($fixed[0])) {
543 0         0 $fixed{$_} ='';
544 0         0 next;
545             }
546 6         24 $fixed{$_}= shift @fixed;
547             }
548 2         8 $save_rec->{'values'}=[\%fixed,\$fixed,\$rest];
549             }
550              
551             ####################################################################
552              
553             # We know how to take a string and add the necessary appurtenances
554             # for appending to the on-file btrieve set of records.
555              
556             ####################################################################
557             sub counted_rec {
558 2     2   3 my ($save_rec,$data) = @_;
559 2         9 return length($data).",".$data."\015\012"; # octal def for x-platform.
560             }
561              
562             ####################################################################
563              
564             # We know how to use our hash to create a string
565             # for appending to the on-file btrieve set of records.
566              
567             ####################################################################
568             sub counted_rec_hash {
569 2     2   3 my ($save_rec) = @_;
570 2         6 my $data = $save_rec->data();
571 2         6 return $save_rec->counted_rec($data);
572             }
573              
574             ####################################################################
575              
576             # Looks up the fixed string component of self.
577              
578             ####################################################################
579             sub fixed {
580 0     0   0 my $save_rec = shift;
581 0 0       0 if (@_) {
582 0         0 ${$save_rec->{values}[1]} = shift;
  0         0  
583             }
584 0         0 return ${$save_rec->{values}[1]};
  0         0  
585             }
586              
587              
588             ####################################################################
589              
590             # Looks up the var string component of self.
591              
592             ####################################################################
593             sub var {
594 2     2   3 my $save_rec = shift;
595 2 50       5 if (@_) {
596 0         0 ${$save_rec->{values}[2]} = shift;
  0         0  
597             }
598 2         3 return ${$save_rec->{values}[2]};
  2         5  
599             }
600              
601             ####################################################################
602              
603             # We know how to produce data from the hashed fixed info.
604              
605             ####################################################################
606             sub data {
607 2     2   3 my ($save_rec) = @_;
608              
609 2         4 my $rrest = $save_rec->{'values'}[2];
610 2         5 my $fixed = $save_rec->fix_hash_to_string();
611 2         7 return $fixed.$$rrest;
612             }
613              
614             ####################################################################
615              
616             # We know how to produce a fixed string from the hashed fixed info.
617              
618             ####################################################################
619              
620             sub fix_hash_to_string {
621 2     2   4 my ($save_rec) = @_;
622              
623 2         3 my $rhfixed = $save_rec->{'values'}[0];
624 2         4 my $template = $save_rec->{'opt'}{'template'};
625 2 50       6 $rhfixed->{"ZZ"} = '' unless defined $rhfixed->{"ZZ"};
626 2         5 my @values = ();
627             # ?? do we want to use an array slice:
628             # my %fixed = %$rhfixed;
629             # my @names = @{$save_rec->{opt}{names}}
630             # @values = @fixed{@names}??
631             #
632 2         3 for (@{$save_rec->{'opt'}{'names'}}) {
  2         6  
633 6         18 push @values, $rhfixed->{$_};
634             }
635 2         14 return pack($template,@values);
636             }
637             1; # so the require or use succeeds
638              
639             __END__