File Coverage

blib/lib/Parse/Lotus123/WK4.pm
Criterion Covered Total %
statement 44 51 86.2
branch 18 38 47.3
condition 3 9 33.3
subroutine 7 7 100.0
pod 0 3 0.0
total 72 108 66.6


line stmt bran cond sub pod time code
1             package Parse::Lotus123::WK4;
2              
3             =head1 NAME
4              
5             Parse::Lotus123::WK4 - extract data from Lotus 1-2-3 .wk4 files
6              
7             =head1 OVERVIEW
8              
9             This module extracts data from Lotus 1-2-3 .wk4 files.
10              
11             =head1 NO DOCUMENTATION
12              
13             Procedural API:
14             Parse::Lotus123::WK4::parse takes a filehandle and returns a three-dimensional arrayref.
15              
16             See the source code to wk42csv for a working example.
17              
18             =head1 SOURCES
19              
20             Description of WK4 format:
21             L
22              
23             Method for decoding IEEE 80-bit floats:
24             L
25              
26             =head1 BUGS
27              
28             This code is experimental, not documented and not properly tested.
29              
30             =head1 NO WARRANTY
31              
32             This code comes with ABSOLUTELY NO WARRANTY of any kind.
33              
34             =head1 AUTHOR
35              
36             Copyright 2008 Reckon LLP and Franck Latrémolière.
37             L
38              
39             =head1 LICENCE
40              
41             This is free software; you can redistribute it and/or modify it under the same terms as Perl.
42              
43             =cut
44              
45              
46 1     1   16891 use warnings;
  1         2  
  1         37  
47 1     1   6 use strict;
  1         2  
  1         160  
48              
49             BEGIN {
50              
51 1     1   3 $Parse::Lotus123::WK4::VERSION = '0.09';
52              
53             # test for float endianness using little-endian 33 33 3b f3, which is a float code for 1.4
54            
55 1         20 my $testFloat = unpack( 'f', pack( 'h*', 'f33b3333' ) );
56 1 50 33     17 $Parse::Lotus::WK4::bigEndian = 1
57             if ( 2.0 * $testFloat > 2.7 && 2.0 * $testFloat < 2.9 );
58 1         3 $testFloat = unpack( 'f', pack( 'h*', '33333bf3' ) );
59 1 50 33     11 $Parse::Lotus::WK4::bigEndian = 0
60             if ( 2.0 * $testFloat > 2.7 && 2.0 * $testFloat < 2.9 );
61 1 50       86 die "Unable to detect endianness of float storage on your machine"
62             unless defined $Parse::Lotus::WK4::bigEndian;
63              
64             }
65              
66             sub decode_lotus_weirdness {
67 1     1 0 6 my $h = unpack 's', pack 'S', $_[0];
68 1 50       7 return $h / 2 unless $h & 1;
69 1         3 my $sw = $h & 0x0f;
70             {
71 1     1   1046 use integer; # this makes the right-shift operator signed for the block
  1         10  
  1         6  
  1         3  
72 1         4 $h >>= 4;
73             }
74 1 50       4 return $h * 5000 if $sw == 0x1;
75 1 50       4 return $h * 500 if $sw == 0x3;
76 1 50       11 return $h / 20 if $sw == 0x5;
77 0 0       0 return $h / 200 if $sw == 0x7;
78 0 0       0 return $h / 2000 if $sw == 0x9;
79 0 0       0 return $h / 20000 if $sw == 0xb;
80 0 0       0 return $h / 16 if $sw == 0xd;
81 0 0       0 return $h / 64 if $sw == 0xf;
82             }
83              
84             sub decode_float80 {
85 1     1 0 6 my( $discard, $mantissa, $hidden, $exponent, $sign ) =
86             unpack 'a11 a52 a1 a15 a1', $_[ 0 ];
87 1         5 $exponent = unpack( 'v', pack 'b15', $exponent ) - 16383 + 1023;
88 1 50 33     14 ($exponent, $mantissa) = (32767, '0' x 52)
89             if $exponent < 0 || $exponent > 2047;
90 1         4 $exponent = unpack 'b11', pack 'v', $exponent;
91 1         5 my $bits64 = pack 'b64', $mantissa . $exponent . $sign;
92 1 50       4 $bits64 = pack 'a' x 8, reverse unpack 'a' x 8, pack 'b64', $bits64
93             if $Parse::Lotus::WK4::bigEndian;
94 1         7 unpack 'd', $bits64;
95             }
96              
97             sub parse($) {
98 1     1 0 536 my $fh = $_[0] ;
99 1         5 my $data = [[[]]];
100 1         29 while ( read( $fh, my $head, 4 ) == 4 ) {
101 73         98 my ( $code, $len ) = unpack( 'vv', $head );
102 73         112 my $read = read ($fh, my $byt, $len);
103 73 100       363 if ( $read != $len ) {
    100          
    50          
    100          
    100          
104             # warn "Could not read $len bytes";
105             # no need to warn the user: we are probably just at the end of the file
106             }
107             elsif ( $code == 0x16 ) {
108 1         6 my ( $row, $sheet, $col, $align, $text ) = unpack( 'vCCCA*', $byt );
109 1         4 $text =~ s/"/'/g;
110 1         6 $data->[$sheet][$row][$col] = $text;
111             }
112             elsif ( $code == 0x17 ) {
113 0         0 my ( $row, $sheet, $col, $b ) = unpack( 'vCCb80', $byt );
114 0         0 $data->[$sheet][$row][$col] = decode_float80 $b;
115             }
116             elsif ( $code == 0x19 ) {
117 1         5 my ( $row, $sheet, $col, $b, $formula ) =
118             unpack( 'vCCb80A*', $byt );
119 1         13 $data->[$sheet][$row][$col] = decode_float80 $b;
120             }
121             elsif ( $code == 0x18 ) {
122 1         4 my ( $row, $sheet, $col, $b ) = unpack( 'vCCv', $byt );
123 1         5 $data->[$sheet][$row][$col] = decode_lotus_weirdness $b;
124             }
125             }
126 1         3 $data;
127             }
128              
129             1;