yu.dong | c33b307 | 2024-08-21 23:14:49 -0700 | [diff] [blame^] | 1 | package Spreadsheet::WriteExcel::OLEwriter; |
| 2 | |
| 3 | ############################################################################### |
| 4 | # |
| 5 | # OLEwriter - A writer class to store BIFF data in a OLE compound storage file. |
| 6 | # |
| 7 | # |
| 8 | # Used in conjunction with Spreadsheet::WriteExcel |
| 9 | # |
| 10 | # Copyright 2000-2010, John McNamara, jmcnamara@cpan.org |
| 11 | # |
| 12 | # Documentation after __END__ |
| 13 | # |
| 14 | |
| 15 | use Exporter; |
| 16 | use strict; |
| 17 | use Carp; |
| 18 | use FileHandle; |
| 19 | |
| 20 | |
| 21 | |
| 22 | |
| 23 | |
| 24 | use vars qw($VERSION @ISA); |
| 25 | @ISA = qw(Exporter); |
| 26 | |
| 27 | $VERSION = '2.37'; |
| 28 | |
| 29 | ############################################################################### |
| 30 | # |
| 31 | # new() |
| 32 | # |
| 33 | # Constructor |
| 34 | # |
| 35 | sub new { |
| 36 | |
| 37 | my $class = shift; |
| 38 | my $self = { |
| 39 | _olefilename => $_[0], |
| 40 | _filehandle => "", |
| 41 | _fileclosed => 0, |
| 42 | _internal_fh => 0, |
| 43 | _biff_only => 0, |
| 44 | _size_allowed => 0, |
| 45 | _biffsize => 0, |
| 46 | _booksize => 0, |
| 47 | _big_blocks => 0, |
| 48 | _list_blocks => 0, |
| 49 | _root_start => 0, |
| 50 | _block_count => 4, |
| 51 | }; |
| 52 | |
| 53 | bless $self, $class; |
| 54 | $self->_initialize(); |
| 55 | return $self; |
| 56 | } |
| 57 | |
| 58 | |
| 59 | ############################################################################### |
| 60 | # |
| 61 | # _initialize() |
| 62 | # |
| 63 | # Create a new filehandle or use the provided filehandle. |
| 64 | # |
| 65 | sub _initialize { |
| 66 | |
| 67 | my $self = shift; |
| 68 | my $olefile = $self->{_olefilename}; |
| 69 | my $fh; |
| 70 | |
| 71 | # If the filename is a reference it is assumed that it is a valid |
| 72 | # filehandle, if not we create a filehandle. |
| 73 | # |
| 74 | if (ref($olefile)) { |
| 75 | $fh = $olefile; |
| 76 | } |
| 77 | else{ |
| 78 | |
| 79 | # Create a new file, open for writing |
| 80 | $fh = FileHandle->new("> $olefile"); |
| 81 | |
| 82 | # Workbook.pm also checks this but something may have happened since |
| 83 | # then. |
| 84 | if (not defined $fh) { |
| 85 | croak "Can't open $olefile. It may be in use or protected.\n"; |
| 86 | } |
| 87 | |
| 88 | # binmode file whether platform requires it or not |
| 89 | binmode($fh); |
| 90 | |
| 91 | $self->{_internal_fh} = 1; |
| 92 | } |
| 93 | |
| 94 | # Store filehandle |
| 95 | $self->{_filehandle} = $fh; |
| 96 | } |
| 97 | |
| 98 | |
| 99 | ############################################################################### |
| 100 | # |
| 101 | # set_size($biffsize) |
| 102 | # |
| 103 | # Set the size of the data to be written to the OLE stream |
| 104 | # |
| 105 | # $big_blocks = (109 depot block x (128 -1 marker word) |
| 106 | # - (1 x end words)) = 13842 |
| 107 | # $maxsize = $big_blocks * 512 bytes = 7087104 |
| 108 | # |
| 109 | sub set_size { |
| 110 | |
| 111 | my $self = shift; |
| 112 | my $maxsize = 7_087_104; # Use Spreadsheet::WriteExcel::Big to exceed this |
| 113 | |
| 114 | if ($_[0] > $maxsize) { |
| 115 | return $self->{_size_allowed} = 0; |
| 116 | } |
| 117 | |
| 118 | $self->{_biffsize} = $_[0]; |
| 119 | |
| 120 | # Set the min file size to 4k to avoid having to use small blocks |
| 121 | if ($_[0] > 4096) { |
| 122 | $self->{_booksize} = $_[0]; |
| 123 | } |
| 124 | else { |
| 125 | $self->{_booksize} = 4096; |
| 126 | } |
| 127 | |
| 128 | return $self->{_size_allowed} = 1; |
| 129 | |
| 130 | } |
| 131 | |
| 132 | |
| 133 | ############################################################################### |
| 134 | # |
| 135 | # _calculate_sizes() |
| 136 | # |
| 137 | # Calculate various sizes needed for the OLE stream |
| 138 | # |
| 139 | sub _calculate_sizes { |
| 140 | |
| 141 | my $self = shift; |
| 142 | my $datasize = $self->{_booksize}; |
| 143 | |
| 144 | if ($datasize % 512 == 0) { |
| 145 | $self->{_big_blocks} = $datasize/512; |
| 146 | } |
| 147 | else { |
| 148 | $self->{_big_blocks} = int($datasize/512) +1; |
| 149 | } |
| 150 | # There are 127 list blocks and 1 marker blocks for each big block |
| 151 | # depot + 1 end of chain block |
| 152 | $self->{_list_blocks} = int(($self->{_big_blocks})/127) +1; |
| 153 | $self->{_root_start} = $self->{_big_blocks}; |
| 154 | } |
| 155 | |
| 156 | |
| 157 | ############################################################################### |
| 158 | # |
| 159 | # close() |
| 160 | # |
| 161 | # Write root entry, big block list and close the filehandle. |
| 162 | # This routine is used to explicitly close the open filehandle without |
| 163 | # having to wait for DESTROY. |
| 164 | # |
| 165 | sub close { |
| 166 | |
| 167 | my $self = shift; |
| 168 | |
| 169 | return if not $self->{_size_allowed}; |
| 170 | |
| 171 | $self->_write_padding() if not $self->{_biff_only}; |
| 172 | $self->_write_property_storage() if not $self->{_biff_only}; |
| 173 | $self->_write_big_block_depot() if not $self->{_biff_only}; |
| 174 | |
| 175 | my $close = 1; # Default to no error for external filehandles. |
| 176 | |
| 177 | # Close the filehandle if it was created internally. |
| 178 | $close = CORE::close($self->{_filehandle}) if $self->{_internal_fh}; |
| 179 | |
| 180 | $self->{_fileclosed} = 1; |
| 181 | |
| 182 | return $close; |
| 183 | } |
| 184 | |
| 185 | |
| 186 | ############################################################################### |
| 187 | # |
| 188 | # DESTROY() |
| 189 | # |
| 190 | # Close the filehandle if it hasn't already been explicitly closed. |
| 191 | # |
| 192 | sub DESTROY { |
| 193 | |
| 194 | my $self = shift; |
| 195 | |
| 196 | local ($@, $!, $^E, $?); |
| 197 | |
| 198 | $self->close() unless $self->{_fileclosed}; |
| 199 | } |
| 200 | |
| 201 | |
| 202 | ############################################################################### |
| 203 | # |
| 204 | # write($data) |
| 205 | # |
| 206 | # Write BIFF data to OLE file. |
| 207 | # |
| 208 | sub write { |
| 209 | |
| 210 | my $self = shift; |
| 211 | |
| 212 | # Protect print() from -l on the command line. |
| 213 | local $\ = undef; |
| 214 | print {$self->{_filehandle}} $_[0]; |
| 215 | } |
| 216 | |
| 217 | |
| 218 | ############################################################################### |
| 219 | # |
| 220 | # write_header() |
| 221 | # |
| 222 | # Write OLE header block. |
| 223 | # |
| 224 | sub write_header { |
| 225 | |
| 226 | my $self = shift; |
| 227 | |
| 228 | return if $self->{_biff_only}; |
| 229 | $self->_calculate_sizes(); |
| 230 | |
| 231 | my $root_start = $self->{_root_start}; |
| 232 | my $num_lists = $self->{_list_blocks}; |
| 233 | |
| 234 | my $id = pack("NN", 0xD0CF11E0, 0xA1B11AE1); |
| 235 | my $unknown1 = pack("VVVV", 0x00, 0x00, 0x00, 0x00); |
| 236 | my $unknown2 = pack("vv", 0x3E, 0x03); |
| 237 | my $unknown3 = pack("v", -2); |
| 238 | my $unknown4 = pack("v", 0x09); |
| 239 | my $unknown5 = pack("VVV", 0x06, 0x00, 0x00); |
| 240 | my $num_bbd_blocks = pack("V", $num_lists); |
| 241 | my $root_startblock = pack("V", $root_start); |
| 242 | my $unknown6 = pack("VV", 0x00, 0x1000); |
| 243 | my $sbd_startblock = pack("V", -2); |
| 244 | my $unknown7 = pack("VVV", 0x00, -2 ,0x00); |
| 245 | my $unused = pack("V", -1); |
| 246 | |
| 247 | # Protect print() from -l on the command line. |
| 248 | local $\ = undef; |
| 249 | |
| 250 | print {$self->{_filehandle}} $id; |
| 251 | print {$self->{_filehandle}} $unknown1; |
| 252 | print {$self->{_filehandle}} $unknown2; |
| 253 | print {$self->{_filehandle}} $unknown3; |
| 254 | print {$self->{_filehandle}} $unknown4; |
| 255 | print {$self->{_filehandle}} $unknown5; |
| 256 | print {$self->{_filehandle}} $num_bbd_blocks; |
| 257 | print {$self->{_filehandle}} $root_startblock; |
| 258 | print {$self->{_filehandle}} $unknown6; |
| 259 | print {$self->{_filehandle}} $sbd_startblock; |
| 260 | print {$self->{_filehandle}} $unknown7; |
| 261 | |
| 262 | for (1..$num_lists) { |
| 263 | $root_start++; |
| 264 | print {$self->{_filehandle}} pack("V", $root_start); |
| 265 | } |
| 266 | |
| 267 | for ($num_lists..108) { |
| 268 | print {$self->{_filehandle}} $unused; |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | |
| 273 | ############################################################################### |
| 274 | # |
| 275 | # _write_big_block_depot() |
| 276 | # |
| 277 | # Write big block depot. |
| 278 | # |
| 279 | sub _write_big_block_depot { |
| 280 | |
| 281 | my $self = shift; |
| 282 | my $num_blocks = $self->{_big_blocks}; |
| 283 | my $num_lists = $self->{_list_blocks}; |
| 284 | my $total_blocks = $num_lists *128; |
| 285 | my $used_blocks = $num_blocks + $num_lists +2; |
| 286 | |
| 287 | my $marker = pack("V", -3); |
| 288 | my $end_of_chain = pack("V", -2); |
| 289 | my $unused = pack("V", -1); |
| 290 | |
| 291 | |
| 292 | # Protect print() from -l on the command line. |
| 293 | local $\ = undef; |
| 294 | |
| 295 | for my $i (1..$num_blocks-1) { |
| 296 | print {$self->{_filehandle}} pack("V",$i); |
| 297 | } |
| 298 | |
| 299 | print {$self->{_filehandle}} $end_of_chain; |
| 300 | print {$self->{_filehandle}} $end_of_chain; |
| 301 | |
| 302 | for (1..$num_lists) { |
| 303 | print {$self->{_filehandle}} $marker; |
| 304 | } |
| 305 | |
| 306 | for ($used_blocks..$total_blocks) { |
| 307 | print {$self->{_filehandle}} $unused; |
| 308 | } |
| 309 | } |
| 310 | |
| 311 | |
| 312 | ############################################################################### |
| 313 | # |
| 314 | # _write_property_storage() |
| 315 | # |
| 316 | # Write property storage. TODO: add summary sheets |
| 317 | # |
| 318 | sub _write_property_storage { |
| 319 | |
| 320 | my $self = shift; |
| 321 | |
| 322 | my $rootsize = -2; |
| 323 | my $booksize = $self->{_booksize}; |
| 324 | |
| 325 | ################# name type dir start size |
| 326 | $self->_write_pps('Root Entry', 0x05, 1, -2, 0x00); |
| 327 | $self->_write_pps('Workbook', 0x02, -1, 0x00, $booksize); |
| 328 | $self->_write_pps('', 0x00, -1, 0x00, 0x0000); |
| 329 | $self->_write_pps('', 0x00, -1, 0x00, 0x0000); |
| 330 | } |
| 331 | |
| 332 | |
| 333 | ############################################################################### |
| 334 | # |
| 335 | # _write_pps() |
| 336 | # |
| 337 | # Write property sheet in property storage |
| 338 | # |
| 339 | sub _write_pps { |
| 340 | |
| 341 | my $self = shift; |
| 342 | |
| 343 | my $name = $_[0]; |
| 344 | my @name = (); |
| 345 | my $length = 0; |
| 346 | |
| 347 | if ($name ne '') { |
| 348 | $name = $_[0] . "\0"; |
| 349 | # Simulate a Unicode string |
| 350 | @name = map(ord, split('', $name)); |
| 351 | $length = length($name) * 2; |
| 352 | } |
| 353 | |
| 354 | my $rawname = pack("v*", @name); |
| 355 | my $zero = pack("C", 0); |
| 356 | |
| 357 | my $pps_sizeofname = pack("v", $length); #0x40 |
| 358 | my $pps_type = pack("v", $_[1]); #0x42 |
| 359 | my $pps_prev = pack("V", -1); #0x44 |
| 360 | my $pps_next = pack("V", -1); #0x48 |
| 361 | my $pps_dir = pack("V", $_[2]); #0x4c |
| 362 | |
| 363 | my $unknown1 = pack("V", 0); |
| 364 | |
| 365 | my $pps_ts1s = pack("V", 0); #0x64 |
| 366 | my $pps_ts1d = pack("V", 0); #0x68 |
| 367 | my $pps_ts2s = pack("V", 0); #0x6c |
| 368 | my $pps_ts2d = pack("V", 0); #0x70 |
| 369 | my $pps_sb = pack("V", $_[3]); #0x74 |
| 370 | my $pps_size = pack("V", $_[4]); #0x78 |
| 371 | |
| 372 | |
| 373 | # Protect print() from -l on the command line. |
| 374 | local $\ = undef; |
| 375 | |
| 376 | print {$self->{_filehandle}} $rawname; |
| 377 | print {$self->{_filehandle}} $zero x (64 -$length); |
| 378 | print {$self->{_filehandle}} $pps_sizeofname; |
| 379 | print {$self->{_filehandle}} $pps_type; |
| 380 | print {$self->{_filehandle}} $pps_prev; |
| 381 | print {$self->{_filehandle}} $pps_next; |
| 382 | print {$self->{_filehandle}} $pps_dir; |
| 383 | print {$self->{_filehandle}} $unknown1 x 5; |
| 384 | print {$self->{_filehandle}} $pps_ts1s; |
| 385 | print {$self->{_filehandle}} $pps_ts1d; |
| 386 | print {$self->{_filehandle}} $pps_ts2d; |
| 387 | print {$self->{_filehandle}} $pps_ts2d; |
| 388 | print {$self->{_filehandle}} $pps_sb; |
| 389 | print {$self->{_filehandle}} $pps_size; |
| 390 | print {$self->{_filehandle}} $unknown1; |
| 391 | } |
| 392 | |
| 393 | |
| 394 | ############################################################################### |
| 395 | # |
| 396 | # _write_padding() |
| 397 | # |
| 398 | # Pad the end of the file |
| 399 | # |
| 400 | sub _write_padding { |
| 401 | |
| 402 | my $self = shift; |
| 403 | my $biffsize = $self->{_biffsize}; |
| 404 | my $min_size; |
| 405 | |
| 406 | if ($biffsize < 4096) { |
| 407 | $min_size = 4096; |
| 408 | } |
| 409 | else { |
| 410 | $min_size = 512; |
| 411 | } |
| 412 | |
| 413 | # Protect print() from -l on the command line. |
| 414 | local $\ = undef; |
| 415 | |
| 416 | if ($biffsize % $min_size != 0) { |
| 417 | my $padding = $min_size - ($biffsize % $min_size); |
| 418 | print {$self->{_filehandle}} "\0" x $padding; |
| 419 | } |
| 420 | } |
| 421 | |
| 422 | |
| 423 | 1; |
| 424 | |
| 425 | |
| 426 | __END__ |
| 427 | |
| 428 | |
| 429 | =head1 NAME |
| 430 | |
| 431 | OLEwriter - A writer class to store BIFF data in a OLE compound storage file. |
| 432 | |
| 433 | =head1 SYNOPSIS |
| 434 | |
| 435 | See the documentation for Spreadsheet::WriteExcel |
| 436 | |
| 437 | =head1 DESCRIPTION |
| 438 | |
| 439 | This module is used in conjunction with Spreadsheet::WriteExcel. |
| 440 | |
| 441 | =head1 AUTHOR |
| 442 | |
| 443 | John McNamara jmcnamara@cpan.org |
| 444 | |
| 445 | =head1 COPYRIGHT |
| 446 | |
| 447 | © MM-MMX, John McNamara. |
| 448 | |
| 449 | All Rights Reserved. This module is free software. It may be used, redistributed and/or modified under the same terms as Perl itself. |