From 14b702dfe36d8389c7dd77bced58c616463ee77e Mon Sep 17 00:00:00 2001 From: Xavier Noguer Gallego Date: Wed, 17 Mar 2004 00:18:36 +0000 Subject: [PATCH] fixing SST table (long strings). Thanks to Bernd Jaenichen git-svn-id: https://svn.php.net/repository/pear/packages/Spreadsheet_Excel_Writer/trunk@153858 c90b9560-bf6c-de11-be94-00142212c4b1 --- Writer/Workbook.php | 382 +++++++++++++++++++------------------------- 1 file changed, 160 insertions(+), 222 deletions(-) diff --git a/Writer/Workbook.php b/Writer/Workbook.php index 5e4f8cb..e22431d 100644 --- a/Writer/Workbook.php +++ b/Writer/Workbook.php @@ -165,6 +165,12 @@ class Spreadsheet_Excel_Writer_Workbook extends Spreadsheet_Excel_Writer_BIFFwri */ var $_tmp_dir; + /** + * number of bytes for sizeinfo of strings + * @var integer + */ + var $_string_sizeinfo_size; + /** * Class constructor * @@ -193,7 +199,8 @@ class Spreadsheet_Excel_Writer_Workbook extends Spreadsheet_Excel_Writer_BIFFwri $this->_palette = array(); $this->_codepage = 0x04E4; // FIXME: should change for BIFF8 $this->_country_code = -1; - + $this->_string_sizeinfo = 3; + // Add the default format for hyperlinks $this->_url_format =& $this->addFormat(array('color' => 'blue', 'underline' => 1)); $this->_str_total = 0; @@ -265,7 +272,7 @@ class Spreadsheet_Excel_Writer_Workbook extends Spreadsheet_Excel_Writer_BIFFwri $version = 0x0600; $this->_BIFF_version = $version; // change BIFFwriter limit for CONTINUE records - $this->_limit = 8224; + $this->_limit = 8228; $this->_tmp_format->_BIFF_version = $version; $this->_url_format->_BIFF_version = $version; $this->_parser->_BIFF_version = $version; @@ -547,7 +554,7 @@ class Spreadsheet_Excel_Writer_Workbook extends Spreadsheet_Excel_Writer_BIFFwri } return true; } - + /** * Sets the temp dir used for storing the OLE file * @@ -1253,7 +1260,7 @@ class Spreadsheet_Excel_Writer_Workbook extends Spreadsheet_Excel_Writer_BIFFwri $data .= pack("C", 0x10); $this->_append($header.$data); } - + /** * Stores the COUNTRY record for localization * @@ -1269,7 +1276,7 @@ class Spreadsheet_Excel_Writer_Workbook extends Spreadsheet_Excel_Writer_BIFFwri $data = pack('vv', $this->_country_code, $this->_country_code); $this->_append($header.$data); } - + /** * Stores the PALETTE biff record. * @@ -1297,245 +1304,176 @@ class Spreadsheet_Excel_Writer_Workbook extends Spreadsheet_Excel_Writer_BIFFwri } /** - * Calculate - * Handling of the SST continue blocks is complicated by the need to include an - * additional continuation byte depending on whether the string is split between - * blocks or whether it starts at the beginning of the block. (There are also - * additional complications that will arise later when/if Rich Strings are - * supported). - * - * @access private - */ + * new method for calculating blocksizes of SST-record. + * Handling of the SST continue blocks is complicated by the need to include an + * additional continuation byte depending on whether the string is split between + * blocks or whether it starts at the beginning of the block. (There are also + * additional complications that will arise later when/if Rich Strings are + * supported). + * + * @author + * @access private + */ function _calculateSharedStringsSizes() { - /* Iterate through the strings to calculate the CONTINUE block sizes. - The SST blocks requires a specialised CONTINUE block, so we have to - ensure that the maximum data block size is less than the limit used by - _add_continue() in BIFFwriter.pm. For simplicity we use the same size - for the SST and CONTINUE records: - 8228 : Maximum Excel97 block size - -4 : Length of block header - -8 : Length of additional SST header information - -8 : Arbitrary number to keep within _add_continue() limit - = 8208 - */ - $total_offset = 12; - $continue_limit = 8208; - $block_length = 0; - $written = 0; + $total_offset = 0; + $continue_limit = 8228; + $header_size = 4; $this->_block_sizes = array(); - $continue = 0; - foreach (array_keys($this->_str_table) as $string) { - $string_length = strlen($string); - - // Block length is the total length of the strings that will be - // written out in a single SST or CONTINUE block. - $block_length += $string_length; - - // We can write the string if it doesn't cross a CONTINUE boundary - if ($block_length < $continue_limit) { - $written += $string_length; - $total_offset += $string_length; - continue; + // set the SST block header information + $buffer = pack("vv", 0x00fc, 0); + $buffer .= pack("VV", 0, 0); + + foreach (array_keys($this->_str_table) as $string) + { + // block is full + if ((strlen($buffer) + strlen($string)) > $continue_limit) { + $this->_calculateSSTContinueBlock($string, &$buffer, &$total_offset, $header_size, $continue_limit); } - - // Deal with the cases where the next string to be written will exceed - // the CONTINUE boundary. If the string is very long it may need to be - // written in more than one CONTINUE record. - while ($block_length >= $continue_limit) { - - // We need to avoid the case where a string is continued in the first - // n bytes that contain the string header information. - $header_length = 3; // Min string + header size -1 - $space_remaining = $continue_limit - $written - $continue; - - - /* TODO: Unicode data should only be split on char (2 byte) - boundaries. Therefore, in some cases we need to reduce the - amount of available - */ - - if ($space_remaining > $header_length) { - // Write as much as possible of the string in the current block - $written += $space_remaining; - - // Reduce the current block length by the amount written - $block_length -= $continue_limit + $continue; - - // Store the max size for this block - $this->_block_sizes[] = $continue_limit; - - // If the current string was split then the next CONTINUE block - // should have the string continue flag (grbit) set unless the - // split string fits exactly into the remaining space. - if ($block_length > 0) { - $continue = 1; - } - else { - $continue = 0; - } - - } - else { - // Store the max size for this block - $this->_block_sizes[] = $written + $continue; - - // Not enough space to start the string in the current block - $block_length -= $continue_limit - $space_remaining - $continue; - $continue = 0; - - } - - // If the string (or substr) is small enough we can write it in the - // new CONTINUE block. Else, go through the loop again to write it in - // one or more CONTINUE blocks - if ($block_length < $continue_limit) { - $written = $block_length; - } - else { - $written = 0; - } + else + { + // add string to block + $buffer .= $string; } } - // Store the max size for the last block unless it is empty - if ($written + $continue) { - $this->_block_sizes[] = $written + $continue; - } - - - /* Calculate the total length of the SST and associated CONTINUEs (if any). - The SST record will have a length even if it contains no strings. - This length is required to set the offsets in the BOUNDSHEET records since - they must be written before the SST records - */ - if (!empty($this->_block_sizes)) { - $total_offset += (count($this->_block_sizes) - 1) * 4; // add CONTINUE headers + // save last block + if (strlen($buffer) > 0) + { + $this->_block_sizes[] = (strlen($buffer) - $header_size); + $total_offset += strlen($buffer); } + return $total_offset; } /** - * Write all of the workbooks strings into an indexed array. - * See the comments in _calculate_shared_string_sizes() for more information. - * - * The Excel documentation says that the SST record should be followed by an - * EXTSST record. The EXTSST record is a hash table that is used to optimise - * access to SST. However, despite the documentation it doesn't seem to be - * required so we will ignore it. - * - * @access private - */ - /* FIXME: update _calcSheetOffsets() when updating this method */ + * Write all of the workbooks strings into an indexed array. + * See the comments in _calculate_shared_string_sizes() for more information. + * + * The Excel documentation says that the SST record should be followed by an + * EXTSST record. The EXTSST record is a hash table that is used to optimise + * access to SST. However, despite the documentation it doesn't seem to be + * required so we will ignore it. + * + * @author + * @access private + */ function _storeSharedStringsTable() { + $continue_limit = 8228; + $record = 0x00fc; // Record identifier - $length = 8 + array_sum($this->_block_sizes); // Number of bytes to follow + $length = array_shift($this->_block_sizes); // Write the SST block header information - $header = pack("vv", $record, $length); - $data = pack("VV", $this->_str_total, $this->_str_unique); - $this->_append($header.$data); + $buffer = pack("vv", $record, $length); + $buffer .= pack("VV", $this->_str_total, $this->_str_total); - - // Iterate through the strings to calculate the CONTINUE block sizes - $continue_limit = 8208; - $block_length = 0; - $written = 0; - $continue = 0; - - - /* TODO: not good for performance */ - foreach (array_keys($this->_str_table) as $string) { - - $string_length = strlen($string); - $encoding = 0; // assume there are no Unicode strings - $split_string = 0; - - // Block length is the total length of the strings that will be - // written out in a single SST or CONTINUE block. - // - $block_length += $string_length; - - - // We can write the string if it doesn't cross a CONTINUE boundary - if ($block_length < $continue_limit) { - $this->_append($string); - $written += $string_length; - continue; + foreach (array_keys($this->_str_table) as $string) + { + // block is full + if ((strlen($buffer) + strlen($string)) > $continue_limit) { + $this->_storeSSTContinueBlock($string, &$buffer, $continue_limit); } - - // Deal with the cases where the next string to be written will exceed - // the CONTINUE boundary. If the string is very long it may need to be - // written in more than one CONTINUE record. - // - while ($block_length >= $continue_limit) { - - // We need to avoid the case where a string is continued in the first - // n bytes that contain the string header information. - // - $header_length = 3; // Min string + header size -1 - $space_remaining = $continue_limit - $written - $continue; - - - // Unicode data should only be split on char (2 byte) boundaries. - // Therefore, in some cases we need to reduce the amount of available - - if ($space_remaining > $header_length) { - // Write as much as possible of the string in the current block - $tmp = substr($string, 0, $space_remaining); - $this->_append($tmp); - - // The remainder will be written in the next block(s) - $string = substr($string, $space_remaining); - - // Reduce the current block length by the amount written - $block_length -= $continue_limit - $continue; - - // If the current string was split then the next CONTINUE block - // should have the string continue flag (grbit) set unless the - // split string fits exactly into the remaining space. - // - if ($block_length > 0) { - $continue = 1; - } - else { - $continue = 0; - } - } - else { - // Not enough space to start the string in the current block - $block_length -= $continue_limit - $space_remaining - $continue; - $continue = 0; - } - - // Write the CONTINUE block header - if (!empty($this->_block_sizes)) { - $record = 0x003C; - $length = array_pop($this->_block_sizes); - - $header = pack('vv', $record, $length); - if ($continue) { - $header .= pack('C', $encoding); - } - $this->_append($header); - } - - // If the string (or substr) is small enough we can write it in the - // new CONTINUE block. Else, go through the loop again to write it in - // one or more CONTINUE blocks - // - if ($block_length < $continue_limit) { - $this->_append($string); - - $written = $block_length; - } - else { - $written = 0; - } + else + { + // add string to block + $buffer .= $string; } } + + // save last block + if (strlen($buffer) > 0) + { + $this->_append($buffer); + } + } + + /** + * method for calculation of SST-record Continue-Blocks. + * + * @author + * @access private + */ + function _calculateSSTContinueBlock($string, &$buffer, &$total_offset, $header_size, $continue_limit) + { + // calculate remaining space in block + $space = $continue_limit - strlen($buffer); + + // if there's space left in block to store at least the sizeinfo of actual string + if ($space > $this->_string_sizeinfo) + { + // split string + $tmp = substr($string, 0, $space); + $buffer .= $tmp; + // save rest of string + $string = substr($string, $space); + } + + // save blocksize decremented by headersize + $this->_block_sizes[] = (strlen($buffer) - $header_size); + // save full blocksize + $total_offset += strlen($buffer); + + // set CONTINUE header + $buffer = pack("vv", 0x003c, 0); + // set optional flagbyte of CONTINUE record for splittet strings + if ($space > $this->_string_sizeinfo) { + $buffer .= pack('C', 0); + } + + // block is full again + if ((strlen($buffer) + strlen($string)) > $continue_limit) { + $this->_calculateSSTContinueBlock($string, &$buffer, &$total_offset, $header_size, $continue_limit); + } + else { + $buffer .= $string; + } + } + + /** + * method for storing of SST-record Continue-Blocks. + * + * @author + * @access private + */ + + function _storeSSTContinueBlock($string, &$buffer, $continue_limit) + { + // calculate remaining space in block + $space = $continue_limit - strlen($buffer); + + // if there's space left in block to store at least the sizeinfo of actual string + if ($space > $this->_string_sizeinfo) + { + // split string + $tmp = substr($string, 0, $space); + $buffer .= $tmp; + // save rest of string including optional flagbyte of CONTINUE record + $string = substr($string, $space); + } + + // save block + $this->_append($buffer); + + // save CONTINUE header + $record = 0x003C; + $length = array_shift($this->_block_sizes); + $buffer = pack('vv', $record, $length); + // set optional flagbyte of CONTINUE record for splittet strings + if ($space > $this->_string_sizeinfo) { + $buffer .= pack('C', 0); + } + + // block is full again + if ((strlen($buffer) + strlen($string)) > $continue_limit) { + $this->_storeSSTContinueBlock($string, &$buffer, $continue_limit); + } + else { + $buffer .= $string; + } } } ?>