PHP分類文章 顯示方式:簡文 | 列表

February 5,2009

[PHP] ZendFramework Zend_Db_Table_Rowset::getRow() 的用法

一般在使用 ZendFrameworkZend_Db_Table_Rowset fetchAll() 時,都是需要 Rowset 裡面全部的資料;不過偶爾有會有只需要單筆資料列的情況,這個時候就可以藉助 current() 或是 getRow() 取得單筆資料列。

current() 很單純,就是目前 Rowset 中指向的資料列,通常在沒有指定指標的情況下,就是該 Rowset 中的第一筆資料:
$table = new Table();
$select = $table->select();
$rowset = $table->fetchAll($select);
$row = $rowset->current();
// 這裡的 $row 就會是 $rowset 的第一筆資料列。
getRow() 就比較有彈性,它可以指定取得第幾個資料列,並決定是不是要將指標移至該資料列(預設為不指定)。
getRow($position, $seek = false) 
// $position 設定取得第幾列資料(從 0 開始)
// $seek 設定是否要將指標移至該資料列(預設為不指定)
用法如下:
$table = new Table();
$select = $table->select();
$rowset = $table->fetchAll($select);
$row = $rowset->getRow(1);
// 這裡的 $row 就會是 $rowset 的第二筆資料列。
要比較注意的是 $seek 的使用時機,是否有需要移動指標,就看程式的需求囉;使用後呈現的資料,如以下的例子:
$table = new Table();
$select = $table->select();
$rowset = $table->fetchAll($select);
$row = $rowset->current();
// 這裡的 $row 就會是 $rowset 的第一筆資料列。

$row = $rowset->getRow(1);
// 這裡的 $row 就會是 $rowset 的第二筆資料列($seek 預設為 false,故指標不移動)。

$row = $rowset->current();
// 這裡的 $row 還是 $rowset 的第一筆資料列。

$row = $rowset->getRow(1, true);
// 這裡的 $row 就會是 $rowset 的第二筆資料列($seek 設為 true,故指標移動)。

$row = $rowset->current();
// 這裡的 $row 就會變成是 $rowset 的第二筆資料列。
大概就是這樣;在這邊做個紀錄,感謝 Jace 解惑^^

Posted by taikobo0 at 樂多Roodo!15:27回應(0)引用(0)

May 15,2008

[PHP] Zend_Search_Lucene中文分詞實做

最近在練習中有用到搜尋的功能。一般對MySQL資料庫作搜尋,常用的做法是針對資料表中的特定欄位,用「%」LIKE的方式去尋找。然而這樣的做法常伴隨著許多限制,使用者必須先選定所要輸入的資料欄位,再對其進行搜尋;習慣了Google搜尋所帶來的便利,最理想的方式是只有一個輸入格,且可以在此輸入格中任意輸入,即可對整個資料庫進行搜尋。在MySQL中稱為Full-Text(全文檢索);然而拜完Google大神以後,網路上前輩們幾乎是一面倒的否定全文檢索。最主要的原因是因為它不支持中文!

全文檢索的做法,即是對資料庫裡的資料進行「分詞」的索引處理,有了索引,搜尋起來自然有效率的多;然而中文字不同於英文,一個句子中單獨一個中文字就可能有它的意思,另一個最大的分別在於中文句子可不像英文句子由單字與「空格」組成;建立索引時的「分詞」的動作,就是以空格進行判斷!

全文檢索的問題在網路上一直存在著,但是前輩們似乎都沒有非常完美的解答;甚至有人直接勸退提問者:「全文檢索的功能,是可以讓你寫好幾篇博士論文的研究!」如此可見,Google雲端運算的強大。既然資料庫端無解,我就從PHP的方向著手吧~Google的確是大神,讓我找到了Zend Framework就有分詞的函式Zend Search Lucene;而且原本這個功能其實也不支持中文,萬能的Google大神還幫我找到了支持中文的解決方法!

PHP製作中文全文搜尋不求人中,作者巨細靡遺的說明了資料夾配置、原始程式碼,還非常貼心的提供範例程式的下載。在如何讓Zend_Search_Lucene支持中文分詞中,作者改良了分詞用的類別,讓中文分詞的動作更加準確!既然有如此完整的範例,我當然是馬上適用在練習中啦!

目前練習所利用的開發框架,是Jace一手打造的Wacow Framework。結合Zend FrameworkSmarty,以及許多在專案製作時常會用到的工具,是公司目前專案開發的主力,也是我目前需要熟練的工具。我把建立索引的功能放在首頁,這樣只要有人進入首頁就會觸發建立索引(當然這樣做的代價是每次進首頁的速度都會被拖慢)。因為自動載入類別的關係,在建立索引時不需再額外include或require,程式碼如下:

IndexController.php(部分節錄)

