提示:不支持.doc文件的读取
有一个客户有这样的需求,需要在ThinkPHP里使用PHPWord组件,把一个文档(DOC1)的内容,插入另一个文档(DOC2)的指定页内。由于两个文档的内容都不是固定的,所以不能使用PHPWord的Template功能。
以前从来没有使用过PHPWord,所以前后也折腾了几天。从熟悉PHPWord的功能开始,看示例代码,还有源码,最终还是摸索出来解决方案,下面说下解决思路:
首先读取DOC1的内容,PHPWord把内容分成不同的节点和容器,最顶级的是Section,里面有TextRun(文本块),Table(表格)等容器。这些容器里又有Text(文本),Row(表格行),Cell(单元格)等,还有一些其他的TextBreak,PageBreak(分页符)等制表符。
逐级读取内容后,然后把读取出的内容插入到一个新的文档内。当读取到指定的分页符之后,再读取DOC2的内容,跟着前面的内容插入,最后保存新的文档。
贴一部分代码:
namespace Home\Logic;
Vendor('PhpOffice.autoload');
use PhpOffice\PhpWord\PhpWord;
use PhpOffice\PhpWord\IOFactory;
use PhpOffice\PhpWord\Style\Font;
use PhpOffice\PhpWord\Shared\ZipArchive;
use PhpOffice\PhpWord\Settings;
class MergeFile {
private $currentPage = 0; // 当前分页
private $page = 0; // 插入页数
private $args = null; // 文本段样式
private $tmpFiles = []; // 临时文件
/**
* 合并文件
*
* @param URI
* 文件1地址
* @param URI
* 文件2地址
* @param Numeric
* 指定插入的页数
*
* @return String
* 新文件的URI
*/
public function joinFile($file1, $file2, $page)
{
$S1 = IOFactory::load($file1)->getSections();
$S2 = IOFactory::load($file2)->getSections();
$this->page = $page > 0 ? $page - 1 : $page;
$phpWord = new PhpWord();
foreach ($S1 as $S) {
$section = $phpWord->addSection($S->getStyle());
$elements = $S->getElements();
# 逐级读取/写入节点
$this->copyElement($elements, $section, $S2);
}
$F1 = IOFactory::createWriter($phpWord);
$path = $_SERVER['DOCUMENT_ROOT'] . __ROOT__ . '/Public/Write/';
if(!is_dir($path)) mkdir($path);
$filePath = $path . time() . '.docx';
$F1->save($filePath);
# 清除临时文件
foreach($this->tmpFiles as $P) {
unlink($P);
}
return $filePath;
}
/**
* 逐级读取/写入节点
*
* @param Array
* 需要读取的节点
* @param PhpOffice\PhpWord\Element\Section
* 节点的容器
* @param Array
* 文档2的所有节点
*/
private function copyElement($elements, &$container, $S2 = null)
{
$inEls = [];
foreach ($elements as $i => $E) {
# 检查当前页数
if ($this->currentPage == $this->page && !is_null($S2)) {
# 开始插入
foreach ($S2 as $k => $v) {
$ELS = $v->getElements();
$this->copyElement($ELS, $container);
}
# 清空防止重复插入
$S2 = null;
}
$ns = get_class($E);
$elName = end(explode('\\', $ns));
$fun = 'add' . $elName;
# 统计页数
if ($elName == 'PageBreak') {
$this->currentPage ++;
}
# 合并文本段
if($elName == 'TextRun'
#&& !is_null($S2)
) {
$tmpEls = $this->getTextElement($E);
if(!is_null($tmpEls)) {
$inEls = array_merge($inEls, $tmpEls);
}
$nextElName = '';
if($i + 1 < count($elements)) {
$nextE = $elements[$i + 1];
$nextClass = get_class($nextE);
$nextElName = end(explode('\\', $nextClass));
}
if($nextElName == 'TextRun') {
# 对比当前和下一个节点的样式
if(is_object(end($inEls))) {
$currentStyle = end($inEls)->getFontStyle();
} else {
continue;
}
$nextEls = $this->getTextElement($nextE);
if(is_null($nextEls)) {
$nextStyle = new Font();
} else {
$nextStyle = current($nextEls)->getFontStyle();
}
}
}
# 设置参数
$a = $b = $c = $d = $e = null;
@list($a, $b, $c, $d, $e) = $args;
$newEl = $container->$fun($a, $b, $c, $d, $e);
$this->setAttr($elName, $newEl, $E);
#$inEls = [];
if(method_exists($E, 'getElements')
&& $elName != 'TextRun'
) {
$inEls = $E->getElements();
}
if(method_exists($E, 'getRows'))
$inEls = $E->getRows();
if(method_exists($E, 'getCells'))
$inEls = $E->getCells();
if (count($inEls) > 0) {
$this->copyElement($inEls, $newEl);
$inEls = [];
$this->args = null;
}
}
return $pageIndex;
}
/**
* 获取Text节点
*/
private function getTextElement($E) {
$elements = $E->getElements();
$result = [];
foreach($elements as $inE) {
$ns = get_class($inE);
$elName = end(explode('\\', $ns));
if($elName == 'Text') {
$inE->setPhpWord(null);
$result[] = $inE;
} elseif (method_exists($inE, 'getElements')) {
$inResult = $this->getTextElement($inE);
}
if(!is_null($inResult))
$result = array_merge($result, $inResult);
}
return count($result) > 0 ? $result : null;
}
private function setAttr($elName, &$newEl, $E)
{
switch (strtolower($elName)) {
case 'footnote':
$newEl->setReferenceId($E->getReferenceId());
break;
case 'formfield':
$newEl->setName($E->getName());
$newEl->setDefault($E->getDefault());
$newEl->setValue($E->getValue());
$newEl->setEntries($E->getEntries());
break;
case 'object':
$newEl->setImageRelationId($E->getImageRelationId());
$newEl->setObjectId($E->getObjectId());
break;
case 'sdt':
$newEl->setValue($E->getValue());
$newEl->setListItems($E->getListItems());
break;
case 'table':
$newEl->setWidth($E->getWidth());
break;
}
}
}
程序员客栈,汇集各路码农,找到你的靠谱技术小伙伴 http://t.cn/RXz4ONT
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。