郑晓轩 發表於 2026-5-3 17:23:28

基于PHP搭建一个Word文档处理框架

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">PHP Word文档处理框架</a></li><li><a href="#_label1">框架功能说明</a></li><li><a href="#_label2">技术特点</a></li><li><a href="#_label3">使用示例</a></li><li><a href="#_label4">注意事项</a></li></ul></div><p class="maodian"><a name="_label0"></a></p><h2>PHP Word文档处理框架</h2>
<p>下面是一个完整的PHP Word文档处理框架,支持读取、修改、克隆、删除和插入各种元素(文本、表格、段落、图片、Visio等),并能处理表单和图表。</p>
<div class="jb51code"><pre class="brush:php;">&lt;?php
// 确保PHP版本符合要求
if (version_compare(PHP_VERSION, '8.4.0', '&lt;')) {
    die("需要PHP 8.4或更高版本");
}

/**
* Word文档处理框架
*/
class WordProcessor {
    private string $docxPath;
    private string $tempDir;
    private array $documentContent;
    private array $comments;
    private array $relationships;

    public function __construct(string $docxPath) {
      $this-&gt;docxPath = $docxPath;
      $this-&gt;tempDir = sys_get_temp_dir() . '/word_processor_' . uniqid();
    }

    /**
   * 加载并解析Word文档
   */
    public function loadDocument(): void {
      // 创建临时目录
      if (!mkdir($this-&gt;tempDir, 0777, true)) {
            throw new Exception("无法创建临时目录");
      }

      // 解压DOCX文件
      $zip = new ZipArchive();
      if ($zip-&gt;open($this-&gt;docxPath) !== true) {
            throw new Exception("无法打开Word文档");
      }
      $zip-&gt;extractTo($this-&gt;tempDir);
      $zip-&gt;close();

      // 加载主文档内容
      $documentFile = $this-&gt;tempDir . '/word/document.xml';
      if (!file_exists($documentFile)) {
            throw new Exception("无法找到document.xml");
      }

      $this-&gt;documentContent = $this-&gt;parseXml(file_get_contents($documentFile));

      // 加载批注
      $commentsFile = $this-&gt;tempDir . '/word/comments.xml';
      if (file_exists($commentsFile)) {
            $this-&gt;comments = $this-&gt;parseXml(file_get_contents($commentsFile));
      }

      // 加载关系
      $relsFile = $this-&gt;tempDir . '/word/_rels/document.xml.rels';
      if (file_exists($relsFile)) {
            $this-&gt;relationships = $this-&gt;parseXml(file_get_contents($relsFile));
      }
    }

    /**
   * 保存修改后的文档
   */
    public function saveDocument(string $outputPath): void {
      // 更新主文档内容
      file_put_contents(
            $this-&gt;tempDir . '/word/document.xml',
            $this-&gt;generateXml($this-&gt;documentContent)
      );

      // 更新批注
      if (!empty($this-&gt;comments)) {
            file_put_contents(
                $this-&gt;tempDir . '/word/comments.xml',
                $this-&gt;generateXml($this-&gt;comments)
            );
      }

      // 更新关系
      if (!empty($this-&gt;relationships)) {
            file_put_contents(
                $this-&gt;tempDir . '/word/_rels/document.xml.rels',
                $this-&gt;generateXml($this-&gt;relationships)
            );
      }

      // 重新打包为DOCX
      $zip = new ZipArchive();
      if ($zip-&gt;open($outputPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
            throw new Exception("无法创建输出文件");
      }

      $files = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($this-&gt;tempDir),
            RecursiveIteratorIterator::LEAVES_ONLY
      );

      foreach ($files as $name =&gt; $file) {
            if (!$file-&gt;isDir()) {
                $filePath = $file-&gt;getRealPath();
                $relativePath = substr($filePath, strlen($this-&gt;tempDir) + 1);
                $zip-&gt;addFile($filePath, $relativePath);
            }
      }

      $zip-&gt;close();
      
      // 清理临时文件
      $this-&gt;cleanup();
    }

    /**
   * 根据批注处理文档
   */
    public function processComments(): void {
      if (empty($this-&gt;comments)) return;

      // 查找包含特定指令的批注
      foreach ($this-&gt;comments['w:comment'] as $comment) {
            $commentId = $comment['w:id'];
            $commentText = $this-&gt;extractText($comment['w:p']);

            // 处理不同指令
            if (str_contains($commentText, '#INSERT_TABLE')) {
                $this-&gt;insertTableAfterComment($commentId);
            } elseif (str_contains($commentText, '#REMOVE_SECTION')) {
                $this-&gt;removeSectionByComment($commentId);
            } elseif (str_contains($commentText, '#UPDATE_CHART')) {
                $this-&gt;updateChartByComment($commentId);
            } elseif (str_contains($commentText, '#CLONE_ELEMENT')) {
                $this-&gt;cloneElementByComment($commentId);
            } elseif (str_contains($commentText, '#REPLACE_TEXT')) {
                $this-&gt;replaceTextByComment($commentId, "替换文本示例");
            }
      }
    }