// 建立分詞索引
// 關閉 Notice 錯誤提醒
error_reporting(E_ALL ^ E_NOTICE);
// 資料是 utf8 為編碼的這句為重點。如果你是 utf8 的話必需加入,否則資料會錯誤;另Phpbean是需要另外建立的中文分詞的類別
Zend_Search_Lucene_Analysis_Analyzer::setDefault(new Phpbean());
if (function_exists("set_time_limit") && ! get_cfg_var('safe_mode')) {
set_time_limit(0);
}
$index = new Zend_Search_Lucene('index', true);
$itemTable = new Items();
$itemRowset = $itemTable->fetchAll();
foreach ($itemRowset as $itemRow) {
$url = '/gime/item/detail/id/' . $itemRow->id; // 建立連結
$itemName = $itemRow->name; // 抓出物品名稱
$description = $itemRow->description; // 抓出物品敘述
//儲存網頁的位置以在搜尋結果中連結.
$doc = new Zend_Search_Lucene_Document(); // 建立新的索引文件
$doc->addField(Zend_Search_Lucene_Field::UnIndexed('url', strtolower($url)));
$doc->addField(Zend_Search_Lucene_Field::Text('name', strtolower($itemName), 'utf-8'));
$doc->addField(Zend_Search_Lucene_Field::Text('contents', strtolower($description), 'utf-8'));
$index->addDocument($doc); //把索引文件加到索引中
}
$index->commit(); //提交,及保存索引

Phpbean.php


class Phpbean extends Zend_Search_Lucene_Analysis_Analyzer_Common
{
private $_position;

private $_cnStopWords = array();

public function setCnStopWords($cnStopWords){
$this->_cnStopWords = $cnStopWords;
}

/**
* Reset token stream
*/
public function reset()
{
$this->_position = 0;
$search = array(",", "/", "\", ".", ";", ":", """, "!", "~", "`", "^", "(", ")", "?", "-", "'", "<", ">", "$", "&", "%", "#", "@", "+", "=", "{", "}", "[", "]", ":", ")", "(", ".", "。", ",", "!", ";", "“", "”", "‘", "’", "[", "]", "、", "—", " ", "《", "》", "-", "…", "【", "】","的");
$this->_input = str_replace($search,' ',$this->_input);
$this->_input = str_replace($this->_cnStopWords,' ',$this->_input);
}

/**
* Tokenization stream API
* Get next token
* Returns null at the end of stream
*
* @return Zend_Search_Lucene_Analysis_Token|null
*/
public function nextToken()
{
if ($this->_input === null) {
return null;
}
$len = strlen($this->_input);
while ($this->_position < $len) {
while ($this->_position < $len && $this->_input[$this->_position] == ' ') {
$this->_position++;
}
$termStartPosition = $this->_position;
$temp_char = $this->_input[$this->_position];
$isCnWord = false;
if (ord($temp_char) > 127) {
$i = 0;
while ($this->_position < $len && ord($this->_input[$this->_position]) >127) {
$this->_position = $this->_position + 3;
$i ++;
if ($i == 2) {
$isCnWord = true;
break;
}
}
if ($i == 1) continue;
} else {
while ($this->_position < $len && ctype_alnum($this->_input[$this->_position])) {
$this->_position++;
}
// echo $this->_position.":".$this->_input[$this->_position]."\n";
}
if ($this->_position == $termStartPosition) {
$this->_position++;
continue;
}

$token = new Zend_Search_Lucene_Analysis_Token(
substr($this->_input,
$termStartPosition,
$this->_position - $termStartPosition),
$termStartPosition,
$this->_position);
$token = $this->normalize($token);
if ($isCnWord) $this->_position = $this->_position - 3;
if ($token !== null) {
return $token;
}
}
return null;
}
}

SearchController.php(部分節錄)

Zend_Search_Lucene_Analysis_Analyzer::setDefault(new Phpbean());
$index = new Zend_Search_Lucene('index');
$query = $this->_request->getParam('query'); // 擷取來自表單的關鍵字
$query = trim($query);
if (strlen($query) > 0) {
try {
$query2 = Zend_Search_Lucene_Search_QueryParser::parse(strtolower($query), "utf-8");
$hits = $index->find($query2); // 根據關鍵字找到資料,並將資料回存為物件
}
catch (Zend_Search_Lucene_Exception $ex) {
$hits = array();
}
$numHits = count($hits); // 根據關鍵字找到資料的數量
}

非常簡單的實作;然而就全文檢索來說還是有缺點的!首先,建立分詞索引時必定會耗費系統資源,故比較好的做法是批次定時處理建立索引的動作。第二點是中文的問題,因為中文字詞與連貫的句子的關係,在分詞時是以二個字為一個詞的最基本單位,所以單一個中文字是不會有任何搜尋結果的。最後因為建立分詞索引為觸發事件,如果沒有去觸發它就無法更新分詞至目前資料庫的最新狀態。我覺得分詞索引的方式很像是MySQL的View資料表,也是將資料表中的欄位作一個資料上的更新,只是它沒有欄位的限制,可以針對建立的「詞」索引進行搜尋。就某方面來說是很好用的功能,也不失為中文在全文索引時的一種解決方案。^^

