|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 /* 00003 * Module written/ported by Xavier Noguer <xnoguer@rezebra.com> 00004 * 00005 * The majority of this is _NOT_ my code. I simply ported it from the 00006 * PERL Spreadsheet::WriteExcel module. 00007 * 00008 * The author of the Spreadsheet::WriteExcel module is John McNamara 00009 * <jmcnamara@cpan.org> 00010 * 00011 * I _DO_ maintain this code, and John McNamara has nothing to do with the 00012 * porting of this code to PHP. Any questions directly related to this 00013 * class library should be directed to me. 00014 * 00015 * License Information: 00016 * 00017 * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets 00018 * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com 00019 * 00020 * This library is free software; you can redistribute it and/or 00021 * modify it under the terms of the GNU Lesser General Public 00022 * License as published by the Free Software Foundation; either 00023 * version 2.1 of the License, or (at your option) any later version. 00024 * 00025 * This library is distributed in the hope that it will be useful, 00026 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00027 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00028 * Lesser General Public License for more details. 00029 * 00030 * You should have received a copy of the GNU Lesser General Public 00031 * License along with this library; if not, write to the Free Software 00032 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 00033 */ 00034 00035 require_once 'Spreadsheet/Excel/Writer/Format.php'; 00036 require_once 'Spreadsheet/Excel/Writer/BIFFwriter.php'; 00037 require_once 'Spreadsheet/Excel/Writer/Worksheet.php'; 00038 require_once 'Spreadsheet/Excel/Writer/Parser.php'; 00039 require_once 'OLE/PPS/Root.php'; 00040 require_once 'OLE/PPS/File.php'; 00041 00050 class Spreadsheet_Excel_Writer_Workbook extends Spreadsheet_Excel_Writer_BIFFwriter 00051 { 00056 var $_filename; 00057 00062 var $_parser; 00063 00068 var $_1904; 00069 00074 var $_activesheet; 00075 00080 var $_firstsheet; 00081 00086 var $_selected; 00087 00092 var $_xf_index; 00093 00099 var $_fileclosed; 00100 00106 var $_biffsize; 00107 00112 var $_sheetname; 00113 00118 var $_tmp_format; 00119 00124 var $_worksheets; 00125 00130 var $_sheetnames; 00131 00136 var $_formats; 00137 00142 var $_palette; 00143 00148 var $_url_format; 00149 00154 var $_codepage; 00155 00160 var $_country_code; 00161 00166 var $_tmp_dir; 00167 00172 var $_string_sizeinfo_size; 00173 00180 function Spreadsheet_Excel_Writer_Workbook($filename) 00181 { 00182 // It needs to call its parent's constructor explicitly 00183 $this->Spreadsheet_Excel_Writer_BIFFwriter(); 00184 00185 $this->_filename = $filename; 00186 $this->_parser = new Spreadsheet_Excel_Writer_Parser($this->_byte_order, $this->_BIFF_version); 00187 $this->_1904 = 0; 00188 $this->_activesheet = 0; 00189 $this->_firstsheet = 0; 00190 $this->_selected = 0; 00191 $this->_xf_index = 16; // 15 style XF's and 1 cell XF. 00192 $this->_fileclosed = 0; 00193 $this->_biffsize = 0; 00194 $this->_sheetname = 'Sheet'; 00195 $this->_tmp_format = new Spreadsheet_Excel_Writer_Format($this->_BIFF_version); 00196 $this->_worksheets = array(); 00197 $this->_sheetnames = array(); 00198 $this->_formats = array(); 00199 $this->_palette = array(); 00200 $this->_codepage = 0x04E4; // FIXME: should change for BIFF8 00201 $this->_country_code = -1; 00202 $this->_string_sizeinfo = 3; 00203 00204 // Add the default format for hyperlinks 00205 $this->_url_format =& $this->addFormat(array('color' => 'blue', 'underline' => 1)); 00206 $this->_str_total = 0; 00207 $this->_str_unique = 0; 00208 $this->_str_table = array(); 00209 $this->_setPaletteXl97(); 00210 $this->_tmp_dir = ''; 00211 } 00212 00220 function close() 00221 { 00222 if ($this->_fileclosed) { // Prevent close() from being called twice. 00223 return true; 00224 } 00225 $res = $this->_storeWorkbook(); 00226 if ($this->isError($res)) { 00227 return $this->raiseError($res->getMessage()); 00228 } 00229 $this->_fileclosed = 1; 00230 return true; 00231 } 00232 00242 function sheets() 00243 { 00244 return $this->worksheets(); 00245 } 00246 00254 function worksheets() 00255 { 00256 return $this->_worksheets; 00257 } 00258 00269 function setVersion($version) 00270 { 00271 if ($version == 8) { // only accept version 8 00272 $version = 0x0600; 00273 $this->_BIFF_version = $version; 00274 // change BIFFwriter limit for CONTINUE records 00275 $this->_limit = 8228; 00276 $this->_tmp_format->_BIFF_version = $version; 00277 $this->_url_format->_BIFF_version = $version; 00278 $this->_parser->_BIFF_version = $version; 00279 00280 $total_worksheets = count($this->_worksheets); 00281 // change version for all worksheets too 00282 for ($i = 0; $i < $total_worksheets; $i++) { 00283 $this->_worksheets[$i]->_BIFF_version = $version; 00284 } 00285 00286 $total_formats = count($this->_formats); 00287 // change version for all formats too 00288 for ($i = 0; $i < $total_formats; $i++) { 00289 $this->_formats[$i]->_BIFF_version = $version; 00290 } 00291 } 00292 } 00293 00301 function setCountry($code) 00302 { 00303 $this->_country_code = $code; 00304 } 00305 00316 function &addWorksheet($name = '') 00317 { 00318 $index = count($this->_worksheets); 00319 $sheetname = $this->_sheetname; 00320 00321 if ($name == '') { 00322 $name = $sheetname.($index+1); 00323 } 00324 00325 // Check that sheetname is <= 31 chars (Excel limit before BIFF8). 00326 if ($this->_BIFF_version != 0x0600) 00327 { 00328 if (strlen($name) > 31) { 00329 return $this->raiseError("Sheetname $name must be <= 31 chars"); 00330 } 00331 } 00332 00333 // Check that the worksheet name doesn't already exist: a fatal Excel error. 00334 $total_worksheets = count($this->_worksheets); 00335 for ($i = 0; $i < $total_worksheets; $i++) { 00336 if ($this->_worksheets[$i]->getName() == $name) { 00337 return $this->raiseError("Worksheet '$name' already exists"); 00338 } 00339 } 00340 00341 $worksheet = new Spreadsheet_Excel_Writer_Worksheet($this->_BIFF_version, 00342 $name, $index, 00343 $this->_activesheet, $this->_firstsheet, 00344 $this->_str_total, $this->_str_unique, 00345 $this->_str_table, $this->_url_format, 00346 $this->_parser); 00347 00348 $this->_worksheets[$index] = &$worksheet; // Store ref for iterator 00349 $this->_sheetnames[$index] = $name; // Store EXTERNSHEET names 00350 $this->_parser->setExtSheet($name, $index); // Register worksheet name with parser 00351 return $worksheet; 00352 } 00353 00362 function &addFormat($properties = array()) 00363 { 00364 $format = new Spreadsheet_Excel_Writer_Format($this->_BIFF_version, $this->_xf_index, $properties); 00365 $this->_xf_index += 1; 00366 $this->_formats[] = &$format; 00367 return $format; 00368 } 00369 00376 function &addValidator() 00377 { 00378 include_once 'Spreadsheet/Excel/Writer/Validator.php'; 00379 /* FIXME: check for successful inclusion*/ 00380 $valid = new Spreadsheet_Excel_Writer_Validator($this->_parser); 00381 return $valid; 00382 } 00383 00394 function setCustomColor($index, $red, $green, $blue) 00395 { 00396 // Match a HTML #xxyyzz style parameter 00397 /*if (defined $_[1] and $_[1] =~ /^#(\w\w)(\w\w)(\w\w)/ ) { 00398 @_ = ($_[0], hex $1, hex $2, hex $3); 00399 }*/ 00400 00401 // Check that the colour index is the right range 00402 if ($index < 8 or $index > 64) { 00403 // TODO: assign real error codes 00404 return $this->raiseError("Color index $index outside range: 8 <= index <= 64"); 00405 } 00406 00407 // Check that the colour components are in the right range 00408 if (($red < 0 or $red > 255) || 00409 ($green < 0 or $green > 255) || 00410 ($blue < 0 or $blue > 255)) 00411 { 00412 return $this->raiseError("Color component outside range: 0 <= color <= 255"); 00413 } 00414 00415 $index -= 8; // Adjust colour index (wingless dragonfly) 00416 00417 // Set the RGB value 00418 $this->_palette[$index] = array($red, $green, $blue, 0); 00419 return($index + 8); 00420 } 00421 00427 function _setPaletteXl97() 00428 { 00429 $this->_palette = array( 00430 array(0x00, 0x00, 0x00, 0x00), // 8 00431 array(0xff, 0xff, 0xff, 0x00), // 9 00432 array(0xff, 0x00, 0x00, 0x00), // 10 00433 array(0x00, 0xff, 0x00, 0x00), // 11 00434 array(0x00, 0x00, 0xff, 0x00), // 12 00435 array(0xff, 0xff, 0x00, 0x00), // 13 00436 array(0xff, 0x00, 0xff, 0x00), // 14 00437 array(0x00, 0xff, 0xff, 0x00), // 15 00438 array(0x80, 0x00, 0x00, 0x00), // 16 00439 array(0x00, 0x80, 0x00, 0x00), // 17 00440 array(0x00, 0x00, 0x80, 0x00), // 18 00441 array(0x80, 0x80, 0x00, 0x00), // 19 00442 array(0x80, 0x00, 0x80, 0x00), // 20 00443 array(0x00, 0x80, 0x80, 0x00), // 21 00444 array(0xc0, 0xc0, 0xc0, 0x00), // 22 00445 array(0x80, 0x80, 0x80, 0x00), // 23 00446 array(0x99, 0x99, 0xff, 0x00), // 24 00447 array(0x99, 0x33, 0x66, 0x00), // 25 00448 array(0xff, 0xff, 0xcc, 0x00), // 26 00449 array(0xcc, 0xff, 0xff, 0x00), // 27 00450 array(0x66, 0x00, 0x66, 0x00), // 28 00451 array(0xff, 0x80, 0x80, 0x00), // 29 00452 array(0x00, 0x66, 0xcc, 0x00), // 30 00453 array(0xcc, 0xcc, 0xff, 0x00), // 31 00454 array(0x00, 0x00, 0x80, 0x00), // 32 00455 array(0xff, 0x00, 0xff, 0x00), // 33 00456 array(0xff, 0xff, 0x00, 0x00), // 34 00457 array(0x00, 0xff, 0xff, 0x00), // 35 00458 array(0x80, 0x00, 0x80, 0x00), // 36 00459 array(0x80, 0x00, 0x00, 0x00), // 37 00460 array(0x00, 0x80, 0x80, 0x00), // 38 00461 array(0x00, 0x00, 0xff, 0x00), // 39 00462 array(0x00, 0xcc, 0xff, 0x00), // 40 00463 array(0xcc, 0xff, 0xff, 0x00), // 41 00464 array(0xcc, 0xff, 0xcc, 0x00), // 42 00465 array(0xff, 0xff, 0x99, 0x00), // 43 00466 array(0x99, 0xcc, 0xff, 0x00), // 44 00467 array(0xff, 0x99, 0xcc, 0x00), // 45 00468 array(0xcc, 0x99, 0xff, 0x00), // 46 00469 array(0xff, 0xcc, 0x99, 0x00), // 47 00470 array(0x33, 0x66, 0xff, 0x00), // 48 00471 array(0x33, 0xcc, 0xcc, 0x00), // 49 00472 array(0x99, 0xcc, 0x00, 0x00), // 50 00473 array(0xff, 0xcc, 0x00, 0x00), // 51 00474 array(0xff, 0x99, 0x00, 0x00), // 52 00475 array(0xff, 0x66, 0x00, 0x00), // 53 00476 array(0x66, 0x66, 0x99, 0x00), // 54 00477 array(0x96, 0x96, 0x96, 0x00), // 55 00478 array(0x00, 0x33, 0x66, 0x00), // 56 00479 array(0x33, 0x99, 0x66, 0x00), // 57 00480 array(0x00, 0x33, 0x00, 0x00), // 58 00481 array(0x33, 0x33, 0x00, 0x00), // 59 00482 array(0x99, 0x33, 0x00, 0x00), // 60 00483 array(0x99, 0x33, 0x66, 0x00), // 61 00484 array(0x33, 0x33, 0x99, 0x00), // 62 00485 array(0x33, 0x33, 0x33, 0x00), // 63 00486 ); 00487 } 00488 00496 function _storeWorkbook() 00497 { 00498 // Ensure that at least one worksheet has been selected. 00499 if ($this->_activesheet == 0) { 00500 $this->_worksheets[0]->selected = 1; 00501 } 00502 00503 // Calculate the number of selected worksheet tabs and call the finalization 00504 // methods for each worksheet 00505 $total_worksheets = count($this->_worksheets); 00506 for ($i = 0; $i < $total_worksheets; $i++) { 00507 if ($this->_worksheets[$i]->selected) { 00508 $this->_selected++; 00509 } 00510 $this->_worksheets[$i]->close($this->_sheetnames); 00511 } 00512 00513 // Add Workbook globals 00514 $this->_storeBof(0x0005); 00515 $this->_storeCodepage(); 00516 if ($this->_BIFF_version == 0x0600) { 00517 $this->_storeWindow1(); 00518 } 00519 if ($this->_BIFF_version == 0x0500) { 00520 $this->_storeExterns(); // For print area and repeat rows 00521 } 00522 $this->_storeNames(); // For print area and repeat rows 00523 if ($this->_BIFF_version == 0x0500) { 00524 $this->_storeWindow1(); 00525 } 00526 $this->_storeDatemode(); 00527 $this->_storeAllFonts(); 00528 $this->_storeAllNumFormats(); 00529 $this->_storeAllXfs(); 00530 $this->_storeAllStyles(); 00531 $this->_storePalette(); 00532 $this->_calcSheetOffsets(); 00533 00534 // Add BOUNDSHEET records 00535 for ($i = 0; $i < $total_worksheets; $i++) { 00536 $this->_storeBoundsheet($this->_worksheets[$i]->name,$this->_worksheets[$i]->offset); 00537 } 00538 00539 if ($this->_country_code != -1) { 00540 $this->_storeCountry(); 00541 } 00542 00543 if ($this->_BIFF_version == 0x0600) { 00544 //$this->_storeSupbookInternal(); 00545 /* TODO: store external SUPBOOK records and XCT and CRN records 00546 in case of external references for BIFF8 */ 00547 //$this->_storeExternsheetBiff8(); 00548 $this->_storeSharedStringsTable(); 00549 } 00550 00551 // End Workbook globals 00552 $this->_storeEof(); 00553 00554 // Store the workbook in an OLE container 00555 $res = $this->_storeOLEFile(); 00556 if ($this->isError($res)) { 00557 return $this->raiseError($res->getMessage()); 00558 } 00559 return true; 00560 } 00561 00569 function setTempDir($dir) 00570 { 00571 if (is_dir($dir)) { 00572 $this->_tmp_dir = $dir; 00573 return true; 00574 } 00575 return false; 00576 } 00577 00584 function _storeOLEFile() 00585 { 00586 $OLE = new OLE_PPS_File(OLE::Asc2Ucs('Book')); 00587 if ($this->_tmp_dir != '') { 00588 $OLE->setTempDir($this->_tmp_dir); 00589 } 00590 $res = $OLE->init(); 00591 if ($this->isError($res)) { 00592 return $this->raiseError("OLE Error: ".$res->getMessage()); 00593 } 00594 $OLE->append($this->_data); 00595 00596 $total_worksheets = count($this->_worksheets); 00597 for ($i = 0; $i < $total_worksheets; $i++) { 00598 while ($tmp = $this->_worksheets[$i]->getData()) { 00599 $OLE->append($tmp); 00600 } 00601 } 00602 00603 $root = new OLE_PPS_Root(time(), time(), array($OLE)); 00604 if ($this->_tmp_dir != '') { 00605 $root->setTempDir($this->_tmp_dir); 00606 } 00607 00608 $res = $root->save($this->_filename); 00609 if ($this->isError($res)) { 00610 return $this->raiseError("OLE Error: ".$res->getMessage()); 00611 } 00612 return true; 00613 } 00614 00620 function _calcSheetOffsets() 00621 { 00622 if ($this->_BIFF_version == 0x0600) { 00623 $boundsheet_length = 12; // fixed length for a BOUNDSHEET record 00624 } else { 00625 $boundsheet_length = 11; 00626 } 00627 $EOF = 4; 00628 $offset = $this->_datasize; 00629 00630 if ($this->_BIFF_version == 0x0600) { 00631 // add the length of the SST 00632 /* TODO: check this works for a lot of strings (> 8224 bytes) */ 00633 $offset += $this->_calculateSharedStringsSizes(); 00634 if ($this->_country_code != -1) { 00635 $offset += 8; // adding COUNTRY record 00636 } 00637 // add the lenght of SUPBOOK, EXTERNSHEET and NAME records 00638 //$offset += 8; // FIXME: calculate real value when storing the records 00639 } 00640 $total_worksheets = count($this->_worksheets); 00641 // add the length of the BOUNDSHEET records 00642 for ($i = 0; $i < $total_worksheets; $i++) { 00643 $offset += $boundsheet_length + strlen($this->_worksheets[$i]->name); 00644 } 00645 $offset += $EOF; 00646 00647 for ($i = 0; $i < $total_worksheets; $i++) { 00648 $this->_worksheets[$i]->offset = $offset; 00649 $offset += $this->_worksheets[$i]->_datasize; 00650 } 00651 $this->_biffsize = $offset; 00652 } 00653 00659 function _storeAllFonts() 00660 { 00661 // tmp_format is added by the constructor. We use this to write the default XF's 00662 $format = $this->_tmp_format; 00663 $font = $format->getFont(); 00664 00665 // Note: Fonts are 0-indexed. According to the SDK there is no index 4, 00666 // so the following fonts are 0, 1, 2, 3, 5 00667 // 00668 for ($i = 1; $i <= 5; $i++){ 00669 $this->_append($font); 00670 } 00671 00672 // Iterate through the XF objects and write a FONT record if it isn't the 00673 // same as the default FONT and if it hasn't already been used. 00674 // 00675 $fonts = array(); 00676 $index = 6; // The first user defined FONT 00677 00678 $key = $format->getFontKey(); // The default font from _tmp_format 00679 $fonts[$key] = 0; // Index of the default font 00680 00681 $total_formats = count($this->_formats); 00682 for ($i = 0; $i < $total_formats; $i++) { 00683 $key = $this->_formats[$i]->getFontKey(); 00684 if (isset($fonts[$key])) { 00685 // FONT has already been used 00686 $this->_formats[$i]->font_index = $fonts[$key]; 00687 } else { 00688 // Add a new FONT record 00689 $fonts[$key] = $index; 00690 $this->_formats[$i]->font_index = $index; 00691 $index++; 00692 $font = $this->_formats[$i]->getFont(); 00693 $this->_append($font); 00694 } 00695 } 00696 } 00697 00703 function _storeAllNumFormats() 00704 { 00705 // Leaning num_format syndrome 00706 $hash_num_formats = array(); 00707 $num_formats = array(); 00708 $index = 164; 00709 00710 // Iterate through the XF objects and write a FORMAT record if it isn't a 00711 // built-in format type and if the FORMAT string hasn't already been used. 00712 $total_formats = count($this->_formats); 00713 for ($i = 0; $i < $total_formats; $i++) { 00714 $num_format = $this->_formats[$i]->_num_format; 00715 00716 // Check if $num_format is an index to a built-in format. 00717 // Also check for a string of zeros, which is a valid format string 00718 // but would evaluate to zero. 00719 // 00720 if (!preg_match("/^0+\d/", $num_format)) { 00721 if (preg_match("/^\d+$/", $num_format)) { // built-in format 00722 continue; 00723 } 00724 } 00725 00726 if (isset($hash_num_formats[$num_format])) { 00727 // FORMAT has already been used 00728 $this->_formats[$i]->_num_format = $hash_num_formats[$num_format]; 00729 } else{ 00730 // Add a new FORMAT 00731 $hash_num_formats[$num_format] = $index; 00732 $this->_formats[$i]->_num_format = $index; 00733 array_push($num_formats,$num_format); 00734 $index++; 00735 } 00736 } 00737 00738 // Write the new FORMAT records starting from 0xA4 00739 $index = 164; 00740 foreach ($num_formats as $num_format) { 00741 $this->_storeNumFormat($num_format,$index); 00742 $index++; 00743 } 00744 } 00745 00751 function _storeAllXfs() 00752 { 00753 // _tmp_format is added by the constructor. We use this to write the default XF's 00754 // The default font index is 0 00755 // 00756 $format = $this->_tmp_format; 00757 for ($i = 0; $i <= 14; $i++) { 00758 $xf = $format->getXf('style'); // Style XF 00759 $this->_append($xf); 00760 } 00761 00762 $xf = $format->getXf('cell'); // Cell XF 00763 $this->_append($xf); 00764 00765 // User defined XFs 00766 $total_formats = count($this->_formats); 00767 for ($i = 0; $i < $total_formats; $i++) { 00768 $xf = $this->_formats[$i]->getXf('cell'); 00769 $this->_append($xf); 00770 } 00771 } 00772 00778 function _storeAllStyles() 00779 { 00780 $this->_storeStyle(); 00781 } 00782 00789 function _storeExterns() 00790 { 00791 // Create EXTERNCOUNT with number of worksheets 00792 $this->_storeExterncount(count($this->_worksheets)); 00793 00794 // Create EXTERNSHEET for each worksheet 00795 foreach ($this->_sheetnames as $sheetname) { 00796 $this->_storeExternsheet($sheetname); 00797 } 00798 } 00799 00805 function _storeNames() 00806 { 00807 // Create the print area NAME records 00808 $total_worksheets = count($this->_worksheets); 00809 for ($i = 0; $i < $total_worksheets; $i++) { 00810 // Write a Name record if the print area has been defined 00811 if (isset($this->_worksheets[$i]->print_rowmin)) { 00812 $this->_storeNameShort( 00813 $this->_worksheets[$i]->index, 00814 0x06, // NAME type 00815 $this->_worksheets[$i]->print_rowmin, 00816 $this->_worksheets[$i]->print_rowmax, 00817 $this->_worksheets[$i]->print_colmin, 00818 $this->_worksheets[$i]->print_colmax 00819 ); 00820 } 00821 } 00822 00823 // Create the print title NAME records 00824 $total_worksheets = count($this->_worksheets); 00825 for ($i = 0; $i < $total_worksheets; $i++) { 00826 $rowmin = $this->_worksheets[$i]->title_rowmin; 00827 $rowmax = $this->_worksheets[$i]->title_rowmax; 00828 $colmin = $this->_worksheets[$i]->title_colmin; 00829 $colmax = $this->_worksheets[$i]->title_colmax; 00830 00831 // Determine if row + col, row, col or nothing has been defined 00832 // and write the appropriate record 00833 // 00834 if (isset($rowmin) && isset($colmin)) { 00835 // Row and column titles have been defined. 00836 // Row title has been defined. 00837 $this->_storeNameLong( 00838 $this->_worksheets[$i]->index, 00839 0x07, // NAME type 00840 $rowmin, 00841 $rowmax, 00842 $colmin, 00843 $colmax 00844 ); 00845 } elseif (isset($rowmin)) { 00846 // Row title has been defined. 00847 $this->_storeNameShort( 00848 $this->_worksheets[$i]->index, 00849 0x07, // NAME type 00850 $rowmin, 00851 $rowmax, 00852 0x00, 00853 0xff 00854 ); 00855 } elseif (isset($colmin)) { 00856 // Column title has been defined. 00857 $this->_storeNameShort( 00858 $this->_worksheets[$i]->index, 00859 0x07, // NAME type 00860 0x0000, 00861 0x3fff, 00862 $colmin, 00863 $colmax 00864 ); 00865 } else { 00866 // Print title hasn't been defined. 00867 } 00868 } 00869 } 00870 00871 00872 00873 00874 /****************************************************************************** 00875 * 00876 * BIFF RECORDS 00877 * 00878 */ 00879 00885 function _storeCodepage() 00886 { 00887 $record = 0x0042; // Record identifier 00888 $length = 0x0002; // Number of bytes to follow 00889 $cv = $this->_codepage; // The code page 00890 00891 $header = pack('vv', $record, $length); 00892 $data = pack('v', $cv); 00893 00894 $this->_append($header . $data); 00895 } 00896 00902 function _storeWindow1() 00903 { 00904 $record = 0x003D; // Record identifier 00905 $length = 0x0012; // Number of bytes to follow 00906 00907 $xWn = 0x0000; // Horizontal position of window 00908 $yWn = 0x0000; // Vertical position of window 00909 $dxWn = 0x25BC; // Width of window 00910 $dyWn = 0x1572; // Height of window 00911 00912 $grbit = 0x0038; // Option flags 00913 $ctabsel = $this->_selected; // Number of workbook tabs selected 00914 $wTabRatio = 0x0258; // Tab to scrollbar ratio 00915 00916 $itabFirst = $this->_firstsheet; // 1st displayed worksheet 00917 $itabCur = $this->_activesheet; // Active worksheet 00918 00919 $header = pack("vv", $record, $length); 00920 $data = pack("vvvvvvvvv", $xWn, $yWn, $dxWn, $dyWn, 00921 $grbit, 00922 $itabCur, $itabFirst, 00923 $ctabsel, $wTabRatio); 00924 $this->_append($header . $data); 00925 } 00926 00935 function _storeBoundsheet($sheetname,$offset) 00936 { 00937 $record = 0x0085; // Record identifier 00938 if ($this->_BIFF_version == 0x0600) { 00939 $length = 0x08 + strlen($sheetname); // Number of bytes to follow 00940 } else { 00941 $length = 0x07 + strlen($sheetname); // Number of bytes to follow 00942 } 00943 00944 $grbit = 0x0000; // Visibility and sheet type 00945 $cch = strlen($sheetname); // Length of sheet name 00946 00947 $header = pack("vv", $record, $length); 00948 if ($this->_BIFF_version == 0x0600) { 00949 $data = pack("Vvv", $offset, $grbit, $cch); 00950 } else { 00951 $data = pack("VvC", $offset, $grbit, $cch); 00952 } 00953 $this->_append($header.$data.$sheetname); 00954 } 00955 00961 function _storeSupbookInternal() 00962 { 00963 $record = 0x01AE; // Record identifier 00964 $length = 0x0004; // Bytes to follow 00965 00966 $header = pack("vv", $record, $length); 00967 $data = pack("vv", count($this->_worksheets), 0x0104); 00968 $this->_append($header . $data); 00969 } 00970 00978 function _storeExternsheetBiff8() 00979 { 00980 $total_references = count($this->_parser->_references); 00981 $record = 0x0017; // Record identifier 00982 $length = 2 + 6 * $total_references; // Number of bytes to follow 00983 00984 $supbook_index = 0; // FIXME: only using internal SUPBOOK record 00985 $header = pack("vv", $record, $length); 00986 $data = pack('v', $total_references); 00987 for ($i = 0; $i < $total_references; $i++) { 00988 $data .= $this->_parser->_references[$i]; 00989 } 00990 $this->_append($header . $data); 00991 } 00992 00998 function _storeStyle() 00999 { 01000 $record = 0x0293; // Record identifier 01001 $length = 0x0004; // Bytes to follow 01002 01003 $ixfe = 0x8000; // Index to style XF 01004 $BuiltIn = 0x00; // Built-in style 01005 $iLevel = 0xff; // Outline style level 01006 01007 $header = pack("vv", $record, $length); 01008 $data = pack("vCC", $ixfe, $BuiltIn, $iLevel); 01009 $this->_append($header . $data); 01010 } 01011 01012 01020 function _storeNumFormat($format, $ifmt) 01021 { 01022 $record = 0x041E; // Record identifier 01023 01024 if ($this->_BIFF_version == 0x0600) { 01025 $length = 5 + strlen($format); // Number of bytes to follow 01026 $encoding = 0x0; 01027 } elseif ($this->_BIFF_version == 0x0500) { 01028 $length = 3 + strlen($format); // Number of bytes to follow 01029 } 01030 01031 $cch = strlen($format); // Length of format string 01032 01033 $header = pack("vv", $record, $length); 01034 if ($this->_BIFF_version == 0x0600) { 01035 $data = pack("vvC", $ifmt, $cch, $encoding); 01036 } elseif ($this->_BIFF_version == 0x0500) { 01037 $data = pack("vC", $ifmt, $cch); 01038 } 01039 $this->_append($header . $data . $format); 01040 } 01041 01047 function _storeDatemode() 01048 { 01049 $record = 0x0022; // Record identifier 01050 $length = 0x0002; // Bytes to follow 01051 01052 $f1904 = $this->_1904; // Flag for 1904 date system 01053 01054 $header = pack("vv", $record, $length); 01055 $data = pack("v", $f1904); 01056 $this->_append($header . $data); 01057 } 01058 01059 01073 function _storeExterncount($cxals) 01074 { 01075 $record = 0x0016; // Record identifier 01076 $length = 0x0002; // Number of bytes to follow 01077 01078 $header = pack("vv", $record, $length); 01079 $data = pack("v", $cxals); 01080 $this->_append($header . $data); 01081 } 01082 01083 01094 function _storeExternsheet($sheetname) 01095 { 01096 $record = 0x0017; // Record identifier 01097 $length = 0x02 + strlen($sheetname); // Number of bytes to follow 01098 01099 $cch = strlen($sheetname); // Length of sheet name 01100 $rgch = 0x03; // Filename encoding 01101 01102 $header = pack("vv", $record, $length); 01103 $data = pack("CC", $cch, $rgch); 01104 $this->_append($header . $data . $sheetname); 01105 } 01106 01107 01120 function _storeNameShort($index, $type, $rowmin, $rowmax, $colmin, $colmax) 01121 { 01122 $record = 0x0018; // Record identifier 01123 $length = 0x0024; // Number of bytes to follow 01124 01125 $grbit = 0x0020; // Option flags 01126 $chKey = 0x00; // Keyboard shortcut 01127 $cch = 0x01; // Length of text name 01128 $cce = 0x0015; // Length of text definition 01129 $ixals = $index + 1; // Sheet index 01130 $itab = $ixals; // Equal to ixals 01131 $cchCustMenu = 0x00; // Length of cust menu text 01132 $cchDescription = 0x00; // Length of description text 01133 $cchHelptopic = 0x00; // Length of help topic text 01134 $cchStatustext = 0x00; // Length of status bar text 01135 $rgch = $type; // Built-in name type 01136 01137 $unknown03 = 0x3b; 01138 $unknown04 = 0xffff-$index; 01139 $unknown05 = 0x0000; 01140 $unknown06 = 0x0000; 01141 $unknown07 = 0x1087; 01142 $unknown08 = 0x8005; 01143 01144 $header = pack("vv", $record, $length); 01145 $data = pack("v", $grbit); 01146 $data .= pack("C", $chKey); 01147 $data .= pack("C", $cch); 01148 $data .= pack("v", $cce); 01149 $data .= pack("v", $ixals); 01150 $data .= pack("v", $itab); 01151 $data .= pack("C", $cchCustMenu); 01152 $data .= pack("C", $cchDescription); 01153 $data .= pack("C", $cchHelptopic); 01154 $data .= pack("C", $cchStatustext); 01155 $data .= pack("C", $rgch); 01156 $data .= pack("C", $unknown03); 01157 $data .= pack("v", $unknown04); 01158 $data .= pack("v", $unknown05); 01159 $data .= pack("v", $unknown06); 01160 $data .= pack("v", $unknown07); 01161 $data .= pack("v", $unknown08); 01162 $data .= pack("v", $index); 01163 $data .= pack("v", $index); 01164 $data .= pack("v", $rowmin); 01165 $data .= pack("v", $rowmax); 01166 $data .= pack("C", $colmin); 01167 $data .= pack("C", $colmax); 01168 $this->_append($header . $data); 01169 } 01170 01171 01186 function _storeNameLong($index, $type, $rowmin, $rowmax, $colmin, $colmax) 01187 { 01188 $record = 0x0018; // Record identifier 01189 $length = 0x003d; // Number of bytes to follow 01190 $grbit = 0x0020; // Option flags 01191 $chKey = 0x00; // Keyboard shortcut 01192 $cch = 0x01; // Length of text name 01193 $cce = 0x002e; // Length of text definition 01194 $ixals = $index + 1; // Sheet index 01195 $itab = $ixals; // Equal to ixals 01196 $cchCustMenu = 0x00; // Length of cust menu text 01197 $cchDescription = 0x00; // Length of description text 01198 $cchHelptopic = 0x00; // Length of help topic text 01199 $cchStatustext = 0x00; // Length of status bar text 01200 $rgch = $type; // Built-in name type 01201 01202 $unknown01 = 0x29; 01203 $unknown02 = 0x002b; 01204 $unknown03 = 0x3b; 01205 $unknown04 = 0xffff-$index; 01206 $unknown05 = 0x0000; 01207 $unknown06 = 0x0000; 01208 $unknown07 = 0x1087; 01209 $unknown08 = 0x8008; 01210 01211 $header = pack("vv", $record, $length); 01212 $data = pack("v", $grbit); 01213 $data .= pack("C", $chKey); 01214 $data .= pack("C", $cch); 01215 $data .= pack("v", $cce); 01216 $data .= pack("v", $ixals); 01217 $data .= pack("v", $itab); 01218 $data .= pack("C", $cchCustMenu); 01219 $data .= pack("C", $cchDescription); 01220 $data .= pack("C", $cchHelptopic); 01221 $data .= pack("C", $cchStatustext); 01222 $data .= pack("C", $rgch); 01223 $data .= pack("C", $unknown01); 01224 $data .= pack("v", $unknown02); 01225 // Column definition 01226 $data .= pack("C", $unknown03); 01227 $data .= pack("v", $unknown04); 01228 $data .= pack("v", $unknown05); 01229 $data .= pack("v", $unknown06); 01230 $data .= pack("v", $unknown07); 01231 $data .= pack("v", $unknown08); 01232 $data .= pack("v", $index); 01233 $data .= pack("v", $index); 01234 $data .= pack("v", 0x0000); 01235 $data .= pack("v", 0x3fff); 01236 $data .= pack("C", $colmin); 01237 $data .= pack("C", $colmax); 01238 // Row definition 01239 $data .= pack("C", $unknown03); 01240 $data .= pack("v", $unknown04); 01241 $data .= pack("v", $unknown05); 01242 $data .= pack("v", $unknown06); 01243 $data .= pack("v", $unknown07); 01244 $data .= pack("v", $unknown08); 01245 $data .= pack("v", $index); 01246 $data .= pack("v", $index); 01247 $data .= pack("v", $rowmin); 01248 $data .= pack("v", $rowmax); 01249 $data .= pack("C", 0x00); 01250 $data .= pack("C", 0xff); 01251 // End of data 01252 $data .= pack("C", 0x10); 01253 $this->_append($header . $data); 01254 } 01255 01261 function _storeCountry() 01262 { 01263 $record = 0x008C; // Record identifier 01264 $length = 4; // Number of bytes to follow 01265 01266 $header = pack('vv', $record, $length); 01267 /* using the same country code always for simplicity */ 01268 $data = pack('vv', $this->_country_code, $this->_country_code); 01269 $this->_append($header . $data); 01270 } 01271 01277 function _storePalette() 01278 { 01279 $aref = $this->_palette; 01280 01281 $record = 0x0092; // Record identifier 01282 $length = 2 + 4 * count($aref); // Number of bytes to follow 01283 $ccv = count($aref); // Number of RGB values to follow 01284 $data = ''; // The RGB data 01285 01286 // Pack the RGB data 01287 foreach ($aref as $color) { 01288 foreach ($color as $byte) { 01289 $data .= pack("C",$byte); 01290 } 01291 } 01292 01293 $header = pack("vvv", $record, $length, $ccv); 01294 $this->_append($header . $data); 01295 } 01296 01319 function _calculateSharedStringsSizes() 01320 { 01321 /* Iterate through the strings to calculate the CONTINUE block sizes. 01322 For simplicity we use the same size for the SST and CONTINUE records: 01323 8228 : Maximum Excel97 block size 01324 -4 : Length of block header 01325 -8 : Length of additional SST header information 01326 -8 : Arbitrary number to keep within _add_continue() limit 01327 = 8208 01328 */ 01329 $continue_limit = 8208; 01330 $block_length = 0; 01331 $written = 0; 01332 $this->_block_sizes = array(); 01333 $continue = 0; 01334 01335 foreach (array_keys($this->_str_table) as $string) { 01336 $string_length = strlen($string); 01337 $headerinfo = unpack("vlength/Cencoding", $string); 01338 $encoding = $headerinfo["encoding"]; 01339 $split_string = 0; 01340 01341 // Block length is the total length of the strings that will be 01342 // written out in a single SST or CONTINUE block. 01343 $block_length += $string_length; 01344 01345 // We can write the string if it doesn't cross a CONTINUE boundary 01346 if ($block_length < $continue_limit) { 01347 $written += $string_length; 01348 continue; 01349 } 01350 01351 // Deal with the cases where the next string to be written will exceed 01352 // the CONTINUE boundary. If the string is very long it may need to be 01353 // written in more than one CONTINUE record. 01354 while ($block_length >= $continue_limit) { 01355 01356 // We need to avoid the case where a string is continued in the first 01357 // n bytes that contain the string header information. 01358 $header_length = 3; // Min string + header size -1 01359 $space_remaining = $continue_limit - $written - $continue; 01360 01361 01362 /* TODO: Unicode data should only be split on char (2 byte) 01363 boundaries. Therefore, in some cases we need to reduce the 01364 amount of available 01365 */ 01366 $align = 0; 01367 01368 # Only applies to Unicode strings 01369 if ($encoding == 1) { 01370 # Min string + header size -1 01371 $header_length = 4; 01372 01373 if ($space_remaining > $header_length) { 01374 # String contains 3 byte header => split on odd boundary 01375 if (!$split_string && $space_remaining % 2 != 1) { 01376 $space_remaining--; 01377 $align = 1; 01378 } 01379 # Split section without header => split on even boundary 01380 else if ($split_string && $space_remaining % 2 == 1) { 01381 $space_remaining--; 01382 $align = 1; 01383 } 01384 01385 $split_string = 1; 01386 } 01387 } 01388 01389 01390 if ($space_remaining > $header_length) { 01391 // Write as much as possible of the string in the current block 01392 $written += $space_remaining; 01393 01394 // Reduce the current block length by the amount written 01395 $block_length -= $continue_limit - $continue - $align; 01396 01397 // Store the max size for this block 01398 $this->_block_sizes[] = $continue_limit - $align; 01399 01400 // If the current string was split then the next CONTINUE block 01401 // should have the string continue flag (grbit) set unless the 01402 // split string fits exactly into the remaining space. 01403 if ($block_length > 0) { 01404 $continue = 1; 01405 } else { 01406 $continue = 0; 01407 } 01408 } else { 01409 // Store the max size for this block 01410 $this->_block_sizes[] = $written + $continue; 01411 01412 // Not enough space to start the string in the current block 01413 $block_length -= $continue_limit - $space_remaining - $continue; 01414 $continue = 0; 01415 01416 } 01417 01418 // If the string (or substr) is small enough we can write it in the 01419 // new CONTINUE block. Else, go through the loop again to write it in 01420 // one or more CONTINUE blocks 01421 if ($block_length < $continue_limit) { 01422 $written = $block_length; 01423 } else { 01424 $written = 0; 01425 } 01426 } 01427 } 01428 01429 // Store the max size for the last block unless it is empty 01430 if ($written + $continue) { 01431 $this->_block_sizes[] = $written + $continue; 01432 } 01433 01434 01435 /* Calculate the total length of the SST and associated CONTINUEs (if any). 01436 The SST record will have a length even if it contains no strings. 01437 This length is required to set the offsets in the BOUNDSHEET records since 01438 they must be written before the SST records 01439 */ 01440 01441 $tmp_block_sizes = array(); 01442 $tmp_block_sizes = $this->_block_sizes; 01443 01444 $length = 12; 01445 if (!empty($tmp_block_sizes)) { 01446 $length += array_shift($tmp_block_sizes); # SST 01447 } 01448 while (!empty($tmp_block_sizes)) { 01449 $length += 4 + array_shift($tmp_block_sizes); # CONTINUEs 01450 } 01451 01452 return $length; 01453 } 01454 01466 function _storeSharedStringsTable() 01467 { 01468 $record = 0x00fc; // Record identifier 01469 $length = 0x0008; // Number of bytes to follow 01470 $total = 0x0000; 01471 01472 // Iterate through the strings to calculate the CONTINUE block sizes 01473 $continue_limit = 8208; 01474 $block_length = 0; 01475 $written = 0; 01476 $continue = 0; 01477 01478 // sizes are upside down 01479 $tmp_block_sizes = $this->_block_sizes; 01480 // $tmp_block_sizes = array_reverse($this->_block_sizes); 01481 01482 # The SST record is required even if it contains no strings. Thus we will 01483 # always have a length 01484 # 01485 if (!empty($tmp_block_sizes)) { 01486 $length = 8 + array_shift($tmp_block_sizes); 01487 } 01488 else { 01489 # No strings 01490 $length = 8; 01491 } 01492 01493 01494 01495 // Write the SST block header information 01496 $header = pack("vv", $record, $length); 01497 $data = pack("VV", $this->_str_total, $this->_str_unique); 01498 $this->_append($header . $data); 01499 01500 01501 01502 01503 /* TODO: not good for performance */ 01504 foreach (array_keys($this->_str_table) as $string) { 01505 01506 $string_length = strlen($string); 01507 $headerinfo = unpack("vlength/Cencoding", $string); 01508 $encoding = $headerinfo["encoding"]; 01509 $split_string = 0; 01510 01511 // Block length is the total length of the strings that will be 01512 // written out in a single SST or CONTINUE block. 01513 // 01514 $block_length += $string_length; 01515 01516 01517 // We can write the string if it doesn't cross a CONTINUE boundary 01518 if ($block_length < $continue_limit) { 01519 $this->_append($string); 01520 $written += $string_length; 01521 continue; 01522 } 01523 01524 // Deal with the cases where the next string to be written will exceed 01525 // the CONTINUE boundary. If the string is very long it may need to be 01526 // written in more than one CONTINUE record. 01527 // 01528 while ($block_length >= $continue_limit) { 01529 01530 // We need to avoid the case where a string is continued in the first 01531 // n bytes that contain the string header information. 01532 // 01533 $header_length = 3; // Min string + header size -1 01534 $space_remaining = $continue_limit - $written - $continue; 01535 01536 01537 // Unicode data should only be split on char (2 byte) boundaries. 01538 // Therefore, in some cases we need to reduce the amount of available 01539 // space by 1 byte to ensure the correct alignment. 01540 $align = 0; 01541 01542 // Only applies to Unicode strings 01543 if ($encoding == 1) { 01544 // Min string + header size -1 01545 $header_length = 4; 01546 01547 if ($space_remaining > $header_length) { 01548 // String contains 3 byte header => split on odd boundary 01549 if (!$split_string && $space_remaining % 2 != 1) { 01550 $space_remaining--; 01551 $align = 1; 01552 } 01553 // Split section without header => split on even boundary 01554 else if ($split_string && $space_remaining % 2 == 1) { 01555 $space_remaining--; 01556 $align = 1; 01557 } 01558 01559 $split_string = 1; 01560 } 01561 } 01562 01563 01564 if ($space_remaining > $header_length) { 01565 // Write as much as possible of the string in the current block 01566 $tmp = substr($string, 0, $space_remaining); 01567 $this->_append($tmp); 01568 01569 // The remainder will be written in the next block(s) 01570 $string = substr($string, $space_remaining); 01571 01572 // Reduce the current block length by the amount written 01573 $block_length -= $continue_limit - $continue - $align; 01574 01575 // If the current string was split then the next CONTINUE block 01576 // should have the string continue flag (grbit) set unless the 01577 // split string fits exactly into the remaining space. 01578 // 01579 if ($block_length > 0) { 01580 $continue = 1; 01581 } else { 01582 $continue = 0; 01583 } 01584 } else { 01585 // Not enough space to start the string in the current block 01586 $block_length -= $continue_limit - $space_remaining - $continue; 01587 $continue = 0; 01588 } 01589 01590 // Write the CONTINUE block header 01591 if (!empty($this->_block_sizes)) { 01592 $record = 0x003C; 01593 $length = array_shift($tmp_block_sizes); 01594 01595 $header = pack('vv', $record, $length); 01596 if ($continue) { 01597 $header .= pack('C', $encoding); 01598 } 01599 $this->_append($header); 01600 } 01601 01602 // If the string (or substr) is small enough we can write it in the 01603 // new CONTINUE block. Else, go through the loop again to write it in 01604 // one or more CONTINUE blocks 01605 // 01606 if ($block_length < $continue_limit) { 01607 $this->_append($string); 01608 $written = $block_length; 01609 } else { 01610 $written = 0; 01611 } 01612 } 01613 } 01614 } 01615 } 01616 ?>