使用PHPWord对Word文件做模板替换

因工作需要,使用了版本比较旧的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("\n","<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文件做模板替换

阅读 9k

推荐阅读
Heier
用户专栏

记录分享学习笔记和心得,方便网友点评和自己收藏

0 人关注
5 篇文章
专栏主页