Posted by taikobo0 at 樂多Roodo!10:22回應(2)引用(0)

April 18,2008

[PHP] 表單輸入時對於HTML的過濾

在PHP中最常見的應用就是互動表單,因此接收用戶端資訊是非常常見的事!然而俗話說:「世風日下,人心不古」,這年頭誰知道用戶端的人在想些什麼?用戶端送出的資訊很有可能會包含一些惡意的語法,對於24小時暴露在網路環境的網站來說,時時刻刻都得小心防範駭客的攻擊,提升自我的資訊安全觀念非常重要。所以在接收用戶端資訊後,存入資料庫之前,都會先對其進行基本的過濾;第一個要判斷的就是HTML語法的攻擊!

許多惡意語法都是建立在HTML上,PHP本身有提供轉換HTML碼的函數:htmlspecialchars()htmlentities();其中htmlspecialchars()只會轉換HTML相關碼:
  • '&' => '&amp;'
  • '"' => '&quot;'
  • ''' => '&#039;' 
  • '<' => '&lt;'
  • '>' => '&gt;'
htmlentities()則是把字串中所有字元做轉換,另外還可以設定轉換字串的編碼方式。還有一個函數:strip_tags()是直接把HTML的標籤整個過濾掉。

之前在題目試作/哇寶基本能力測試一文中,也有提到相關的觀念。另外附上那個時候google到的一個國外防止XSS的網頁:PHP XSS (cross site scripting) filter function,他有對HTML作是否可能被當作惡意的語法作判斷,不過測試後發現對中文的處理好像有點問題...就當作是英文的過濾函式吧~

Posted by taikobo0 at 樂多Roodo!18:07回應(0)引用(0)

April 6,2008

[PHP] ZendFramework 1.5.1安裝

ZendFramework是PHP的一個開發用框架,主要以PHP5作為撰寫時的基礎。要使用之前當然得先建立環境,以下是安裝的順序:參考「在Zend Framework上開發一個HelloWorld

1、首先先從ZendFramework的官方網站下在最新版本的ZendFramework 1.5.1;解壓縮後放在任意資料夾。我是放在「C:\ZendFramework」這個目錄下。

2、修改Apache httpd.conf設定:
(1)啟動 Apache的 .htaccess功能,搜尋「AllowOverride」 並將其設定為 All
(2)
開啟 LoadModule rewrite_module modules/mod_rewrite.so。

3、
修改PHP php.ini設定:
(1)設定include_path = ".;C:\ZendFramework"
(2)開啟extension=php_pdo.dll、extension=php_pdo_mysql.dll

重新啟動
Apache,如此一來基本的環境算是建置完成了。

Posted by taikobo0 at 樂多Roodo!22:22回應(0)引用(0)

March 11,2008

[PHP] 編碼轉換函數mb_convert_encoding()

一般正常在處理Big5<->UTF-8編碼上的轉換,第一個想到的就是iconv()函數;雖然需要額外安裝iconv函式庫,不過因為大部分人都有這種需求,虛擬主機商一般都會安裝,如果是自己的主機就更沒有這種問題了~

在Big5轉換成UTF-8方面,完全沒有問題;不過在UTF-8轉換成Big5方面,可就出現問題啦!因為UTF-8編碼對應的字遠比Big5編碼多,所以在一些特殊中文字的轉換上,如:堃、犇等字,iconv()函數就會出現錯誤啦!本來一直都沒有發現到這方面的問題,不過最近在公司專案管理系統上的Excel表轉出時,遇到有對方公司有「犇」這個字,讓iconv()函數發生錯誤而導致轉出的Excel檔案整個損壞的情況發生...Excel在正常輸入時是對應UTF-8編碼的,不過在轉出檔案時必須改為Big5編碼,不然UTF-8編碼的中文字會變成亂碼~無法自動判斷真是傷腦筋耶!

後來上網找了很久,都沒有令人滿意的答案;大家在轉出Excel檔時,幾乎都還是利用iconv()函數先轉成Big5編碼再轉出。後來我發現到另外一個函數:mb_convert_encoding(),似乎也是在處理編碼轉換的函數。試用之後雖然不存在的字還是不會自己生出來(如:堃、犇這些Big5編碼本來就沒有的字),但是轉不出來的字它會以「?」顯示,不會發生錯誤訊息,自然也就能正確的開啟轉出的Excel檔囉!雖然這並不是治本的方法,不過算是有稍微解決一些問題啦!

編碼真的是我目前最深的痛,轉來轉去真的很麻煩;目前我寫的PHP已經一律採用UTF-8編碼了~在網頁上是沒什麼問題,不過需要配合其他程式(如:Excel)時就必須轉來轉去...還蠻麻煩的哩!希望以後開發的軟體都能支援Unicode,畢竟萬國碼才是未來的趨勢吧!

Posted by taikobo0 at 樂多Roodo!10:37回應(5)引用(0)

March 6,2008

[PHP] 匯出pdf檔的方法

偶然在網路上看到介紹的文章:免費好用的 PDF Library 大蒐集。原本是想用來寫一個套印的小程式的,不過沒想到這玩意兒這麼難搞;因為中文編碼的關係。何況已經有人寫出類似的程式囉:藍色小舖PHP -> FPDF。還是稍微記錄一下使用的過程吧!

說到pdf檔產生,網路上似乎一面倒的推薦FPDF,其他pdf產生的class也幾乎是在它的基礎下衍生出來的~算是元老級的pdf class!另外對於中文的支援有提供
chinese.zip、以及支援中文Unicode的chinese-unicode.zip;目前我試用中文Unicode的FPDF可以完整呈現!(未使用Unicode的FPDF不知道為什麼我的FoxitReader一片空白...)以下是範例的程式碼:

<?php
require('fpdf/chinese-unicode.php');  //include必要程式

$pdf=new PDF_Unicode();  //
調用PDF_Unicode class

$pdf->Open();
$pdf->AddPage();
$pdf->AddUniCNShwFont('uni');
$pdf->SetFont('uni','',20);
 
$pdf->Write(10, "1234abcd學生名字\n伃綉堃亘");
$pdf->Ln();
$pdf->MultiCell (120, 10, "服\n務\n單\n位");
$pdf->Cell (240, 10, "本文用UTF8做為中文字編碼, 在這裡還是呼叫同樣的FPDF函數");
$pdf->Ln();

$pdf->Output();
?>

我另外還試過TCPDF,號稱支援Unicode,日文、德文、阿拉伯文洋洋灑灑的列了十幾種...只可惜獨漏中文(繁/簡體)~而且它所謂支援
Unicode的方法,是直接將字型檔壓縮在pdf檔中,所以會造成pdf檔的異常肥大;實在不是一種很優的方式。

Posted by taikobo0 at 樂多Roodo!15:16回應(0)引用(0)

March 3,2008

[PHP] Excel檔案轉出的方式

轉入之後當然接下來的就是轉出囉!我原本採用的轉出方法是非常陽春的斷欄「\t」與斷行「\n」的應用,所以在使用M$ Excel 2007時總是會出現錯誤訊息提示。與轉入的方式比較起來,Excel檔案轉出的方式似乎比較多:PHP導入導出Excel方法小結。裡面共介紹6種方式~不巧在下小弟我因為儲存格格式的問題,幾乎把所有方法都試過一遍了...下面來分別介紹使用方式與心得吧!

1、PHPExcel:PHPExcel,一個最好的控制excel的類

非常可惜的這是我能找到介紹最完整的網路說明~這套class是屬於非常新的一個class,所以關於用法網路上並沒有介紹的很詳細;官方網站裡的說明文件也非常陽春...雖然功能強大,而且通吃M$ Excel 2007及其以下的各版本,文章裡大力推薦,但是因為說明文件資料不足、網上範例太少、寫入時採用英文字母,很難用迴圈跑出來(汗)、儲存格格式無法設定...等原因,最後放棄這一個方法。

/*PHPExcel使用*/
error_reporting(E_ALL);  //開啟錯誤顯示(?)
set_include_path(get_include_path() . PATH_SEPARATOR . '../Excel/');  //設定class路徑
include 'PHPExcel.php';  //include必要程式
include 'PHPExcel/Writer/Excel5.php';  //使用Excel 2003以下的版本

$objPHPExcel = new PHPExcel();  //調用PHPExcel class
$objWriter = new PHPExcel_Writer_Excel5($objPHPExcel);  //調用Excel 2003以下的版本
$objPHPExcel->setActiveSheetIndex(0);  //設定動作Sheet      
       
$objPHPExcel->getActiveSheet()->setCellValue('A1', '日期');  //指定A1儲存格內容
$objPHPExcel->getActiveSheet()->setCellValue('B1', '姓名');  //指定B1儲存格內容
$objPHPExcel->getActiveSheet()->setCellValue('C1', '上班時數');  //指定C1儲存格內容
$objPHPExcel->getActiveSheet()->setCellValue('D1', '加班時數');  //指定D1儲存格內容
$objPHPExcel->getActiveSheet()->setCellValue('E1', '工程案號');  //指定E1儲存格內容
       
$objPHPExcel->getActiveSheet()->setTitle('Simple');  //指定Sheet名稱
$objWriter->save('error_log.xls');  //另存Excel檔案

最後儲存的檔案會再放置網頁的資料夾中,如果要讓使用者另存下載,就必須加上header。

2、pear的Spreadsheet_Excel_Writer:關於PEAR類庫中用於操作EXCEL的類庫Spreadsheet_Excel_Writer

這算是一種歷史悠久的方法,運用PHP非常強大的函式庫pear;關於pear我其實一直不是很了解,這次剛好藉由這次機會接觸。首先是pear的安裝,實際跑過一遍以後才發現原來安裝這麼容易:直接執行PHP目錄下的「go-pear.bat」,安裝過程一直按「Enter」就OK了!安裝完成以後,PHP目錄下PEAR的目錄裡面就會有資料了,此時pear就算安裝完成啦!接下來是Spreadsheet_Excel_Writer的安裝;在命令提示字元下執行:

pear install OLE-0.5
pear install Spreadsheet_Excel_Writer-0.9.1

pear就會自動上網下載與安裝!感覺好linux唷~安裝也變得非常容易。全部安裝完成以後,只剩下要注意pear在PHP裡的path路徑是否正確,就可以直接include進來囉!使用Spreadsheet_Excel_Writer唯一比較麻煩的是必須先安裝pear及其函式庫,使用上非常便利。原本是我心目中的第一首選!不過不知道為什麼,我們公司的伺服器pear裝不起來,試了好久都不行;雖然網路上說可以直接COPY pear整個資料夾再設定path,不過因為怕不正確安裝會導致不可預期的後果,且儲存格格式也是無法設定;所以放棄此一方法。

/*PEAR的Spreadsheet_Excel_Writer*/
require_once 'Spreadsheet/Excel/Writer.php';  //require必要程式
       
$workbook = new Spreadsheet_Excel_Writer();  //調用Spreadsheet_Excel_Writer class
$workbook->setVersion(8);  //設定Excel版本為XP以上
$worksheet =& $workbook->addWorksheet('Sheet1');  //增加一個Sheet
$worksheet->setInputEncoding('utf-8');  //設定編碼為UTF   
$format_locked =& $workbook->addFormat();
$format_locked -> setLocked();  //設定鎖定格式,防止別人修改
//$worksheet->setColumn(0,255,8.38,$format_locked);  //此為指定整個工作表鎖定狀態 
     
$worksheet->writeString(0, 0, '日期');  //設定(0,0)儲存格內容
$worksheet->writeString(0, 1, '姓名');  //設定(0,1)儲存格內容
$worksheet->writeString(0, 2, '上班時數');  //設定(0,2)儲存格內容
$worksheet->writeString(0, 3, '加班時數');  //設定(0,3)儲存格內容
$worksheet->writeString(0, 4, '工程案號');  //設定(0,4)儲存格內容

//錯誤資料寫入Excel      
for ($i = 1;$i <= $error_row;$i++){
    for ($j = 1;$j <= 5;$j++){
        $worksheet->write($i, $j-1, $error_msg[$i][$j]);
    }
}

$workbook->send('error_log.xls');  //另存下載Excel檔案          
$workbook->close();  //關閉(?)

3、XML轉出:使用 PHP 輸出帶格式的 Excel 文件

話說M$ Excel從2003開始,導入了對XML檔案的支援,終於冥頑不靈的M$也向開放性格事低頭了呢!只要由PHP輸出正確的XML格式,再另存成xls檔就可以餵給Excel讀了吧...本來是這樣想的...沒想到我用的M$ Excel 2007在另存成XML檔時好像還得經過些設定;我整個不知道要怎麼做啊!在不確定因素及設定太過複雜的情況下,我放棄了這個方法~(其實也是懶得弄一些有的沒的啦...)

4、利用pack()函數將資料包裝使其接近Excel格式:這段操作excel的代碼應該怎麼操作設置每列的寬度

如同標題說,就是利用pack()函數將資料包裝使其接近Excel格式。Excel的格式是M$自定的一種資料格式,這個方法是利用PHP的pack()函數,把我們所需的資料包裝成接近Excel原始格式的一種方法;其實說穿了不過就是把「\t」「\n」替換掉的另一種陽春的方法。不過這種方是因為接近Excel原始格式,所以在開啟的時候並不會有錯誤訊息提示,算是一種簡單易懂又最貼近Excel格式的方法。文章裡也推薦這個方法,所以最後我採用了這個方式~雖然它仍然無法設定儲存格格式...(汗)

/*利用pack()函數將資料包裝使其接近Excel格式*/
$filename = 'error_log.xls';  //設定另存下載檔案名稱
       
header ('Content-type: application/x-msexcel');  //送出header
header ("Content-Disposition: attachment; filename={$filename}" );
       
xlsBOF();  //起始包裝函數
//標題列
xlsWriteLabel(0, 0, u2b('日期'));  //設定(0,0)儲存格內容,儲存格內容為文字
xlsWriteLabel(0, 1, u2b('姓名'));  //設定(0,1)儲存格內容,儲存格內容為文字
xlsWriteLabel(0, 2, u2b('上班時數'));  //設定(0,2)儲存格內容,儲存格內容為文字
xlsWriteLabel(0, 3, u2b('加班時數'));  //設定(0,3)儲存格內容,儲存格內容為文字
xlsWriteLabel(0, 4, u2b('工程案號'));  //設定(0,4)儲存格內容,儲存格內容為文字
//錯誤列 
for ($i = 0;$i < $error_row;$i++){
    xlsWriteLabel($i + 1, 0, $error_msg[$i][0]);  //設定($i + 1,0)儲存格內容,儲存格內容為文字
    xlsWriteLabel($i + 1, 1, $error_msg[$i][1]);  //設定($i + 1,1)儲存格內容,儲存格內容為文字
    xlsWriteNumber($i + 1, 2, $error_msg[$i][2]);  //設定($i + 1,2)儲存格內容,儲存格內容為數字
    xlsWriteNumber($i + 1, 3, $error_msg[$i][3]);  //設定($i + 1,3)儲存格內容,儲存格內容為數字
    xlsWriteLabel($i + 1, 4, $error_msg[$i][4]);  //設定($i + 1,4)儲存格內容,儲存格內容為文字
}
xlsEOF();  //結束包裝函數

//Excel生成用函數,起始包裝
function xlsBOF() {
    echo pack("ssssss", 0x809, 0x8, 0x0, 0x10, 0x0, 0x0); 
    return;
}
//Excel生成用函數,結束包裝
function xlsEOF() {
    echo pack("ss", 0x0A, 0x00);
    return;
}
//Excel生成用函數,數字包裝用
function xlsWriteNumber($Row, $Col, $Value) {
    echo pack("sssss", 0x203, 14, $Row, $Col, 0x0);
    echo pack("d", $Value);
    return;
}
//Excel生成用函數,文字包裝用
function xlsWriteLabel($Row, $Col, $Value ) {
    $L = strlen($Value);
    echo pack("ssssss", 0x204, 8 + $L, $Row, $Col, 0x0, $L);
    echo $Value;
    return;
}

5、使用「\t」、「\n」的方法:

這就是我原來使用的方法,雖然簡單易懂易用,但是因為用M$ Excel 2007開啟時總是會出現錯誤訊息提示~所以才換掉。順便一提,它也沒辦法設定儲存格格式。

/*最原始的\t\n用法*/
$filename = 'error_log.xls';  //設定另存下載檔案名稱
       
header("Content-Type: application/vnd.ms-excel");  //送出header,這我是直接從網路上抄來的
header("Content-Disposition:filename=sub.xls");
header("Content-Disposition: attachment; filename={$filename}");
header("Pragma: no-cache");
header("Expires: 0");
       
$error = '';  //起始錯誤字串的值
//      
for ($i = 1;$i <= $error_row;$i++){
    for ($j = 1;$j <= 5;$j++){
        $error .= $error_msg[$i][$j]."\t";  //跨欄
    }
    $error .= "\n";  //斷行
}
echo $error;  //印出完整錯誤字串

6、使用com()函數:

這個方式應該是設定功能最完整個方法!因為它是直接調用Server端的M$ Excel來產生檔案的,關於這個方法我沒有實際研究。因為這個方法有限制:它只適用在Server為M$ Windows作業系統平台,且必須安裝M$ Office才能用;也就是說,如果作業平台是Linux,或是沒有安裝M$ Office就不能用。非常剛好的,我們公司的Server雖然是M$ Windows作業系統平台,卻沒有安裝M$ Office...想當然爾我就放棄這個方法啦!不過這或許是唯一一個可以設定儲存格格式的方法...

很難想像為了儲存格格式,我把網路上傳授的6種方法幾乎都試過了...雖然最後仍然沒有解決我的問題,不過因此而認識到PHP的無限可能~也學到不少東西哩!最後我對日期的處理方式,是消極的用錯誤訊息防止使用者匯入錯誤格式;這也不失為解決問題的其中一個方法啦!^^

Posted by taikobo0 at 樂多Roodo!8:35回應(13)引用(0)

February 26,2008

[PHP] Excel檔案轉入的方式

依然是專案管理系統的功能。因為之前Excel檔案的匯入是採用另存新檔後的csv匯入;因為以PHP來說,xls的Excel檔內含太多不可確定的格式,不如單純的逗號分隔檔案csv的資料來的純粹。不過站在使用者的角度,「另存新檔」本身就是一個多餘的動作,何況再轉存成csv檔後並不是直接按確定這麼簡單;因為Excel檔案格式的改變,所以會有M$貼心的提醒...總之最後導致整個匯入動作感覺很不友善。

儘管PTT PHP版一面倒的建議採用csv檔作資料的讀入處理,但是使用者才不管你這麼多勒!對使用者友善,就是對程式設計者的殘忍啊...然而,之所以會需要程式設計者,也是為了要對使用者更友善啊~所以,我找了一下網路上直接上傳Excel檔轉入資料的方法,目前找到二種試驗後都成功的方法:

第一種是利用PHP的COM()函數,呼叫PHP所在Server的M$ Office程式做處理。這個函數運用的感覺比較像是在寫bat檔,利用一連串指令在使用者不知情的狀況下完成想要達到的目的;當然,缺點就是這種方式只適用在M$ Windows的作業系統,而且Server上還必須要有M$ Office...

雖然說是直接上傳Excel檔轉入資料,但說穿了不過是利用COM()函數在使用者不知情的情況下另存成csv檔後,再作處理。以下是利用COM()函數另存新檔的程式碼:

$excel = new COM("excel.application") or die("Unable to instanciate excel");  //調用COM class,順便判斷是否能正常連結Excel應用程式
$excel->DisplayAlerts = 0;  //關閉開啟Excel時的警告
$strTemp = 'C:\\xxx\\xxx\\xxx\\csv\\'.$file_name;  //上傳成功檔案的絕對路徑
$excel->Workbooks->Open($strTemp);  //打開該檔案
$csvfile_name = substr($file_name, 0,strlen($file_name)-4).'.csv';  //利用原檔名將副檔名改寫成csv
$strTemp = 'C:\\xxx\\xxx\\xxx\\csv\\'.$csvfile_name;  //另存成csv檔後的絕對路徑
$excel->ActiveWorkbook->SaveAs($strTemp ,6);  //另存新檔,6代表csv逗號分隔檔案                     
$excel->Quit();  //退出Excel
//$excel->Release();  //釋放Excel所佔用的記憶體...吧?不過我的電腦必須註解掉這行才能動作,不知道為什麼
$excel = null;  //釋放Excel所佔用的記憶體...吧?其實我不太知道這跟上面有什麼不同

下來就是利用另存的csv檔對資料做處理囉!基本上作法沒有變,只是使用者可以省略自己另存的動作,而由Server代替轉存,使用者自然會覺得方便許多。可惜本公司的Server雖然是M$ Windows系統,但是並沒有安裝M$ Office;所以只能用第二種方法囉。

第二種方法是偉大的前輩事先做好的class:PHP-ExcelReader。PHP真不愧為網路程式設計的龍頭,許多功能其實前輩們都已經想好了!其實主要用的的檔案也只有二個:oleread.inc跟reader.php。而且因為已經封裝成class,使用方面也非常的簡單,以下式簡單的程式碼:

require_once '../Excel/reader.php';
$data = new Spreadsheet_Excel_Reader();  //調用Spreadsheet_Excel_Reader class
$data->setOutputEncoding('utf-8');  //設定輸出的編碼,可直接選擇輸出為UTF8編碼
$data->read("../xxx/xxx/test.xls");  //檔案的路徑(可使用相對路徑)
error_reporting(E_ALL ^ E_NOTICE);  //錯誤顯示

for ($i = 1; $i <= $data->sheets[0]['numRows']; $i++) {
    for ($j = 1; $j <= $data->sheets[0]['numCols']; $j++) {
        echo "\"".$data->sheets[0]['cells'][$i][$j]."\",";
    }
    echo "\n";
}

利用陣列的方式,擷取Excel檔裡面的資料;['numRows']列、['numCols']行、['cells']元素,搭配二維陣列取得。擷取出來的資料就可以直接對其作處理,非常容易使用!

關於Excel檔寫入也有另一個class:Spreadsheet_Excel_Writer。這個class需要pear的支援,所以使用前必須先安裝pear;這個部份我還沒有深入研究測試,就先行打住。另外還有一個class比較新,是支援M$ Office Excel 2007的格式:PHPExcel。這個class同時支援讀入跟寫出,不過目前只針對Excel 2007的版本作處理。目前我們公司仍以2003為主,所以這個版本也沒有深究。當然2007一定是未來的趨勢,所以以後應該還是有機會會接觸到吧。


Posted by taikobo0 at 樂多Roodo!9:08回應(6)引用(0)

February 13,2008

[PHP] 去除特殊字元:str_replace()函數運用

因為公司會計部門的人還是比較喜歡(習慣)過去使用Excel編輯成本的環境,新系統上線的不習慣在無形間浪費了許多工時;所以現在專案管理系統在輸入方面將新增轉入功能。簡單說就是利用原先熟悉的Excel環境提供轉檔程式轉進資料庫中。不過這也代表著我先前做的輸入介面與自動完成完全無用武之地啊~(噗)當然已經作的介面也不需要特別去刪掉,就當作有二種輸入介面吧!

因為Excel格式之複雜,導致PHP在轉檔時會發生許多不可預期的問題;所以不論是網路上或是PTT幾乎都建議改以csv檔作轉入的動作;Excel本身也有提供另存成csv檔的功能~也算是一種折衷的方案囉。

之前有試寫人工成本Excel的資料轉入,昨天嘗試著修改了一下介面與原系統整合,本來以為已經沒有問題了~沒想到今天早上卻發現原始csv檔內的特殊字元ex:\t、\n、\r在作怪,導致原先的Javascript警告視窗失效。上網搜尋一下馬上就找到解決的辦法啦~網路真是大家好朋友:PHP如何取消字串裡的特殊字元呢??

主要是利用str_replace()函數,運用取代函數將特殊字數取代掉;是很聰明的應用方式哩!其中以陣列方式一次取代是最方便:


$str = str_replace(array("\n","\t","\r"), array("","",""), $str);

剛好我從csv檔取得的資料也是陣列的格式,真是幫了我一個大忙哩^^

Posted by taikobo0 at 樂多Roodo!9:33回應(0)引用(0)

February 6,2008

[PHP] 利用GD函式庫,製作互動式圖片

很久以前就有這樣的計畫,利用現有的圖片由使用者自行加註文字,然後合成為另一張新的圖片~爬了一下PTT的PHP版,很快就看到類似的東西。剛好十天的假期在家閒閒沒事,就來研究一下這玩意兒要怎麼弄吧。

說到跟圖片有關的東西,就不能忘了PHP的GD函式庫啦!它強大的功能也曾經幫我在名片查詢系統時,利用PHP上傳名片圖檔的時候同步縮小圖片呢~這次運用到的函數是ImageTTFText,它可以利用自備的TrueType字體檔,寫入文字到圖形中。最近我對微軟正黑體還蠻有好感的,再加上它算是一套免費(由微軟官方釋出,不過好像是不能任意提供給人家下載啦)的字體;拿來當圖片用的字型,在適合不過啦!

下面是php程式碼:


header("content-type:image/jpeg"); // 送出JPG的header
$nimage=imagecreatetruecolor(1024,729); // 建立一個寬 1024 高 729 像素的圖片
$black=imagecolorallocate($nimage,0,0,0); // 設定文字顯示RGB顏色
$simage =ImageCreateFromJPEG('card2008.jpg'); // 利用ImageCreateFromJPEG函數讀取原始圖片
imagecopy($nimage,$simage,0,0,0,0,1024,729); // 利用imagecopy函數複製原始圖片到建立的新圖片上
$font = '/xxx/xxx/xxx.TTF'; // 字形路徑設定
ImageTTFText($nimage,26,0,60,570,$black,$font,$wish_word); // 利用ImageTTFText函數將文字合併於圖片
ImageJPEG($nimage); // 輸出JPEG圖片
imagedestroy($nimage);
imagedestroy($simage); //釋放之前暫存圖片的記憶體


當然,重點是在ImageTTFText函數的用法:

ImageTTFText($nimage,26,0,60,570,$black,$font,$wish_word); 

$nimage:新建立的圖片的變數名
26:字型的大小
0:字型的角度
60:X座標軸位置
570:Y座標軸位置
$black:字型顏色
$font:字體檔位置;這邊因為我架設主機的平台是linux,不知道為什麼相對位置整個沒反應。弄了好久最後用絕對位置搞定,所以要特別注意。
$wish_word:輸出於圖片上的話

另外因為輸出字數的問題也花了我一點時間...
因為ImageTTFText不會自動幫你斷行,所以必須自己加『\n』去斷行
UTF-8的編碼方式中文字是3個字元,解決方法就是自己算字數然後加『\n』:

if(strlen($wish_words) > (26*3) and (strlen($wish_words) < (26*3*3+1))){
   for($i=0;$i<floor(strlen($wish_words)/78);$i++){
    $wish_word .= substr($wish_words,($i*78),78)."\n";
   }
}

我設定在字數多於26個字時斷行
其中floor()函數是取小數點後無條件捨去的正整數,另一個ceil()函數則是無條件進位後的正整數
這裡我使用floor()函數
substr()函數擷取我要的字段後在自己加『\n』斷行

其實我來以為這只是一個很簡單的GD函數運用,可是因為字型檔路徑跟編碼問題還是讓我花了不少時間
成功了以後又覺得怎麼那麼簡單?
所以雖然函數就在那,但還是要用過才知道用法,才知道會碰到什麼問題,要怎麼解決!

因為從前年開始,我每年都會為PTT的資科系版畫新年賀圖
今年就想利用這個賀圖結合一些祝福的話,在除夕夜的簡訊中連同網址一起發出
所以才會心血來潮的想做這玩意兒;本來以為簡單的東西,其實還是花了不少時間。
這裡是成果,祝大家新年快樂^^

Posted by taikobo0 at 樂多Roodo!12:13回應(0)引用(0)
 [1]  [2]  [最終頁]