摘要: 最近一個項目開發要用到PHP技術導出Word文檔,采用PHPWord插件,版本為0.6.2 beta,CodePlex已停止維護。網上還有另外一個版本的PhpWord,項目類名大小寫上略有不同,隸屬於PHPOffice/PHPWord,GitHub項目地址。這個版本的PHPWord為CodePlex停止維護后添加,目前更新至0.15,個人覺得0.12作者更新的Release較為實用,此項目內容更加豐富,支持的功能也比較多(包括行間距,縮進和首行縮進等)。但是有些API,在PHPOffice/PHPWord里是不推薦的,比如createSection需要改成addSection,另外應用這個版本的PHPWord不需要像PHPWord 0.6.2那樣做任何中文支持的修改。本文重點就PHPWord 0.6.2 作一介紹。
1、增加東亞字體支持
打開/Writer/Word2007/Base.php文件,大概在第349行,函數_writeTextStyle內添加:
$objWriter->writeAttribute('w:eastAsia', $font)
修改后的內容如下:
if($font != 'Arial') { $objWriter->startElement('w:rFonts'); $objWriter->writeAttribute('w:eastAsia', $font); // 添加這行 $objWriter->writeAttribute('w:ascii', $font); $objWriter->writeAttribute('w:hAnsi', $font); $objWriter->writeAttribute('w:cs', $font); $objWriter->endElement(); }
2、默認模板中文亂碼(此模板后面會修改,不推薦此方法)
打開/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); }
中文調用方式也要修改:
$document->setValue('Template', iconv('utf-8', 'GB2312//IGNORE', '中文'));
3、中文亂碼問題
打開/PHPWord/Section.php,找到代碼$givenText = utf8_encode($text);修改為$givenText = iconv('gbk', 'utf-8', $text);代碼如下:
/** * Add a Text Element * * @param string $text * @param mixed $styleFont * @param mixed $styleParagraph * @return PHPWord_Section_Text */ public function addText($text, $styleFont = null, $styleParagraph = null) { //$givenText = utf8_encode($text); $givenText = iconv('gbk', 'utf-8', $text); // 注釋掉上面行后添加這行 $text = new PHPWord_Section_Text($givenText, $styleFont, $styleParagraph); $this->_elementCollection[] = $text; return $text; }
替換Section.php文件所有utf8_encode($參數)函數為iconv('gbk','utf-8',$參數)
同理修改/PHPWord/Section目錄下Header.php、Footer.php、TextRun.php、Table/Cell.php
其中TextRun.php是防止文本資源(段落連續)中文錯誤,Cell.php是防止表格中文錯誤。重點是addText函數。
調用方式修改為:
$section->addText(iconv('utf-8','GBK//IGNORE','中文'));
3、單元格合並問題(類colspan和rowspan)
打開PHPWord/Style/Cell.php,增加兩個私有屬性
private $_rowMerge = null; private $_cellMerge = null;
構造函數初始化賦值null
$this->_rowMerge=null; $this->_cellMerge=null;
同文件,增加如下方法
public function getRowMerge() { return $this->_rowMerge; } public function setRowMerge($pValue = null) { $this->_rowMerge = $pValue; return $this; } public function getCellMerge() { return $this->_cellMerge; } public function setCellMerge($pValue = null) { $this->_cellMerge = $pValue; return $this; }
編輯PHPWord/Writer/Word2007/Base.php,修改函數_writeCellStyle,$styles增加新屬性判斷
$rowMerge = $style->getRowMerge(); $cellMerge = $style->getCellMerge(); //$styles = (!is_null($bgColor) || !is_null($valign) || !is_null($textDir) || $borders) ? true : false; $styles = (!is_null($bgColor) || !is_null($valign) || !is_null($textDir) || $borders || !is_null($rowMerge) || !is_null($cellMerge)) ? true : false;
修改之后的if($styles)判斷條件,增加單元格合並內容判斷:
if (!is_null($cellMerge)) { $objWriter->startElement('w:gridSpan'); if ((string)$cellMerge !== 'continue') { $objWriter->writeAttribute('w:val', $cellMerge); } $objWriter->endElement(); } if (!is_null($rowMerge)) { $objWriter->startElement('w:vMerge'); if ((string)$rowMerge !== 'continue') { $objWriter->writeAttribute('w:val', $rowMerge); } $objWriter->endElement(); }
Rowspan調用方式
$table1->addCell(2000,array('rowMerge' => 'restart'))->addText(iconv('utf-8','GBK//IGNORE','中文'));//需要合並的第一行 $table1->addCell(2000,array('rowMerge' => 'continue')); //需要合並的其余行,有幾行需要復制幾行,一般是放循環里面
Colspan調用方式比價簡單
$table1->addCell(2000,array('cellMerge' => 'restart'))->addText(iconv('utf-8','GBK//IGNORE','中文'));//直接通過cell寬度控制即可
5、模板動態生成表格
默認導入模板之后,只能setValue,不能再增加行或文字
但一般表格文件均為動態行,/PHPWord/Template.php文件不再滿足要求
CloneRow提供了一個解決方案,GitHub項目地址
/** * 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.'}'; } 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; } } } } /** * Clone Rows in tables * * @param string $search * @param array $data */ public function cloneRow($search, $data=array()) {//新增如下兩函數 // remove ooxml-tags inside pattern foreach ($data as $nn => $fieldset) { foreach ($fieldset as $field => $val) { $key = '{'.$search.'.'.$field.'}'; $this->setValue($key, $key, 1); } } // how many clons we need $numberOfClones = 0; if (is_array($data)) { foreach ($data as $colName => $dataArr) { if (is_array($dataArr)) { $c = count($dataArr); if ($c > $numberOfClones) $numberOfClones = $c; } } } if ($numberOfClones > 0) { // read document as XML $xml = DOMDocument::loadXML($this->_documentXML, LIBXML_NOENT | LIBXML_XINCLUDE | LIBXML_NOERROR | LIBXML_NOWARNING); // search for tables $tables = $xml->getElementsByTagName('tbl'); foreach ($tables as $table) { $text = $table->textContent; // search for pattern. Like {TBL1. if (mb_strpos($text, '{'.$search.'.') !== false) { // search row for clone $patterns = array(); $rows = $table->getElementsByTagName('tr'); $isUpdate = false; $isFind = false; foreach ($rows as $row) { $text = $row->textContent; $TextWithTags = $xml->saveXML($row); if ( mb_strpos($text, '{'.$search.'.') !== false // Pattern found in this row OR (mb_strpos($TextWithTags, '<w:vMerge/>') !== false AND $isFind)
// This row is merged with upper row (Upper row have pattern) ) { // This row need to clone $patterns[] = $row->cloneNode(true); $isFind = true; } else { // This row don't have any patterns. It's table header or footer if (!$isUpdate and $isFind) { // This is table footer // Insert new rows before footer $this->InsertNewRows($table, $patterns, $row, $numberOfClones); $isUpdate = true; } } } // if table without footer if (!$isUpdate and $isFind) { $this->InsertNewRows($table, $patterns, $row, $numberOfClones); } } } // save document $res_string = $xml->saveXML(); $this->_documentXML = $res_string; // parsing data foreach ($data as $colName => $dataArr) { $pattern = '{' . $search . '.' . $colName . '}'; foreach ($dataArr as $value) { $this->setValue($pattern, $value, 1); } } } } /** * Insert new rows in table * * @param object &$table * @param object $patterns * @param object $row * @param int $numberOfClones */ protected function InsertNewRows(&$table, $patterns, $row, $numberOfClones) { for ($i = 1; $i < $numberOfClones; $i++) { foreach ($patterns as $pattern) { $new_row = $pattern->cloneNode(true); $table->insertBefore($new_row, $row); } } } }
請注意,此setValue函數與2不同,那么問題來了,需要進行中文編碼轉換
if(!is_array($replace)) {
$replace =iconv('gbk', 'utf-8', $replace); }
運行中,新版本PHP大概5.4之后會提示( ! ) Deprecated: Non-static method DOMDocument::loadXML() should not be called statically, assuming $this from incompatible context in /PHPWord/Template.php on line 168
非靜態函數不能直接采用類名::方法的方式調用,DOMDocument::loadXML,可按如下修改,也可直接前面@注釋掉錯誤提示即可。
$xml = new DOMDocument(); $xml->loadXML($this->_documentXML, LIBXML_NOENT | LIBXML_XINCLUDE | LIBXML_NOERROR | LIBXML_NOWARNING);
至此,舊版本PHPWord所有中文問題和表格合並及模板動態表格問題已OK。
注意:所有中文調用均需要iconv轉換
關注/PHPWord/Examples,有文本,表格,圖片,鏈接,對象等等操作實例
采用更新版本的PHPWord,無中文問題,樣式定義更方便,抽空再單獨介紹