使用PHPWord對Word檔案做模板替換

Heier發表於2019-02-16

因工作需要,使用了版本比較舊的PHPWord專案
官方已不見維護更新,上次版本更新是在Fri Jul 8, 2011 at 8:00 AM
如果PHP版本>=5.3.3,強烈推薦使用PHPOffice/PHPWord這個開源專案
本篇針對的為舊版本的PHPWord

基本安裝

見官網

問題總結

Autoloader自動載入部分情況下失敗

在使用Yii 1配置自動載入時無法正常載入類庫,需對其PHPWord/Autoloader.php做部分調整,這兒借鑑了PHPExcelAutoloader:

/**
 * PHPWord_Autoloader
 */
class PHPWord_Autoloader
{
    /**
     * Register the Autoloader with SPL
     *
     */
    public static function Register() {
        $functions = spl_autoload_functions();  
        foreach ( $functions as  $function)  
            spl_autoload_unregister($function);  
        $functions = array_merge(array(array(`PHPWord_Autoloader`,`Load`)),$functions);  
        foreach ( $functions as $function)  
            $x = spl_autoload_register($function);  
        return $x; 

    }   //    function Register()


    /**
     * Autoload a class identified by name
     *
     * @param    string    $pClassName        Name of the object to load
     */
    public static function Load($pClassName){
        if ((class_exists($pClassName,FALSE)) || (strpos($pClassName, `PHPWord`) !== 0)) {
            //    Either already loaded, or not a PHPWord class request
            return FALSE;
        }

        $pClassFilePath = PHPWORD_BASE_PATH .
                          str_replace(`_`,DIRECTORY_SEPARATOR,$pClassName) .
                          `.php`;

        if ((file_exists($pClassFilePath) === FALSE) || (is_readable($pClassFilePath) === FALSE)) {
            //    Can`t load
            return FALSE;
        }

        require($pClassFilePath);
    }   //    function Load()

}

模板替換時無法識別模板標籤

表現

  1. 使用/複製官方樣例的模板檔案替換正常

  2. 自己手動敲出模板標籤替換異常

原因

  1. PHPWord的替換規則是將Word檔案解析成XML進行替換處理,當Word解析成XML時字元分離了,導致匹配不上模板標籤;

  2. 具體分析可參考一下資料:

解決辦法

參考http://stackoverflow.com/a/21750677/5270710

改進Template類:
可參考Github: Arisse/PHPWord_CloneRowTemplate類進行改造。
因為下面仍需要修改Template類,這兒暫時就不貼程式碼了,下面一併貼出改造後的程式碼。

中文亂碼

參考CSDN: PHPWord利用模板替換字串生成精確的word文件

編輯PHPWord/Template.php,找到程式碼$replace = utf8_encode($replace);,刪除或者註釋掉這行程式碼,新增$replace = iconv( `gbk`,`utf-8`, $replace);,比如程式碼改為如下:

/**
 * Set a Template value
 * 
 * @param mixed $search
 * @param mixed $replace
 */
public function setValue($search, $replace) {
    if(substr($search, 0, 2) !== `${` && substr($search, -1) !== `}`) {
        $search = `${`.$search.`}`;
    }
 
    if(!is_array($replace)) {
        //$replace = utf8_encode($replace);
        $replace =iconv(`gbk`, `utf-8`, $replace); // 註釋掉上面行後新增這行
    }
 
    $this->_documentXML = str_replace($search, $replace, $this->_documentXML);
}

空格輸出

參考CSDN: PhpWord 如何輸出換行符到 word?

在想要輸出換行的地方用<w:br />代替即可.

標記符號輸出

參考百度經驗: 如何在word中選擇打鉤的方框
僅以輸出為例,其它符號與之類似。
注:PHP檔案需要使用UTF-8編碼

  1. Word檔案中按照參考檔案方式插入

  2. 複製符號到PHP檔案;

  3. 正常的輸出替換。
    具體程式碼見如下的專案程式碼

Template類程式碼