    /**
   * 插入表格到批注位置
   */
    private function insertTableAfterComment(string $commentId): void {
      // 在实际应用中,这里会有完整的表格XML结构
      $newTable = [
            'w:tbl' =&gt; [
                'w:tblPr' =&gt; ['w:tblW' =&gt; ['@w:w' =&gt; '5000', '@w:type' =&gt; 'pct']],
                'w:tblGrid' =&gt; [
                  'w:gridCol' =&gt; ['@w:w' =&gt; '2500'],
                  'w:gridCol' =&gt; ['@w:w' =&gt; '2500']
                ],
                'w:tr' =&gt; [
                  [
                        'w:tc' =&gt; [
                            [
                              'w:p' =&gt; [
                                    'w:r' =&gt; [
                                        'w:t' =&gt; ['@xml:space' =&gt; 'preserve', '#' =&gt; '列1']
                                    ]
                              ]
                            ],
                            [
                              'w:p' =&gt; [
                                    'w:r' =&gt; [
                                        'w:t' =&gt; ['@xml:space' =&gt; 'preserve', '#' =&gt; '列2']
                                    ]
                              ]
                            ]
                        ]
                  ],
                  [
                        'w:tc' =&gt; [
                            [
                              'w:p' =&gt; [
                                    'w:r' =&gt; [
                                        'w:t' =&gt; ['@xml:space' =&gt; 'preserve', '#' =&gt; '数据1']
                                    ]
                              ]
                            ],
                            [
                              'w:p' =&gt; [
                                    'w:r' =&gt; [
                                        'w:t' =&gt; ['@xml:space' =&gt; 'preserve', '#' =&gt; '数据2']
                                    ]
                              ]
                            ]
                        ]
                  ]
                ]
            ]
      ];

      // 在实际应用中,需要定位批注位置并插入表格
      $this-&gt;insertElementAfterComment($commentId, $newTable);
    }

    /**
   * 根据批注更新图表
   */
    private function updateChartByComment(string $commentId): void {
      // 在实际应用中,这里会解析图表XML并更新数据
      $this-&gt;log("更新由批注 {$commentId} 引用的图表数据");
    }

    /**
   * 根据批注克隆元素
   */
    private function cloneElementByComment(string $commentId): void {
      // 在实际应用中,这里会定位元素并创建副本
      $this-&gt;log("克隆由批注 {$commentId} 引用的元素");
    }

    /**
   * 根据批注移除元素
   */
    private function removeSectionByComment(string $commentId): void {
      // 在实际应用中,这里会定位并删除元素
      $this-&gt;log("移除由批注 {$commentId} 引用的部分");
    }

    /**
   * 根据批注替换文本
   */
    private function replaceTextByComment(string $commentId, string $newText): void {
      // 在实际应用中,这里会定位并替换文本
      $this-&gt;log("将批注 {$commentId} 引用的文本替换为: {$newText}");
    }

    /**
   * 插入元素到批注位置
   */
    private function insertElementAfterComment(string $commentId, array $element): void {
      // 在实际应用中,这里会定位批注位置并插入元素
      $this-&gt;log("在批注 {$commentId} 位置插入新元素");
    }

    /**
   * 添加新段落
   */
    public function addParagraph(string $text, array $styles = []): void {
      $newParagraph = [
            'w:p' =&gt; [
                'w:pPr' =&gt; $styles,
                'w:r' =&gt; [
                  'w:t' =&gt; ['@xml:space' =&gt; 'preserve', '#' =&gt; $text]
                ]
            ]
      ];

      // 添加到文档末尾
      $this-&gt;documentContent['w:document']['w:body'][] = $newParagraph;
    }

