File Coverage

blib/lib/SAS/TRX.pm
Criterion Covered Total %
statement 24 121 19.8
branch 8 34 23.5
condition 0 3 0.0
subroutine 5 15 33.3
pod 1 11 9.0
total 38 184 20.6


line stmt bran cond sub pod time code
1             package SAS::TRX;
2              
3 1     1   23831 use 5.006;
  1         5  
  1         40  
4 1     1   6 use strict;
  1         2  
  1         36  
5 1     1   5 use warnings;
  1         6  
  1         60  
6              
7             our $VERSION = '0.07';
8              
9 1     1   19288 use IO::File;
  1         11204  
  1         1561  
10              
11             #
12             # Constructor
13             #
14             sub new
15             {
16 0     0 0 0 my $class = shift;
17 0         0 my $self = {
18             FH => undef,
19             TRX => {},
20             };
21            
22 0         0 bless ($self,$class);
23 0         0 return $self;
24             }
25              
26             #
27             # Given a file named
28             #
29             sub load
30             {
31 0     0 1 0 my $self = shift;
32 0         0 my $src = shift;
33              
34 0         0 $self->{FH} = new IO::File $src;
35              
36 0         0 $self->read_trx();
37             }
38              
39             my $LIBRARY_HEADER = 'HEADER RECORD*******LIBRARY HEADER RECORD!!!!!!!000000000000000000000000000000 ';
40             my $NAMESTR_HEADER = 'HEADER RECORD\*\*\*\*\*\*\*NAMESTR HEADER RECORD!!!!!!!000000(\d{4})00000000000000000000';
41             my $MEMBER_HEADER = 'HEADER RECORD\*\*\*\*\*\*\*MEMBER HEADER RECORD!!!!!!!000000000000000001600000000(\d{3})';
42             my $DSCRPTR_HEADER = 'HEADER RECORD*******DSCRPTR HEADER RECORD!!!!!!!000000000000000000000000000000 ';
43             my $OBS_HEADER = 'HEADER RECORD*******OBS HEADER RECORD!!!!!!!000000000000000000000000000000 ';
44              
45             #
46             # Get LIBRARY header. Abort if not found
47             #
48             sub library_hdr
49             {
50 0     0 0 0 my $self = shift;
51              
52 0         0 my ($tmp, %lhdata);
53              
54 0         0 read $self->{FH}, $tmp, 80;
55 0 0       0 die "LIBRARY header not found:$tmp:" unless $tmp eq $LIBRARY_HEADER;
56              
57 0         0 read $self->{FH}, $tmp, 80;
58 0         0 @lhdata{qw(SYMBOL1 SYMBOL2 LIB VER OS CREATE)} = unpack '(A8)4 A32 A16', $tmp;
59              
60 0         0 read $self->{FH}, $tmp, 80;
61 0         0 @lhdata{qw(DATETIME16)} = unpack 'A16', $tmp;
62              
63 0         0 @{$self}{qw(LIB VER)} = @lhdata{qw(LIB VER)};
  0         0  
64             }
65              
66             #
67             # Get member descriptor data
68             #
69             sub descriptor_hdr
70             {
71 0     0 0 0 my $self = shift;
72              
73 0         0 my ($tmp, %mhdata);
74              
75 0         0 read $self->{FH}, $tmp, 80;
76 0 0       0 die "DSCRPTR header not found" unless ($tmp eq $DSCRPTR_HEADER);
77              
78 0         0 read $self->{FH}, $tmp, 80;
79 0         0 @mhdata{qw(SYMBOL DSNAME SASDATA OS BLANKS CREATE)} = unpack '(A8)5 A24 A16', $tmp;
80              
81 0         0 read $self->{FH}, $tmp, 80;
82 0         0 @mhdata{qw(DATETIME16 BLANKS DSLABEL DSTYPE)} = unpack 'A16 A16 A40 A8', $tmp;
83              
84 0         0 return @mhdata{qw{DSNAME DSLABEL DSTYPE}};
85             }
86              
87             #
88             # Get NAMESTR header. Return number of NAMESTR records
89             #
90             sub namestr_hdr
91             {
92 0     0 0 0 my $self = shift;
93              
94 0         0 my ($tmp, $nnames);
95 0         0 read $self->{FH}, $tmp, 80;
96              
97 0 0       0 die 'NAMESTR header not found' unless ($tmp =~ m/$NAMESTR_HEADER/o);
98 0         0 return $1;
99             }
100              
101             #
102             # Get NAMESTR record
103             #
104             sub namestr_rec
105             {
106 0     0 0 0 my $self = shift;
107 0         0 my $reclen = shift;
108              
109 0         0 my ($tmp, %nsdata);
110              
111 0         0 read $self->{FH}, $tmp, $reclen;
112 0         0 @nsdata{qw(NTYPE NHFUN NLNG NVAR0 NNAME NLABEL NFORM NFL NFD NFJ NFILL NIFORM NIFL NIFD NPOS REST)} = unpack 'n4 A8 A40 A8 n3 A2 A8 n2 N A52', $tmp;
113 0         0 return \%nsdata;
114             }
115              
116             #
117             # Get OBS header
118             #
119             sub obs_hdr
120             {
121 0     0 0 0 my $self = shift;
122              
123 0         0 my $tmp;
124 0         0 read $self->{FH}, $tmp, 80;
125              
126 0 0       0 die "OBS header not found:$tmp:" unless ($tmp eq $OBS_HEADER);
127             }
128              
129              
130             #
131             # Read library member
132             #
133             sub get_member
134             {
135 0     0 0 0 my $self = shift;
136              
137 0         0 my ($vars, $i, $tmp);
138              
139 0         0 my $nstr_len = $self->{NSTR_LEN};
140              
141 0         0 my ($dsname, $dslabel, $dstype) = $self->descriptor_hdr();
142 0         0 $self->{TRX}{$dsname}{DSLABEL} = $dstype;
143 0         0 $self->{TRX}{$dsname}{DSTYPE} = $dstype;
144              
145             # Dataset structure description
146 0         0 $vars = $self->namestr_hdr();
147 0         0 for ($i=0; $i < $vars; $i++) {
148 0         0 push @{ $self->{TRX}{$dsname}{VAR} }, $self->namestr_rec($nstr_len);
  0         0  
149             }
150             # Align to the next punch card
151 0 0       0 if ($vars * $nstr_len % 80) {
152 0         0 seek($self->{FH}, 80 - $vars * $nstr_len % 80, 1);
153             }
154              
155              
156              
157 0         0 my ($databuf, $rowlen, $var, $format, @types);
158             # Compute row length
159 0         0 $tmp = $#{$self->{TRX}{$dsname}{VAR}};
  0         0  
160 0         0 $rowlen = $self->{TRX}{$dsname}{VAR}[$tmp]{NPOS}+
161             $self->{TRX}{$dsname}{VAR}[$tmp]{NLNG};
162              
163 0         0 $self->{TRX}{$dsname}{CNAMES} = [];
164 0         0 $self->{TRX}{$dsname}{CTYPES} = [];
165             # Compute conversion formats.
166 0         0 foreach $var (@{ $self->{TRX}{$dsname}{VAR} }) {
  0         0  
167 0         0 $format .= 'a' . $var->{NLNG};
168              
169             # Remember just a list of variable names
170 0         0 push @{$self->{TRX}{$dsname}{CNAMES}}, $var->{NNAME};
  0         0  
171             # And types
172 0         0 push @{$self->{TRX}{$dsname}{CTYPES}}, $var->{NTYPE};
  0         0  
173             }
174              
175             # Upload to destination. May create header for compressed INSERT
176 0 0       0 $self->data_header($dsname) if ($self->can('data_header'));
177              
178             # Observation data
179 0         0 $self->obs_hdr();
180 0         0 $databuf='';
181 0         0 while (read( $self->{FH}, $tmp, 80 )) {
182 0         0 $databuf .= $tmp;
183 0 0       0 last if $databuf =~ m/$MEMBER_HEADER/o;
184              
185 0         0 while (length($databuf) >= $rowlen) {
186 0         0 $self->row2array($dsname, $databuf, $format);
187 0         0 $databuf = substr($databuf, $rowlen);
188 0 0       0 last unless $databuf =~ /[^ ]/go;
189             }
190 0 0       0 last if eof $self->{FH}; # read after eof may be wrong
191             }
192 0         0 $self->{NSTR_LEN} = $1; # In case the library is joined from various platforms data
193              
194             # Upload to destination. May create header for compressed INSERT
195 0 0       0 $self->data_footer($dsname) if $self->can('data_footer');
196             }
197              
198             #
199             # Convert TRX observation (data row)
200             # into array of values
201             #
202             sub row2array
203             {
204 0     0 0 0 my ($self, $dsname, $row, $format) = @_;
205              
206              
207 0         0 my @data = unpack($format, $row);
208            
209 0         0 for (my $i=0; $i<= $#data; $i++) {
210 0 0       0 if ($self->{TRX}{$dsname}{CTYPES}[$i] == 1) {
211 0         0 $data[$i] = ibm_float($data[$i]);
212             } else {
213             # Trim whitespaces
214 0         0 $data[$i] =~ s/\s+$//;
215 0         0 $data[$i] =~ s/^\s+//;
216             }
217             }
218              
219             # Unload to target
220 0 0       0 $self->data_row($dsname, \@data) if $self->can('data_row');
221             }
222              
223             #
224             # Decrypt TRX numeric representation
225             #
226             # I agree that "significand" is "that which is to be signified".
227             # Let the meaningful part be "mantissa". As it was before.
228             #
229             sub ibm_float
230             {
231 8     8 0 17 my $value = shift;
232              
233 8         26 my ($firstbyte,$bin) = unpack "CB*", $value;
234              
235 8 100       27 if ($bin == 0) {
236 2 100       10 return undef if ($firstbyte); # Undefined values
237 1         5 return 0;
238             }
239              
240 6         9 my $exp=($firstbyte & 0x7F) - 0x40;
241 6         7 my $mantissa = 0;
242              
243 6         13 while (length($bin)) {
244 296 100       428 $mantissa += 1 if (chop $bin);
245 296         456 $mantissa /= 2;
246             }
247              
248 6 100       12 $mantissa = -$mantissa if ($firstbyte & 0x80);
249 6         26 return $mantissa*(16**$exp);
250             }
251              
252             #
253             # Read library
254             #
255             sub read_trx
256             {
257 0     0 0   my $self = shift;
258              
259 0           my ($tmp, $nstr_len);
260              
261 0           $self->library_hdr();
262              
263             # Skip possible junk until member header
264 0   0       do {
265 0           read $self->{FH}, $tmp, 80;
266             } until (eof($self->{FH}) || $tmp =~ m/$MEMBER_HEADER/o);
267 0           $self->{NSTR_LEN} = $1;
268              
269             # Get library members
270 0           until (eof($self->{FH})) {
271 0           $self->get_member();
272             }
273             # We have got it all. Dump the results, if anybody cares
274 0 0         $self->data_description() if $self->can('data_description');
275             }
276              
277              
278             1;
279              
280             __END__