Laravel中使用PHPWord替换DOCX模板HTML占位符时生成损坏文件的解决求助
Laravel中使用PHPWord替换DOCX模板HTML占位符时生成损坏文件的解决求助
大家好,我目前在Laravel项目里使用PHPWord包处理DOCX模板替换,需求是把模板里的${html}占位符替换成带格式的HTML内容(比如<p>Hello,<br><br><strong>Welcome</strong></p>)。但现在遇到的问题是,PHPWord会把HTML标签直接作为纯文本插入,没法渲染出对应的格式(比如粗体、换行效果)。
我翻了PHPWord的官方文档,没找到处理HTML解析替换的清晰方案,后来参考了一个实现思路,但按照这个思路写代码后,生成的DOCX文件直接损坏了,根本打不开。下面是我写的DocxKeyReplacer服务类代码,麻烦各位帮忙看看哪里出了问题,或者有没有更可靠的实现方式?
<?php namespace App\Services; use App\Exceptions\DocxKeyReplacerException; use App\Traits\Makeable; use PhpOffice\PhpWord\Exception\CopyFileException; use PhpOffice\PhpWord\Exception\CreateTemporaryFileException; use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Shared\Html; use PhpOffice\PhpWord\Shared\XMLWriter; use PhpOffice\PhpWord\TemplateProcessor; use PhpOffice\PhpWord\Writer\Word2007\Element\Container; class DocxKeyReplacer { use Makeable; /** * @throws DocxKeyReplacerException */ public function __construct(private $inputFile, private $outputFile, private $properties = []) { if (pathinfo(parse_url($this->inputFile, PHP_URL_PATH), PATHINFO_EXTENSION) !== 'docx') { throw DocxKeyReplacerException::formatIsInvalid(); } } /** * Executes the process of filling a document template with data and images. * * @return string Path to the output file. * * @throws CopyFileException * @throws CreateTemporaryFileException */ public function execute(): string { $templateProcessor = $this->initializeTemplateProcessor($this->inputFile); foreach ($this->properties as $property => $data) { $this->processTemplateProperty($processor, $property, $data); } return $this->finalizeTemplateProcessing($templateProcessor, $this->outputFile); } /** * Initialize the template processor with the input file. * * @throws CopyFileException * @throws CreateTemporaryFileException */ protected function initializeTemplateProcessor(string $inputFile): TemplateProcessor { return new TemplateProcessor($inputFile); } /** * Process a single property and set it in the template processor. */ protected function processTemplateProperty(TemplateProcessor $processor, string $property, array $data): void { $fieldType = data_get($data, 'field_type'); $value = data_get($data, 'value'); if ($this->isSignatureField($property, $value)) { $processor->setImageValue($property, $value); } elseif ($fieldType === 'html') { if (empty($value)) { $processor->setValue($property, $value); return; } $markup = $this->sanitizeHtml($value); $phpWord = new \PhpOffice\PhpWord\PhpWord; $section = $phpWord->addSection(); Html::addHtml($section, $markup); $xmlWriter = new XMLWriter; $containerWriter = new Container($xmlWriter, $section, false); $containerWriter->write(); $processor->replaceXmlBlock($property, $xmlWriter->getData()); } elseif ($this->isFileFieldWithImage($fieldType)) { foreach ($value as $key => $fileObject) { $processor->setValue($property, '${'.$property.$key.'}'.'${'.$property.'}'); $path = $this->extractPath($fileObject); $imageInfo = $path ? @getimagesize($path) : null; // TemplateProcessor supports only following mime type if ($imageInfo && in_array($imageInfo['mime'], ['image/jpeg', 'image/png', 'image/bmp', 'image/gif'])) { $processor->setImageValue($property.$key, $fileObject); } else { Settings::setOutputEscapingEnabled(true); $processor->setValue($property.$key, $path); } } $processor->setValue($property, ''); } else { Settings::setOutputEscapingEnabled(false); $value = htmlspecialchars($value); $value = preg_replace('~\R~u', '</w:t><w:br/></w:t>', $value); $processor->setValue($property, $value); } } protected function sanitizeHtml(string $html): string { $html = preg_replace('#<br(?![^>]*\/)>#i', '<br />', $html); $html = preg_replace('#<hr(?![^>]*\/)>#i', '<hr />', $html); $html = preg_replace('#<img([^>]*)(?<!/)>#i', '<img$1 />', $html); return $html; } /** * Checks if the given field is a signature field. */ protected function isSignatureField(string $property, mixed $value): bool { return in_array($property, ['signature', 'second_signature']) && $value; } /** * Checks if the field type is 'file' and the value contains an image path. */ protected function isFileFieldWithImage(string $fieldType): bool { return $fieldType === 'file'; } /** * Extracts image path or direct value based on whether the value is an array. */ protected function extractPath(mixed $value): mixed { return is_array($value) ? data_get($value, 'path') : $value; } /** * Save the processed template to the output file and return its path. */ protected function finalizeTemplateProcessing(TemplateProcessor $processor, string $outputFile): string { $processor->saveAs($outputFile); return $outputFile; } }
我主要的疑问点在处理HTML字段的这段逻辑:通过创建临时的PhpWord实例和Section,用Html类转换HTML内容,再生成XML来替换模板块,但这么做为什么会导致文件损坏呢?有没有正确的方式可以实现HTML格式的模板替换?
内容来源于stack exchange




