File Coverage

blib/lib/Parse/Stata/DtaReader.pm
Criterion Covered Total %
statement 81 125 64.8
branch 24 88 27.2
condition 5 36 13.8
subroutine 10 14 71.4
pod 0 9 0.0
total 120 272 44.1


line stmt bran cond sub pod time code
1             package Parse::Stata::DtaReader;
2              
3             =head1 NAME
4              
5             Parse::Stata::DtaReader - read Stata 8 and Stata 10 .dta files
6              
7             =head1 OVERVIEW
8              
9             This module reads Stata 8 and Stata 10 .dta files.
10              
11             The API is object oriented: create a new instance of
12             Parse::Stata::DtaReader by providing a handle to the .dta file, and then
13             use attributes and methods of that object to obtain the data.
14              
15             =head1 SYNOPSIS
16              
17             Look at the source code of dta2csv and dta2sql for working code examples.
18              
19             =head2 Open a .dta file
20              
21             open my $fileHandle, '<', 'test.dta';
22            
23             my $dta = new Parse::Stata::DtaReader($fileHandle);
24              
25             =head2 Retrieve general information
26              
27             Number of variables and observations:
28              
29             print "$dta->{nvar} variables; $dta->{nobs} observations\n";
30              
31             Variable names and types:
32              
33             print join( ',', @{ $dta->{varlist} } ) . "\n";
34            
35             for (my $i = 0; $i < $dta->{nvar}; ++$i) {
36             print "$dta->{varlist}[$i] has SQL type " .
37             $dta->sqlType($i) . "\n";
38             }
39              
40             =head2 Retrieve data
41              
42             while ( my @a = $dta->readRow ) {
43             print join( ',', @a ) . "\n";
44             }
45              
46             =head1 SOURCES
47              
48             Stata .dta format specification from:
49             http://www.stata.com/help.cgi?dta_114
50             http://www.stata.com/help.cgi?dta_113
51              
52             =head1 BUGS
53              
54             It works for me, but has not been fully tested.
55              
56             All Stata missing values will be converted into a perl undef,
57             losing the information about the type of missing value.
58              
59             =head1 NO WARRANTY
60              
61             This code comes with ABSOLUTELY NO WARRANTY of any kind.
62              
63             =head1 AUTHOR
64              
65             Copyright 2007, 2008, 2014 Reckon LLP and Franck Latrémolière.
66             L
67              
68             =head1 LICENCE
69              
70             This is free software; you can redistribute it and/or modify it under the same terms as Perl.
71              
72             =cut
73              
74 1     1   51324 use warnings;
  1         4  
  1         66  
75 1     1   8 use strict;
  1         2  
  1         555  
76 1     1   10 use Carp;
  1         279  
  1         935  
