File Coverage

blib/lib/Spreadsheet/WriteExcel/Properties.pm
Criterion Covered Total %
statement 105 108 97.2
branch 13 16 81.2
condition 1 2 50.0
subroutine 12 12 100.0
pod 0 2 0.0
total 131 140 93.5


line stmt bran cond sub pod time code
1             package Spreadsheet::WriteExcel::Properties;
2              
3             ###############################################################################
4             #
5             # Properties - A module for creating Excel property sets.
6             #
7             #
8             # Used in conjunction with Spreadsheet::WriteExcel
9             #
10             # Copyright 2000-2010, John McNamara.
11             #
12             # Documentation after __END__
13             #
14              
15 32     32   1861 use Exporter;
  32         72  
  32         1339  
16 32     32   194 use strict;
  32         71  
  32         3534  
17 32     32   2995 use Carp;
  32         1478  
  32         3667  
18 32     32   42647 use POSIX 'fmod';
  32         313574  
  32         249  
19 32     32   88950 use Time::Local 'timelocal';
  32         68261  
  32         3859  
20              
21              
22              
23              
24 32     32   254 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  32         77  
  32         50225  
25             @ISA = qw(Exporter);
26              
27             $VERSION = '2.40';
28              
29             # Set up the exports.
30             my @all_functions = qw(
31             create_summary_property_set
32             create_doc_summary_property_set
33             _pack_property_data
34             _pack_VT_I2
35             _pack_VT_LPSTR
36             _pack_VT_FILETIME
37             );
38              
39             my @pps_summaries = qw(
40             create_summary_property_set
41             create_doc_summary_property_set
42             );
43              
44             @EXPORT = ();
45             @EXPORT_OK = (@all_functions);
46             %EXPORT_TAGS = (testing => \@all_functions,
47             property_sets => \@pps_summaries,
48             );
49              
50              
51             ###############################################################################
52             #
53             # create_summary_property_set().
54             #
55             # Create the SummaryInformation property set. This is mainly used for the
56             # Title, Subject, Author, Keywords, Comments, Last author keywords and the
57             # creation date.
58             #
59             sub create_summary_property_set {
60              
61 22     22 0 4860 my @properties = @{$_[0]};
  22         62  
62              
63 22         36 my $byte_order = pack 'v', 0xFFFE;
64 22         29 my $version = pack 'v', 0x0000;
65 22         27 my $system_id = pack 'V', 0x00020105;
66 22         27 my $class_id = pack 'H*', '00000000000000000000000000000000';
67 22         24 my $num_property_sets = pack 'V', 0x0001;
68 22         23 my $format_id = pack 'H*', 'E0859FF2F94F6810AB9108002B27B3D9';
69 22         28 my $offset = pack 'V', 0x0030;
70 22         67 my $num_property = pack 'V', scalar @properties;
71 22         28 my $property_offsets = '';
72              
73             # Create the property set data block and calculate the offsets into it.
74 22         46 my ($property_data, $offsets) = _pack_property_data(\@properties);
75              
76             # Create the property type and offsets based on the previous calculation.
77 22         92 for my $i (0 .. @properties -1) {
78 99         230 $property_offsets .= pack('VV', $properties[$i]->[0], $offsets->[$i]);
79             }
80              
81             # Size of $size (4 bytes) + $num_property (4 bytes) + the data structures.
82 22         44 my $size = 8 + length($property_offsets) + length($property_data);
83 22         40 $size = pack 'V', $size;
84              
85              
86 22         158 return $byte_order .
87             $version .
88             $system_id .
89             $class_id .
90             $num_property_sets .
91             $format_id .
92             $offset .
93             $size .
94             $num_property .
95             $property_offsets .
96             $property_data;
97             }
98              
99              
100             ###############################################################################
101             #
102             # Create the DocSummaryInformation property set. This is mainly used for the
103             # Manager, Company and Category keywords.
104             #
105             # The DocSummary also contains a stream for user defined properties. However
106             # this is a little arcane and probably not worth the implementation effort.
107             #
108             sub create_doc_summary_property_set {
109              
110 14     14 0 17 my @properties = @{$_[0]};
  14         31  
111              
112 14         21 my $byte_order = pack 'v', 0xFFFE;
113 14         19 my $version = pack 'v', 0x0000;
114 14         17 my $system_id = pack 'V', 0x00020105;
115 14         16 my $class_id = pack 'H*', '00000000000000000000000000000000';
116 14         14 my $num_property_sets = pack 'V', 0x0002;
117              
118 14         16 my $format_id_0 = pack 'H*', '02D5CDD59C2E1B10939708002B2CF9AE';
119 14         16 my $format_id_1 = pack 'H*', '05D5CDD59C2E1B10939708002B2CF9AE';
120 14         16 my $offset_0 = pack 'V', 0x0044;
121 14         30 my $num_property_0 = pack 'V', scalar @properties;
122 14         20 my $property_offsets_0 = '';
123              
124             # Create the property set data block and calculate the offsets into it.
125 14         26 my ($property_data_0, $offsets) = _pack_property_data(\@properties);
126              
127             # Create the property type and offsets based on the previous calculation.
128 14         40 for my $i (0 .. @properties -1) {
129 14         83 $property_offsets_0 .= pack('VV', $properties[$i]->[0], $offsets->[$i]);
130             }
131              
132             # Size of $size (4 bytes) + $num_property (4 bytes) + the data structures.
133 14         29 my $data_len = 8 + length($property_offsets_0) + length($property_data_0);
134 14         22 my $size_0 = pack 'V', $data_len;
135              
136              
137             # The second property set offset is at the end of the first property set.
138 14         26 my $offset_1 = pack 'V', 0x0044 + $data_len;
139              
140             # We will use a static property set stream rather than try to generate it.
141 14         187 my $property_data_1 = pack 'H*', join '', qw (
142             98 00 00 00 03 00 00 00 00 00 00 00 20 00 00 00
143             01 00 00 00 36 00 00 00 02 00 00 00 3E 00 00 00
144             01 00 00 00 02 00 00 00 0A 00 00 00 5F 50 49 44
145             5F 47 55 49 44 00 02 00 00 00 E4 04 00 00 41 00
146             00 00 4E 00 00 00 7B 00 31 00 36 00 43 00 34 00
147             42 00 38 00 33 00 42 00 2D 00 39 00 36 00 35 00
148             46 00 2D 00 34 00 42 00 32 00 31 00 2D 00 39 00
149             30 00 33 00 44 00 2D 00 39 00 31 00 30 00 46 00
150             41 00 44 00 46 00 41 00 37 00 30 00 31 00 42 00
151             7D 00 00 00 00 00 00 00 2D 00 39 00 30 00 33 00
152             );
153              
154              
155 14         90 return $byte_order .
156             $version .
157             $system_id .
158             $class_id .
159             $num_property_sets .
160             $format_id_0 .
161             $offset_0 .
162             $format_id_1 .
163             $offset_1 .
164              
165             $size_0 .
166             $num_property_0 .
167             $property_offsets_0 .
168             $property_data_0 .
169              
170             $property_data_1;
171             }
172              
173              
174             ###############################################################################
175             #
176             # _pack_property_data().
177             #
178             # Create a packed property set structure. Strings are null terminated and
179             # padded to a 4 byte boundary. We also use this function to keep track of the
180             # property offsets within the data structure. These offsets are used by the
181             # calling functions. Currently we only need to handle 4 property types:
182             # VT_I2, VT_LPSTR, VT_FILETIME.
183             #
184             sub _pack_property_data {
185              
186 36     36   36 my @properties = @{$_[0]};
  36         76  
187 36   50     141 my $offset = $_[1] || 0;
188 36         46 my $packed_property = '';
189 36         38 my $data = '';
190 36         41 my @offsets;
191              
192             # Get the strings codepage from the first property.
193 36         45 my $codepage = $properties[0]->[2];
194              
195             # The properties start after 8 bytes for size + num_properties + 8 bytes
196             # for each property type/offset pair.
197 36         68 $offset += 8 * (@properties + 1);
198              
199 36         59 for my $property (@properties) {
200 113         176 push @offsets, $offset;
201              
202 113         148 my $property_type = $property->[1];
203              
204 113 100       268 if ($property_type eq 'VT_I2') {
    100          
    50          
205 36         68 $packed_property = _pack_VT_I2($property->[2]);
206             }
207             elsif ($property_type eq 'VT_LPSTR') {
208 73         149 $packed_property = _pack_VT_LPSTR($property->[2], $codepage);
209             }
210             elsif ($property_type eq 'VT_FILETIME') {
211 4         14 $packed_property = _pack_VT_FILETIME($property->[2]);
212             }
213             else {
214 0         0 croak "Unknown property type: $property_type\n";
215             }
216              
217 113         168 $offset += length $packed_property;
218 113         262 $data .= $packed_property;
219             }
220              
221 36         126 return $data, \@offsets;
222             }
223              
224              
225             ###############################################################################
226             #
227             # _pack_VT_I2().
228             #
229             # Pack an OLE property type: VT_I2, 16-bit signed integer.
230             #
231             sub _pack_VT_I2 {
232              
233 37     37   56 my $type = 0x0002;
234 37         47 my $value = $_[0];
235              
236 37         79 my $data = pack 'VV', $type, $value;
237              
238 37         84 return $data;
239             }
240              
241              
242             ###############################################################################
243             #
244             # _pack_VT_LPSTR().
245             #
246             # Pack an OLE property type: VT_LPSTR, String in the Codepage encoding.
247             # The strings are null terminated and padded to a 4 byte boundary.
248             #
249             sub _pack_VT_LPSTR {
250              
251 84     84   6812 my $type = 0x001E;
252 84         132 my $string = $_[0] . "\0";
253 84         96 my $codepage = $_[1];
254 84         91 my $length;
255             my $byte_string;
256              
257 84 100       159 if ($codepage == 0x04E4) {
    50          
258             # Latin1
259 72         88 $byte_string = $string;
260 72         87 $length = length $byte_string;
261             }
262             elsif ($codepage == 0xFDE9) {
263             # UTF-8
264 12 50       26 if ( $] > 5.008 ) {
265 12         1167 require Encode;
266 12 100       11378 if (Encode::is_utf8($string)) {
267 8         26 $byte_string = Encode::encode_utf8($string);
268             }
269             else {
270 4         5 $byte_string = $string;
271             }
272             }
273             else {
274 0         0 $byte_string = $string;
275             }
276              
277 12         63 $length = length $byte_string;
278             }
279             else {
280 0         0 croak "Unknown codepage: $codepage\n";
281             }
282              
283             # Pack the data.
284 84         172 my $data = pack 'VV', $type, $length;
285 84         130 $data .= $byte_string;
286              
287             # The packed data has to null padded to a 4 byte boundary.
288 84 100       206 if (my $extra = $length % 4) {
289 65         138 $data .= "\0" x (4 - $extra);
290             }
291              
292 84         2143 return $data;
293             }
294              
295              
296             ###############################################################################
297             #
298             # _pack_VT_FILETIME().
299             #
300             # Pack an OLE property type: VT_FILETIME.
301             #
302             sub _pack_VT_FILETIME {
303              
304 5     5   635 my $type = 0x0040;
305 5         8 my $localtime = $_[0];
306              
307             # Convert from localtime to seconds.
308 5         8 my $seconds = Time::Local::timelocal(@{$localtime});
  5         24  
309              
310             # Add the number of seconds between the 1601 and 1970 epochs.
311 5         269 $seconds += 11644473600;
312              
313             # The FILETIME seconds are in units of 100 nanoseconds.
314 5         12 my $nanoseconds = $seconds * 1E7;
315              
316             # Pack the total nanoseconds into 64 bits.
317 5         12 my $time_hi = int($nanoseconds / 2**32);
318 5         51 my $time_lo = POSIX::fmod($nanoseconds, 2**32);
319              
320 5         62 my $data = pack 'VVV', $type, $time_lo, $time_hi;
321              
322 5         17 return $data;
323             }
324              
325              
326             1;
327              
328              
329             __END__