PHPWord
本来想着当调包侠呢,结果翻了一遍文档,没有这种操作支持,阿这😂
GPT
不出意外的一顿胡扯,给👨🦳气的要中风啦
思路
word 也就是docx
结尾的文件本质上就是xml
字符串,
两个word文件合并其实就是把两个字符串拼接起来,你真是小天才呢👨🎤
具体步骤
原地址 【能打开的直接抄就完啦】
打不开的也别急,我给你搬运一份奥🤟
首先要拓展一下官方的类,为啥涅?里面的属性咱拿不到
class OpenTemplateProcessor extends \PhpOffice\PhpWord\TemplateProcessor {
public function __construct($instance) {
return parent::__construct($instance);
}
public function __get($key) {
return $this->$key;
}
public function __set($key, $val) {
return $this->$key = $val;
}
}
然后就可以愉快的拼接xml
字符串啦,有一些xml
的固定格式需要注意👍
$mainTemplateProcessor = new \common\helpers\OpenTemplateProcessor($filename);$innerTemplateProcessor = new \common\helpers\OpenTemplateProcessor($filename);
// 拓展类就是为了拿到他的xml
$innerXml = $innerTemplateProcessor->tempDocumentMainPart;
$innerXml = preg_replace('/^[\s\S]*<w:body>(.*)<\/w:body>.*/ ', '$1 ', $innerXml);// remove tag containing header, footer, images
$innerXml = preg_replace('/<w:sectPr>.*<\/w:sectPr>/ ', ' ', $innerXml);// 把取出来的内容放进</w:body> 结束符之前
$mainXml = $mainTemplateProcessor->tempDocumentMainPart;
$mainXml = preg_replace('/<\/w:body>/', '<w:p><w:r><w:br w:type = "page" /><w:lastRenderedPageBreak/></w:r></w:p>' . $innerXml . ' </w:body> ', $mainXml);
$mainTemplateProcessor->tempDocumentMainPart = $mainXml;
$mainTemplateProcessor->saveAs($folder . "1.docx");
关于文件合并后图片显示重复的问题
因为之前拿相同的文件测试的并没有发现😅
word文件转成xml后的内容(以OpenTemplateProcessor这个类为例)
$this->tempDocumentRelations
<Relationships
<Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.png"/>
</Relationships>
$this->tempDocumentMainPart
<w:document
xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
........
xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
xmlns:wpsCustomData="http://www.wps.cn/officeDocument/2013/wpsCustomData" mc:Ignorable="w14 w15 wp14">
<w:body>
....太多了省略下
<w:drawing>
<wp:inline distT="0" distB="0" distL="114300" distR="114300">
<wp:extent cx="5273040" cy="3495675"/>
<wp:effectExtent l="0" t="0" r="3810" b="9525"/>
<wp:docPr id="1" name="图片 1" descr="B1E79294E5A79B%BE-14023560415"/>
<wp:cNvGraphicFramePr>
<a:graphicFrameLocks
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" noChangeAspect="1"/>
</wp:cNvGraphicFramePr>
<a:graphic
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:pic
xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:nvPicPr>
<pic:cNvPr id="1" name="图片 1" descr="B1E79294E5A79B%BE-14023560415"/> // 这个name
<pic:cNvPicPr>
<a:picLocks noChangeAspect="1"/>
</pic:cNvPicPr>
</pic:nvPicPr>
<pic:blipFill>
<a:blip r:embed="rId4"/> // 还有这个r:embed
<a:stretch>
<a:fillRect/>
</a:stretch>
</pic:blipFill>
<pic:spPr>
<a:xfrm>
<a:off x="0" y="0"/>
<a:ext cx="5273040" cy="3495675"/>
</a:xfrm>
<a:prstGeom prst="rect">
<a:avLst/>
</a:prstGeom>
</pic:spPr>
</pic:pic>
</a:graphicData>
</a:graphic>
</wp:inline>
</w:drawing>
根据结构可以看出图片是怎么构成的 name
和 r:embed
其中r:embed
的值就在 Relationships
中,这样就标识了一个图片
看到这大概明白怎么回事了,因为不同word文件中的r:embed
值可能是相同(大概率是),百度了一下word里的图片默认是按1,2这样递增的数
值标识。
按我们上面的操作,只拼接了w:body
里的字符 并忽略了文件2的 Relationships
,所以文件2的图片指向了文件1的Relationships
思路弄明白就简单了,就是把Relationships
的图片也合并过来,然后r:embed
的标识符要改,还有一点比较重要是图片资源也要写入到第一个word中去,光修改标识符是不够的,毕竟字符串合并图片都不存在
简单的示例:
$files = File::files(public_path("storage/we")); // 获取目录下的所有文件
$mainXml = "";
$mainTemplateProcessor = null;
foreach ($files as $key=> $file) {
if ($key != 0) {
$innerTemplateProcessor = new Template($file->getRealPath());
$innerXml = $innerTemplateProcessor->tempDocumentMainPart;
// 正则出所有的图片
preg_match_all('/<Relationship\s+Id="([^"]+)"\s+Type="http:\/\/schemas\.openxmlformats\.org\/officeDocument\/2006\/relationships\/image"\s+Target="([^"]+)"\/>/',$innerTemplateProcessor->tempDocumentRelations['word/document.xml'],$res);
// 把图片索引更改下,追加并写入
foreach ($res[0] as $k => $v) {
$rid = "rId{$key}{$k}";
$innerXml = str_replace($res[1][$k],$rid,$innerXml);
$extension = pathinfo($res[2][$k], PATHINFO_EXTENSION);
$mediaName = "media/image{$rid}.{$extension}";
$relations = str_replace(
[$res[1][$k],$res[2][$k]],
[$rid,$mediaName],
$v);
if (!$mainTemplateProcessor->zipClass->addFromString('word/'.$mediaName,$innerTemplateProcessor->zipClass->getFromName('word/'.$res[2][$k]))){
throw new \Exception("add media fail");
}
$tempData = $mainTemplateProcessor->tempDocumentRelations;
$tempData['word/document.xml'] = str_replace('</Relationships>',$relations,$mainTemplateProcessor->tempDocumentRelations['word/document.xml']).'</Relationships>';
$mainTemplateProcessor->tempDocumentRelations = $tempData;
} $innerXml = preg_replace('/^[\s\S]*<w:body>(.*)<\/w:body>.*/ ', '$1 ', $innerXml); $innerXml = preg_replace('/<w:sectPr>.*<\/w:sectPr>/ ', ' ', $innerXml); $mainXml= preg_replace('/<\/w:body>/', '<w:p><w:r><w:br w:type = "page" /><w:lastRenderedPageBreak/></w:r></w:p>' . $innerXml . ' </w:body> ', $mainXml);
} else {
$mainTemplateProcessor = new Template($file->getRealPath());
$mainXml = $mainTemplateProcessor->tempDocumentMainPart;
} }
$mainTemplateProcessor->tempDocumentMainPart = $mainXml;
$mainTemplateProcessor->saveAs(public_path("storage/3.docx"));
发表评论 取消回复