不過, 我們發現, 在資料筆數較少, 檔案較小的時候, 運作十分正常. 不過, 當筆數大到一定的大小之後, 所產生的檔案, 使用 Microsoft Excel 打開時, 會出現損毀的情形, 其中的內容是正確無誤, 不過格式都會不見. (據說用 OpenOffice 打開時會告知是 SST 錯誤)
今天在 PEAR 的網站中, 看到幾個月之前, 有人提出了一個解決方法, 修改 Workbook.php 中的下列兩個 function 如下:
/**
* 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
*/
function _calculateSharedStringsSizes()
{
/* Iterate through the strings to calculate the CONTINUE block sizes.
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
*/
$continue_limit = 8208;
$block_length = 0;
$written = 0;
$this->_block_sizes = array();
$continue = 0;
foreach (array_keys($this->_str_table) as $string) {
$string_length = strlen($string);
$headerinfo = unpack("vlength/Cencoding", $string);
$encoding = $headerinfo["encoding"];
$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) {
$written += $string_length;
continue;
}
// 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
*/
$align = 0;
# Only applies to Unicode strings
if ($encoding == 1) {
# Min string + header size -1
$header_length = 4;
if ($space_remaining > $header_length) {
# String contains 3 byte header => split on odd boundary
if (!$split_string && $space_remaining % 2 != 1) {
$space_remaining--;
$align = 1;
}
# Split section without header => split on even boundary
else if ($split_string && $space_remaining % 2 == 1) {
$space_remaining--;
$align = 1;
}
$split_string = 1;
}
}
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 - $align;
// Store the max size for this block
$this->_block_sizes[] = $continue_limit - $align;
// 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;
}
}
}
// 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
*/
$tmp_block_sizes = array();
$tmp_block_sizes = $this->_block_sizes;
$length = 12;
if (!empty($tmp_block_sizes)) {
$length += array_shift($tmp_block_sizes); # SST
}
while (!empty($tmp_block_sizes)) {
$length += 4 + array_shift($tmp_block_sizes); # CONTINUEs
}
return $length;
}
/**
* 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
*/
function _storeSharedStringsTable()
{
$record = 0x00fc; // Record identifier
$length = 0x0008; // Number of bytes to follow
$total = 0x0000;
// Iterate through the strings to calculate the CONTINUE block sizes
$continue_limit = 8208;
$block_length = 0;
$written = 0;
$continue = 0;
// sizes are upside down
$tmp_block_sizes = $this->_block_sizes;
// $tmp_block_sizes = array_reverse($this->_block_sizes);
# The SST record is required even if it contains no strings. Thus we will
# always have a length
#
if (!empty($tmp_block_sizes)) {
$length = 8 + array_shift($tmp_block_sizes);
}
else {
# No strings
$length = 8;
}
// Write the SST block header information
$header = pack("vv", $record, $length);
$data = pack("VV", $this->_str_total, $this->_str_unique);
$this->_append($header . $data);
/* TODO: not good for performance */
foreach (array_keys($this->_str_table) as $string) {
$string_length = strlen($string);
$headerinfo = unpack("vlength/Cencoding", $string);
$encoding = $headerinfo["encoding"];
$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;
}
// 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
// space by 1 byte to ensure the correct alignment.
$align = 0;
// Only applies to Unicode strings
if ($encoding == 1) {
// Min string + header size -1
$header_length = 4;
if ($space_remaining > $header_length) {
// String contains 3 byte header => split on odd boundary
if (!$split_string && $space_remaining % 2 != 1) {
$space_remaining--;
$align = 1;
}
// Split section without header => split on even boundary
else if ($split_string && $space_remaining % 2 == 1) {
$space_remaining--;
$align = 1;
}
$split_string = 1;
}
}
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 - $align;
// 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_shift($tmp_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;
}
}
}
}
我們試用後, 目前看起來一切都正常.
請問一下, Excel 產生後, 要如何轉成字串,
才能用Email 的方式寄出!
我上網找了很久,都沒找到!
因為轉成字串, 就能每天自動跑報表出來,Email給所有人
!!
都用 pear 的 Spreadsheet_Excel_Writer 了, 應該也不差再用一個 Mail_mime 物件.
http://pear.php.net/manual/en/package.mail.mail-mime.php
http://tw.myblog.yahoo.com/c9993/article?mid=20&prev=157&next=155&l=f&fid=7
搭配一下應該就OK了
tommy~
請問一下你試用後正常~那你的檔案有測到多大的呢??
我有用你介紹的方式執行,初期正常,但我今天檔案到54kb就又出現原來的問題~!故有此一問!!謝謝!
幾百 K 吧, 可能有上到幾 MB 吧. 我們的客戶之後都沒反應過有不正常的情形了.
如果只是 54K 就有問題, 比較像是沒改之前的情形.
我記得我們沒改之前, 當初好像多數在 200K 左右會出問題.
謝謝!這讓我頭痛了半天......
sorry~!!我打錯了~!
是154kb~!!
謝謝tommy的提醒,確實是我沒有改過去!現在ok了(我一直以為我有改過!)
tommy 您好:
當我在使用 Spreadsheet_Excel_Writer 時,
每當把 *.xls 下載完後,想要直接開啟,
都會出現一個 Microsoft Excel 的警告視窗,
內容為
"Microsoft Excel 無法使用 *.xls , 可能是因為這個檔案是唯讀的,或者存放它的地方是唯讀的,或是伺服器沒有回應"
當我按"取消"的時候,會出現 2 個狀況,
1.直接修復,並且提示"Excel 嘗試回復您的公式和數值,但是部份資料可能已經遺失或損毀",但是檔案內容都正確;
2.修復失敗,我得使用開啟舊檔,選擇"開啟並修復"-->"抽選資料"-->"復原公式",
之後 Excel 直接修復,並出現 敘述 1 的狀況;
我的測試程式
require_once 'Spreadsheet/Excel/Writer.php';
// Creating a workbook
$workbook = new Spreadsheet_Excel_Writer();
// Creating a worksheet
$worksheet =& $workbook->addWorksheet('Reporter');
$workbook->setVersion(8);
$worksheet->setInputEncoding('utf-8');
$x = 0;
$y = 0;
$worksheet->write($x, $y++, '群組');
$worksheet->write($x, $y++, '報表月份');
$worksheet->write($x, $y++, '應發獎金');
// hide gridlines so they don't mess with our Excel art.
$worksheet->hideGridLines();
// sending HTTP headers
$workbook->send('sales_reporter.xls');
$workbook->close();
這個狀況我一直處理不掉,請問這個狀況該如何處理呢??
我用你上頭寫的程式, 在我的環境並不會有問題.
不過建議 $workbook->setVersion(8); 寫在產生 worksheet 之前 (不過好像之後也不會有問題).
另外, 下載的檔案, 不要直接用 excel 打開來, 先存起來. 然後再看看格式是不是正確的 excel 檔案.
也許用 notepad 先看一下, 也許是你的系統的 php 會有些錯誤 (警告? 提示?) 訊息出來吧, 這樣子會變成在那 excel 檔案前面出現那些字串, 格式自然會有問題.
對了, 程式我放這兒, 可以自己試看看:
http://www.teatime.com.tw/~tommy/test/excel.php
source code 可以用這個看 (只是一個 soft link 到同一個檔案):
http://www.teatime.com.tw/~tommy/test/excel.phps
tommy 您好:
真的是ok;
怪事了,為什麼在我的環境卻是有問題的,
請問可否給我一些想法呢???
是否安裝pear套件時,有什麼地方應該注意的呢??
謝謝您;
另外補充:
我都是先存檔之後再開啟檔案的;
我有使用notepad打開檔案,
開頭是
"俵遄??",
看不出是否是錯誤...
我猜測是你的那個 php 檔案因為是 UTF-8 編碼的, 在 Windows 下頭, 多數的編輯器會在檔案前面加上 BOM 字元 (3 bytes), 而那些字元會被送出, 所以產生的 excel 檔案會多那幾個 bytes, 格式自然也就不對了.
如果你確定不是因為 BOM 造成的問題. 就把你有問題的檔案與我這兒產生正常的檔案用 binary mode 比較一下看看吧. 或者把有問題的檔案 email 給我看看吧.
謝謝您寶貴的意見,我再試試看..
非常感謝....問題解決!!!!
Tommy大大你好
請教,如何在Linux環境安裝Spreadsheet_Excel_Writer
我server上已用yum安裝了PHP,也download了OLE及Spreadsheet_Excel_Writer的tar檔
查了google都是教人用win環境安裝,Linux環境要怎麼安裝呢?
謝謝
linux 下頭與 window 下沒什麼不同吧.
如果你是用 tarball 裝的, 那只好裝了一個之後跑看看有少什麼再裝缺的東西吧. 或者學著用 pear 裝吧. 這些東西本來就是 pear 裡頭的東西, 一個指令應該就會把缺的都裝上去.
http://pear.php.net/manual/en/
或者直接找有包含這些東西的 linux 發行版本吧. 例如 Debian (UBuntu 應該也有, 反正也可以用 Debian 的套件庫).
感謝Tommy大大
我install pear就可以把OLE及Spreadsheet_Excel_Writerj都安裝起來了
也可以正常運作了,
感謝
Tommy大大,
我是從Spreadsheet_Excel_Writer的那個bug討論的thread裡面找到你這裡來的。我安裝好了Spreadsheet_Excel_Writer以及OLE後按照你的http://www.teatime.com.tw/~tommy/test/excel.phps 裡面的方法生成出來的excel文檔,正文內容看上去都OK,但是下面sheetname裡面包含的中文都亂掉了。請問您有遇見過這種情況么?
謝謝!
名稱的問題請參考另外一篇.
http://blog.teatime.com.tw/1/post/301
太贊了!感謝T大大!