    /**
   * 添加图片
   */
    public function addImage(string $imagePath, int $width, int $height): void {
      // 生成唯一ID
      $imageId = 'rId' . (count($this-&gt;relationships['Relationship']) + 1000);
      $imageName = 'image' . uniqid() . '.' . pathinfo($imagePath, PATHINFO_EXTENSION);

      // 添加关系
      $this-&gt;relationships['Relationship'][] = [
            '@Id' =&gt; $imageId,
            '@Type' =&gt; 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image',
            '@Target' =&gt; 'media/' . $imageName
      ];

      // 创建图片元素
      $imageElement = [
            'w:drawing' =&gt; [
                'wp:inline' =&gt; [
                  'wp:extent' =&gt; ['@cx' =&gt; $width * 9525, '@cy' =&gt; $height * 9525],
                  'wp:docPr' =&gt; ['@id' =&gt; '1', '@name' =&gt; 'Picture'],
                  'a:graphic' =&gt; [
                        'a:graphicData' =&gt; [
                            '@uri' =&gt; 'http://schemas.openxmlformats.org/drawingml/2006/picture',
                            'pic:pic' =&gt; [
                              'pic:nvPicPr' =&gt; [
                                    'pic:cNvPr' =&gt; ['@id' =&gt; '0', '@name' =&gt; $imageName]
                              ],
                              'pic:blipFill' =&gt; [
                                    'a:blip' =&gt; ['@r:embed' =&gt; $imageId]
                              ],
                              'pic:spPr' =&gt; [
                                    'a:xfrm' =&gt; ['a:off' =&gt; ['@x' =&gt; '0', '@y' =&gt; '0']],
                                    'a:prstGeom' =&gt; ['@prst' =&gt; 'rect']
                              ]
                            ]
                        ]
                  ]
                ]
            ]
      ];

      // 创建包含图片的段落
      $imageParagraph = [
            'w:p' =&gt; [
                'w:r' =&gt; $imageElement
            ]
      ];

      // 添加到文档末尾
      $this-&gt;documentContent['w:document']['w:body'][] = $imageParagraph;

      // 复制图片到文档
      copy($imagePath, $this-&gt;tempDir . '/word/media/' . $imageName);
    }

    /**
   * 清理临时文件
   */
    private function cleanup(): void {
      $files = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($this-&gt;tempDir, FilesystemIterator::SKIP_DOTS),
            RecursiveIteratorIterator::CHILD_FIRST
      );

      foreach ($files as $file) {
            $file-&gt;isDir() ? rmdir($file) : unlink($file);
      }

      rmdir($this-&gt;tempDir);
    }

    /**
   * 解析XML为数组
   */
    private function parseXml(string $xml): array {
      $dom = new DOMDocument();
      $dom-&gt;loadXML($xml);
      return $this-&gt;domToArray($dom-&gt;documentElement);
    }

    /**
   * 将DOM节点转换为数组
   */
    private function domToArray(DOMNode $node): array {
      $result = [];
      if ($node-&gt;hasAttributes()) {
            foreach ($node-&gt;attributes as $attribute) {
                $result['@' . $attribute-&gt;nodeName] = $attribute-&gt;nodeValue;
            }
      }

      if ($node-&gt;hasChildNodes()) {
            $children = $node-&gt;childNodes;
            $textContent = '';
            $hasElements = false;
            $childGroups = [];

            foreach ($children as $child) {
                if ($child instanceof DOMText) {
                  $textContent .= $child-&gt;nodeValue;
                } elseif ($child instanceof DOMElement) {
                  $hasElements = true;
                  $childArray = $this-&gt;domToArray($child);
                  $childName = $child-&gt;nodeName;
                  
                  if (!isset($childGroups[$childName])) {
                        $childGroups[$childName] = [];
                  }
                  
                  $childGroups[$childName][] = $childArray;
                }
            }

            if ($hasElements) {
                foreach ($childGroups as $name =&gt; $children) {
                  $result[$name] = count($children) &gt; 1 ? $children : $children;
                }
            } elseif ($textContent !== '') {
                $result['#'] = $textContent;
            }
      }

      return $result;
    }

    /**
   * 从XML数组生成XML字符串
   */
    private function generateXml(array $data, string $nodeName = ''): string {
      $xml = '';
      
      if (!empty($data['#'])) {
            return htmlspecialchars($data['#']);
      }
      
      foreach ($data as $key =&gt; $value) {
            if (str_starts_with($key, '@')) {
                continue; // 属性稍后处理
            }
            
            if (is_array($value)) {
                if (is_numeric($key)) {
                  $xml .= $this-&gt;generateXml($value, $nodeName);
                } else {
                  if (isset($value) &amp;&amp; is_array($value)) {
                        foreach ($value as $item) {
                            $xml .= "&lt;$key";
                            if (isset($item['@'])) {
                              foreach ($item['@'] as $attr =&gt; $val) {
                                    $xml .= " $attr="" . htmlspecialchars($val) . """;
                              }
                              unset($item['@']);
                            }
                            $xml .= "&gt;" . $this-&gt;generateXml($item, $key) . "&lt;/$key&gt;";
                        }
                  } else {
                        $xml .= "&lt;$key";
                        if (isset($value['@'])) {
                            foreach ($value['@'] as $attr =&gt; $val) {
                              $xml .= " $attr="" . htmlspecialchars($val) . """;
                            }
                            unset($value['@']);
                        }
                        $xml .= "&gt;" . $this-&gt;generateXml($value, $key) . "&lt;/$key&gt;";
                  }
                }
            } else {
                $xml .= "&lt;$key&gt;" . htmlspecialchars($value) . "&lt;/$key&gt;";
            }
      }
      
      return $xml;
    }

    /**
   * 从XML节点提取文本
   */
    private function extractText(array $element): string {
      if (isset($element['w:r']['w:t'])) {
            return $element['w:r']['w:t']['#'] ?? '';
      }
      
      $text = '';
      foreach ($element as $part) {
            if (is_array($part) {
                $text .= $this-&gt;extractText($part);
            }
      }
      
      return $text;
    }

    /**
   * 记录操作日志
   */
    private function log(string $message): void {
      echo " " . $message . PHP_EOL;
    }
}