// code
/**
* Set a Template value
*
* @param mixed $search
* @param mixed $replace
*/
public function setValue($search, $replace, $limit=-1) {
    if(substr($search, 0, 1) !== `{` && substr($search, -1) !== `}`) {
        $search = `{`.$search.`}`;
    }
    
    if(!is_array($replace)) {
        // $replace = utf8_encode($replace);
        // $replace = iconv( `gbk`,`utf-8`, $replace);
        $replace = str_replace("
","<w:br />",$replace);
    }

    preg_match_all(`/{[^}]+}/`, $this->_documentXML, $matches);
    foreach ($matches[0] as $k => $match) {
        $no_tag = strip_tags($match);
        if ($no_tag == $search) {
            $match = `{`.$match.`}`;
            $this->_documentXML = preg_replace($match, $replace, $this->_documentXML, $limit);    
            if ($limit == 1) {
                break;
            }            
        }
    }
}
// code

專案程式碼

// @author Heier xheier@outlook.com
public function actionExportPersonTable() {
    // 獲取資料部分程式碼
    // ...

    $PHPWord = new PHPWord();
    
    // Word模板目錄
    $personBasePath = Yii::app()->basePath.`/person/`;
    
    // 刪除目錄下臨時檔案-十分鐘以前
    $this->delfile( $personBasePath, 10 );
    
    // 模板檔名
    $tempName = $personBasePath . `/moban.docx`;
    
    $word = $PHPWord->loadTemplate( $tempName );
    
    // 專案使用的是GBK編碼,需要做轉換
    $username = iconv(`gbk`, `utf-8`, getUserNameById($personData[0][`user_id`]) );
    $personal_type = $personData[0][`personal_type`];
    
    // 模板替換開始
    // 可以輸出打勾的方框
    $deptA=$deptBP=$deptB=$deptC=$deptD = `□`;
    if( $DirectorLevel == `A` ) {
        $deptA = `☑`;
    } elseif( $DirectorLevel == `B+` ) {
        $deptBP = `☑`;
    } elseif( $DirectorLevel == `B` ) {
        $deptB = `☑`;
    } elseif( $DirectorLevel == `C` ) {
        $deptC = `☑`;
    } elseif( $DirectorLevel == `D` ) {
        $deptD = `☑`;
    }

    $word->setValue(`deptA`, $deptA);
    $word->setValue(`deptBP`, $deptBP);
    $word->setValue(`deptB`, $deptB);
    $word->setValue(`deptC`, $deptC);
    $word->setValue(`deptD`, $deptD);

    // 設定其它替換
    // ...
    
    // 生成臨時檔案以供下載
    $tmpFileName = md5( time().`Heier` );
    
    $word->save($personBasePath . `/` . $tmpFileName .`.docx`);
    $file = $personBasePath . `/` . $tmpFileName .`.docx`;
    
    // 下載Word檔案
    ob_start(); //開啟緩衝區
    $fp = fopen($file,"r");
    $file_size = filesize($file);
    $downFileName = `XXX.docx`;
    
    header("Cache-Control: public");
    header("Content-type: application/octet-stream");
    header("Accept-Ranges: bytes");
    header("Content-Disposition: attachment; filename={$downFileName}");
    header("Pragma:no-cache");
    header("Expires:0");
    $buffer = 1024;
    $file_count = 0;
    //向瀏覽輸出回資料
    while(!feof($fp) && $file_count < $file_size){
        $file_con = fread($fp,$buffer);
        $file_count += $buffer;
        echo $file_con;
    }
    ob_end_flush();//輸出全部內容到瀏覽器
}

參考文件彙總

  1. CodePlex: PHPWord;

  2. Github: PHPOffice/PHPWord;

  3. phpword: setValue() not working;

  4. stackoverflow: PhpWord doesn`t replace text;

  5. Github: Arisse/PHPWord_CloneRow;

  6. CSDN: PHPWord利用模板替換字串生成精確的word文件;

  7. CSDN: PhpWord 如何輸出換行符到 word?

關於我

文章轉載自我的部落格:
Heier Blog: 使用PHPWord對Word檔案做模板替換

相關文章