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>

根据结构可以看出图片是怎么构成的 namer: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"));

点赞(1) 打赏

评论列表 共有 0 条评论

暂无评论

微信小程序

微信扫一扫体验

立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部