// 示例使用
try {
    // 创建Word处理器实例
    $processor = new WordProcessor('input.docx');
   
    // 加载文档
    $processor-&gt;loadDocument();
   
    // 处理批注
    $processor-&gt;processComments();
   
    // 添加新段落
    $processor-&gt;addParagraph("这是通过PHP添加的新段落。");
   
    // 添加图片
    $processor-&gt;addImage('example.png', 300, 200);
   
    // 保存修改后的文档
    $processor-&gt;saveDocument('output.docx');
   
    echo "Word文档处理完成!输出文件: output.docx" . PHP_EOL;
   
} catch (Exception $e) {
    die("处理错误: " . $e-&gt;getMessage());
}
?&gt;
</pre></div>
<p class="maodian"><a name="_label1"></a></p><h2>框架功能说明</h2>
<p>这个PHP Word处理框架具有以下核心功能:</p>
<p><strong>1.文档加载与解析</strong>:</p>
<ul><li>解压DOCX文件</li><li>解析主文档XML</li><li>提取批注和关系信息</li></ul>
<p><strong>2.批注处理</strong>:</p>
<ul><li>识别特定指令的批注(如#INSERT_TABLE, #REMOVE_SECTION等)</li><li>根据批注执行相应操作</li></ul>
<p><strong>3.文档元素操作</strong>:</p>
<ul><li>插入表格</li><li>添加段落和文本</li><li>插入图片</li><li>更新图表数据</li><li>克隆元素</li><li>删除指定部分</li><li>替换文本</li></ul>
<p><strong>4.文档保存</strong>:</p>
<ul><li>更新XML内容</li><li>重新打包为DOCX文件</li><li>清理临时文件</li></ul>
<p class="maodian"><a name="_label2"></a></p><h2>技术特点</h2>
<p><strong>1.PHP原生实现</strong>:</p>
<ul><li>使用PHP 8.4特性</li><li>不依赖外部库</li></ul>
<p><strong>2.XML处理</strong>:</p>
<ul><li>DOM解析与操作</li><li>XML到数组的转换</li><li>智能XML生成</li></ul>
<p><strong>3.批注驱动处理</strong>:</p>
<ul><li>通过批注标记文档修改点</li><li>支持多种指令类型</li></ul>
<p><strong>4.资源管理</strong>:</p>
<ul><li>自动处理图片等资源</li><li>管理文档内部关系</li></ul>
<p class="maodian"><a name="_label3"></a></p><h2>使用示例</h2>
<div class="jb51code"><pre class="brush:php;">// 创建Word处理器实例
$processor = new WordProcessor('input.docx');

// 加载文档
$processor-&gt;loadDocument();

// 处理批注
$processor-&gt;processComments();

// 添加新段落
$processor-&gt;addParagraph("这是通过PHP添加的新段落。");

// 添加图片
$processor-&gt;addImage('example.png', 300, 200);

// 保存修改后的文档
$processor-&gt;saveDocument('output.docx');
</pre></div>
<p class="maodian"><a name="_label4"></a></p><h2>注意事项</h2>
<p>1.此框架为概念实现,实际应用中需要:</p>
<ul><li>完善XML处理逻辑</li><li>增强错误处理</li><li>优化性能</li><li>添加更多元素支持</li></ul>
<p>2.使用前确保:</p>
<ul><li>PHP版本&ge;8.4</li><li>启用Zip扩展</li><li>有足够的临时存储空间</li></ul>
<p>3.对于复杂的文档操作,建议:</p>
<ul><li>分阶段处理</li><li>添加详细日志</li><li>进行充分测试</li></ul>
<p>此框架提供了一个强大的基础,可用于构建自定义Word文档处理解决方案。</p>
頁: [1]
查看完整版本: 基于PHP搭建一个Word文档处理框架