77              
78             BEGIN {
79              
80 1     1   3 $Parse::Stata::DtaReader::VERSION = '0.711';
81              
82             # test for float endianness using little-endian 33 33 3b f3, which is a float code for 1.4
83              
84 1         95 my $testFloat = unpack( 'f', pack( 'h*', 'f33b3333' ) );
85 1 50 33     10 $Parse::Stata::DtaReader::byteOrder = 1 # big-endian
86             if ( 2.0 * $testFloat > 2.7 && 2.0 * $testFloat < 2.9 );
87 1         3 $testFloat = unpack( 'f', pack( 'h*', '33333bf3' ) );
88 1 50 33     10 $Parse::Stata::DtaReader::byteOrder = 2 # little-endian
89             if ( 2.0 * $testFloat > 2.7 && 2.0 * $testFloat < 2.9 );
90 1 50       6724 warn "Unable to detect endianness of float storage on your machine"
91             unless $Parse::Stata::DtaReader::byteOrder;
92              
93             }
94              
95             sub new($$) {
96 3     3 0 890 my $className = shift;
97 3         7 my $fileHandle = shift;
98 3         6 my $self = { fh => $fileHandle };
99 3         7 bless $self, $className;
100 3         6 $self->readHeader;
101 3 50 66     21 if ( $self->{ds_format}
      33        
102             and $self->{ds_format} == 114 || $self->{ds_format} == 113 )
103             {
104 3         9 $self->readDescriptors;
105 3         9 $self->readVariableLabels;
106 3         8 $self->discardExpansionFields;
107 3         7 $self->prepareDataReader;
108             }
109 3         8 return $self;
110             }
111              
112             sub readHeader($) {
113 3     3 0 4 my $self = shift;
114 3         4 local $_;
115 3 50       59 unless ( read $self->{fh}, $_, 4 ) {
116 0         0 carp "Cannot read any data";
117 0         0 return;
118             }
119 3         14 ( $self->{ds_format}, $self->{byteorder}, $self->{filetype}, $_ ) =
120             unpack( 'CCCC', $_ );
121 3         10 read $self->{fh}, $_, 105;
122 3 100       22 ( $self->{nvar}, $self->{nobs}, $self->{data_label}, $self->{time_stamp} )
123             = unpack( ( $self->{byteorder} == 2 ? 'vV' : 'nN' ) . 'A81A18', $_ );
124 3         14 $self->{data_label} =~ s/\x00.*$//s;
125 3         7 $self->{time_stamp} =~ s/\x00.*$//s;
126             }
127              
128             sub readDescriptors($) {
129 3     3 0 3 my $self = shift;
130 3         5 my $nv = $self->{nvar};
131 3         3 local $_;
132 3         7 read $self->{fh}, $_, $nv;
133 3         16 $self->{typlist} = [ unpack( 'C' x $nv, $_ ) ];
134 3         9 read $self->{fh}, $_, $nv * 33;
135 3         8 $self->{varlist} = [ map { s/\x00.*$//s; $_ } unpack( 'A33' x $nv, $_ ) ];
  9         13  
  9         18  
136 3         7 read $self->{fh}, $_, $nv * 2 + 2;
137 3 100       15 $self->{srtlist} =
138             [ unpack( ( $self->{byteorder} == 2 ? 'v' : 'n' ) x ( 1 + $nv ), $_ ) ];
139 3 100       7 my $fmtSize = $self->{ds_format} == 113 ? 12 : 49;
140 3         7 read $self->{fh}, $_, $nv * $fmtSize;
141 9         10 $self->{fmtlist} =
142 3         9 [ map { s/\x00.*$//s; $_ } unpack( ( 'A' . $fmtSize ) x $nv, $_ ) ];
  9         18  
143 3         8 read $self->{fh}, $_, $nv * 33;
144 3         8 $self->{lbllist} = [ map { s/\x00.*$//s; $_ } unpack( 'A33' x $nv, $_ ) ];
  9         8  
  9         17  
145             }
146              
147             sub readVariableLabels($) {
148 3     3 0 4 my $self = shift;
149 3         6 my $nv = $self->{nvar};
150 3         4 local $_;
151 3         7 read $self->{fh}, $_, $nv * 81;
152 9         19 $self->{variableLabelList} =
153 3         10 [ map { s/\x00.*$//s; $_ } unpack( 'A81' x $nv, $_ ) ];
  9         20  
154             }
155              
156             sub discardExpansionFields($) {
157 3     3 0 4 my $self = shift;
158 3         4 local $_;
159 3         3 my $size = -1;
160 3         8 while ($size) {
161 3         8 read $self->{fh}, $_, 5;
162 3 100       9 $size =
163             unpack( $self->{byteorder} == 2 ? 'V' : 'N', substr( $_, 1, 4 ) );
164 3 50       12 read $self->{fh}, $_, $size if $size > 0;
165             }
166             }
167              
168             sub prepareDataReader($) {
169 3     3 0 4 my $self = shift;
170 3         4 $self->{nextRow} = 1;
171 3         23 $self->{rowPattern} = '';
172 3         5 $self->{rowSize} = 0;
173 3         4 for my $vt ( @{ $self->{typlist} } ) {
  3         6  
174 9 50       35 if ( $vt == 255 ) {
    100          
    50          
    50          
    100          
    50          
175 0         0 $self->{rowSize} += 8;
176 0 0       0 $self->{rowPattern} .=
177             $self->{byteorder} == $Parse::Stata::DtaReader::byteOrder
178             ? 'd'
179             : 'H16';
180             }
181             elsif ( $vt == 254 ) {
182 7         8 $self->{rowSize} += 4;
183 7 100       27 $self->{rowPattern} .=
184             $self->{byteorder} == $Parse::Stata::DtaReader::byteOrder
185             ? 'f'
186             : 'H8';
187             }
188             elsif ( $vt == 253 ) {
189 0         0 $self->{rowSize} += 4;
190 0 0       0 $self->{rowPattern} .= $self->{byteorder} == 2 ? 'V' : 'N';
191             }
192             elsif ( $vt == 252 ) {
193 0         0 $self->{rowSize} += 2;
194 0 0       0 $self->{rowPattern} .= $self->{byteorder} == 2 ? 'v' : 'n';
195             }
196             elsif ( $vt == 251 ) {
197 1         1 $self->{rowSize} += 1;
198 1         2 $self->{rowPattern} .= 'c';
199             }
200             elsif ( $vt < 245 ) {
201 1         2 $self->{rowSize} += $vt;
202 1         4 $self->{rowPattern} .= 'A' . $vt;
203             }
204             }
205             }
206              
207             sub hasNext($) {
208 0     0 0   my $self = shift;
209 0 0         return $self->{nextRow} > $self->{nobs} ? undef : $self->{nextRow};
210             }
211              
212             sub readRow($) {
213 0     0 0   my $self = shift;
214 0           local $_;
215 0 0         return () unless $self->{rowSize} == read $self->{fh}, $_, $self->{rowSize};
216 0           $self->{nextRow}++;
217 0           my @a = unpack( $self->{rowPattern}, $_ );
218 0           for ( my $i = 0 ; $i < @a ; $i++ ) {
219 0           my $t = $self->{typlist}->[$i];
220 0 0         if ( defined $a[$i] ) {
221 0 0         if ( $self->{byteorder} != $Parse::Stata::DtaReader::byteOrder ) {
222 0 0         if ( $t == 254 ) {
    0          
223 0           $a[$i] =
224             unpack( 'f',
225             pack( 'N', ( unpack( 'V', pack( 'H8', $a[$i] ) ) ) ) );
226             }
227             elsif ( $t == 255 ) {
228 0           $a[$i] = unpack(
229             'd',
230             pack( 'NN',
231             reverse( unpack( 'VV', pack( 'H16', $a[$i] ) ) ) )
232             );
233             }
234             }
235 0 0         if ( $t < 245 ) {
    0          
    0          
    0          
    0          
    0          
236 0           $a[$i] =~ s/\x00.*$//s;
237             }
238             elsif ( $t == 251 ) {
239 0 0 0       undef $a[$i] if $a[$i] > 100 && $a[$i] < 128;
240             }
241             elsif ( $t == 252 ) {
242 0           $a[$i] = unpack 's', pack 'S', $a[$i];
243 0 0 0       undef $a[$i] if $a[$i] > 32740 && $a[$i] < 32768;
244             }
245             elsif ( $t == 253 ) {
246 0           $a[$i] = unpack 'l', pack 'L', $a[$i];
247 0 0 0       undef $a[$i] if $a[$i] > 2147483620 && $a[$i] < 2147483648;
248             }
249             elsif ( $t == 254 ) {
250 0 0 0       undef $a[$i]
      0        
251             if defined $a[$i] and $a[$i] > 1.701e38 || $a[$i] < -1.701e38;
252             }
253             elsif ( $t == 255 ) {
254 0 0 0       undef $a[$i]
      0        
255             if defined $a[$i]
256             and $a[$i] > 8.988e307 || $a[$i] < -1.798e308;
257             }
258             }
259             }
260 0           return @a;
261             }
262              
263             sub _sqlType($) {
264 0 0   0     return 'DOUBLE' if $_[0] == 255;
265 0 0         return 'FLOAT' if $_[0] == 254;
266 0 0         return 'INT' if $_[0] == 253;
267 0 0         return 'SMALLINT' if $_[0] == 252;
268 0 0         return 'TINYINT' if $_[0] == 251;
269 0 0 0       return "CHAR($_[0])" if $_[0] > 0 && $_[0] < 245;
270 0           return undef;
271             }
272              
273             sub sqlType($$) {
274 0     0 0   my ( $self, $varNumber ) = @_;
275 0 0         return _sqlType( $self->{typlist}[$varNumber] ) if defined $varNumber;
276 0           return map { _sqlType($_); } @{ $self->{typlist} };
  0            
  0            
277             }
278              
279             1;