不過, 我們發現, 在資料筆數較少, 檔案較小的時候, 運作十分正常. 不過, 當筆數大到一定的大小之後, 所產生的檔案, 使用 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大大!
我已經patch 了以上的補丁,但是仍然發生錯誤
Fatal error: Call to undefined method Spreadsheet_Excel_Writer_Worksheet::setBIFF8InputEncoding() in C:\Program Files\EasyPHP 3.0\www\yamao1\Spreadsheet\Excel\Writer\Workbook.php on line 363
有錯.... 表示沒 patch 好吧.
可否email給我一個patch好的workbook.php...
感謝TOMMY大神。
my email: tsang301@yahoo.com.hk
抓 http://www.teatime.com.tw/~tommy/files/Spreadsheet_Excel_Writer-0.9.1_patched.tgz 這個吧.
已經copy 了, 但是excel 內的中文變成問號(?????)
本人的php 程式是先撈數據庫的資料,然後生成excel檔,數據庫的中文資料都變為亂碼了。本人用phpmyadmin,所有數據庫校驗及varchar,text為utf-8 unicode編碼, 是否數據庫編碼的影響?
程式碼如下:
// include class file
include 'Spreadsheet/Excel/Writer.php';
session_start();
$_SESSION["m_computer_code"] = "vch";
$savename = date("YmdHis");
// initialize reader object
$workbook = new Spreadsheet_Excel_Writer("sheets/".$_SESSION["m_computer_code"]."$savename.xls");
$workbook->setVersion(8, 'utf-8');
$worksheet =& $workbook->addWorksheet('International worksheet');
$worksheet->setInputEncoding('UTF-8');
// attempt a connection
try {
$pdo = new PDO("mysql:dbname=phpcart;host=localhost;charset=utf-8", "admin", "Yamao1946");
} catch (PDOException $e) {
die("ERROR: Could not connect: " . $e->getMessage());
}
// read data from database
// convert into spreadsheet
$rowCount = 0;
$sql = "SELECT * FROM `directadd` WHERE `m_computer_code`='". $_SESSION["m_computer_code"]."'";
if ($result = $pdo->query($sql)) {
// get header row
for ($x=0; $xcolumnCount(); $x++) {
$meta = $result->getColumnMeta($x);
$worksheet->write($rowCount, $x, $meta['name']);
}
// get data rows
$rowCount++;
while($row = $result->fetch()) {
foreach ($row as $key => $value) {
$worksheet->write($rowCount, $key, $value);
}
$rowCount++;
}
} else {
echo "ERROR: Could not execute $sql. " . print_r($pdo->errorInfo());
}
// close connection
unset($pdo);
// save file to disk
$workbook->close();
經本人嘗試,如果是static的資料,無論是繁體和簡體都能正常顯示在Excel中。但是經過撈數據庫的步驟後,所生出來的Excel的中文資料都變成問號。
也許你資料庫內抓出來的資料編碼不對吧.
以你上頭的程式來看, 只有使用 utf-8 才會正確的被存到 excel 檔案中.
我的phpmyadmin 所有item的校對都是utf8 unicode 而且default character set 都是uft8,所有的設定都是一致的, 究竟為甚麼生成的excel 中文會變成問號? 救命呀! TOMMY 大神
自己想辦法確認那些字是正確的吧. 畢竟如果是你自己打上去的字可以正確的出來, 表示不是這個模組的問題.
自己把資料庫抓出來的資料寫到檔案上去看看是那一種編碼的字吧.
問題已解決!原來要正確顯示由數據庫中所撈的中文字,需要在連接數據後,查詢語句之前,加上
mysql_query('set names utf8;');由於我使用pdo函式連接數據庫,需要改為$pdo->query('set names utf8;')。加了後,所有繁中,簡中都能夠顯示。總之,感謝TOMMY大神的PATCH。
萬分感謝!!@@
呀.. 多謝大大發表這個excel writer
本人把這個套件install在pear 了
但是使用以下php碼就發生了exception
Warning: Cannot modify header information - headers already sent by (output started at D:\xampplite\htdocs\test\excel.php:2) in D:\xampplite\php\PEAR\Spreadsheet\Excel\Writer.php on line 67
Warning: Cannot modify header information - headers already sent by (output started at D:\xampplite\htdocs\test\excel.php:2) in D:\xampplite\php\PEAR\Spreadsheet\Excel\Writer.php on line 68
Warning: Cannot modify header information - headers already sent by (output started at D:\xampplite\htdocs\test\excel.php:2) in D:\xampplite\php\PEAR\Spreadsheet\Excel\Writer.php on line 69
Warning: Cannot modify header information - headers already sent by (output started at D:\xampplite\htdocs\test\excel.php:2) in D:\xampplite\php\PEAR\Spreadsheet\Excel\Writer.php on line 70
Warning: Cannot modify header information - headers already sent by (output started at D:\xampplite\htdocs\test\excel.php:2) in D:\xampplite\php\PEAR\Spreadsheet\Excel\Writer.php on line 71
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 18 bytes) in D:\xampplite\php\PEAR\Spreadsheet\Excel\Writer\Workbook.php on line 1308
本人是使用xampplite 套件的...是不是預設設定的問題嗎?
1. 應該是 BOM 造成的吧.
2. 把 php 可以使用的記憶體在 php.ini 中加大吧.
:| 多謝大大回覆 原來xampplite 需要預設date_default_timezone_set('Asia/Hong_Kong') ;
加了這句後能夠下載了
不過為什麼活頁簿的名稱是亂碼?
名稱是亂碼的修正請參考這兒另一篇與 spreadsheet_excel 有關的文吧.









