如何使用模板处理器添加更多内容而不仅仅是简单的值?
$templateProcessor = new TemplateProcessor(resource_path('prints/form.docx'));
$templateProcessor->setValue('date-now', $this->date(Carbon::now()));
$templateProcessor->setValue('title', $title);
$phpWord = IOFactory::load(resource_path('prints/form.docx'));
$section = $phpWord->addSection();
$section->addTitle($title, 0);
$section->addTextBreak();
// Add whole pages, with tables, images, titles, links
// Combine $templateProcessor replaceXmlBlock with $secion
目前 PhpWord 不提供将
TemplateProcessor
与更复杂的 IOFactory::load
或 sections
组合使用的直接方法。
仅使用 IOFactory 还会破坏已设置的文档样式,如页眉、页脚、标题等。
为了解决文档中格式、图像和其他关系丢失的问题,我通过复制 PhPWord 的各个部分制作了一个扩展模板处理器。
use PhpOffice\PhpWord\Element\AbstractContainer;
use PhpOffice\PhpWord\Element\Image;
use PhpOffice\PhpWord\Element\Link;
use PhpOffice\PhpWord\Exception\Exception;
use PhpOffice\PhpWord\Media;
use PhpOffice\PhpWord\PhpWord;
use PhpOffice\PhpWord\Settings;
use PhpOffice\PhpWord\Shared\XMLWriter;
use PhpOffice\PhpWord\Shared\ZipArchive;
use PhpOffice\PhpWord\TemplateProcessor;
class ExtendedTemplateProcessor extends TemplateProcessor {
/**
* @param PhpWord $phpWord
*
* @throws Exception
*/
public function setupRelationships(PhpWord $phpWord): void {
$originalRid = $this->getNextRelationsIndex($this->getMainPartName());
$rid = $originalRid;
$xmlWriter = new XMLWriter();
$sections = $phpWord->getSections();
foreach ($sections as $section) {
$this->fixRelId($section->getElements(), $rid);
}
$sectionMedia = Media::getElements('section');
if (!empty($sectionMedia)) {
$this->addFilesToPackage($this->zip(), $sectionMedia, $originalRid);
foreach ($sectionMedia as $element) {
$this->writeMediaRel($xmlWriter, $element['rID'] - 1 + $originalRid, $element);
}
}
$mediaXml = $xmlWriter->getData();
// Add link as relationship in XML relationships.
$mainPartName = $this->getMainPartName();
$this->tempDocumentRelations[$mainPartName] = str_replace(
'</Relationships>',
$mediaXml,
$this->tempDocumentRelations[$mainPartName]
) . '</Relationships>';
}
protected function fixRelId(array $elements, &$id): void {
foreach ($elements as $element) {
if (
$element instanceof Link
|| $element instanceof Image
) {
$element->setRelationId($id++ - 6);
}
if ($element instanceof AbstractContainer) {
$this->fixRelId($element->getElements(), $id);
}
}
}
/**
* Write media relationships.
*
* @param int $relId
* @param array $mediaRel
*
* @throws Exception
*/
protected function writeMediaRel(XMLWriter $xmlWriter, $relId, $mediaRel): void {
$typePrefix = 'officeDocument/2006/relationships/';
$typeMapping = ['image' => 'image', 'object' => 'oleObject', 'link' => 'hyperlink'];
$targetMapping = ['image' => 'media/', 'object' => 'embeddings/'];
$mediaType = $mediaRel['type'];
$type = $typeMapping[$mediaType] ?? $mediaType;
$targetPrefix = $targetMapping[$mediaType] ?? '';
$target = $mediaRel['target'];
$targetMode = ($type == 'hyperlink') ? 'External' : '';
$this->writeRel($xmlWriter, $relId, $typePrefix . $type, $targetPrefix . $target, $targetMode);
}
/**
* Write individual rels entry.
*
* Format:
* <Relationship Id="rId..." Type="http://..." Target="....xml" TargetMode="..." />
*
* @param int $relId Relationship ID
* @param string $type Relationship type
* @param string $target Relationship target
* @param string $targetMode Relationship target mode
*
* @throws Exception
*/
protected function writeRel(XMLWriter $xmlWriter, $relId, $type, $target, $targetMode = ''): void {
if ($type != '' && $target != '') {
if (strpos($relId, 'rId') === false) {
$relId = 'rId' . $relId;
}
$xmlWriter->startElement('Relationship');
$xmlWriter->writeAttribute('Id', $relId);
$xmlWriter->writeAttribute('Type', 'http://schemas.openxmlformats.org/' . $type);
$xmlWriter->writeAttribute('Target', $target);
if ($targetMode != '') {
$xmlWriter->writeAttribute('TargetMode', $targetMode);
}
$xmlWriter->endElement();
} else {
throw new Exception('Invalid parameters passed.');
}
}
/**
* Add files to package.
*
* @param mixed $elements
*
* @throws Exception
*/
protected function addFilesToPackage(ZipArchive $zip, array $elements, $originalRid): void {
$types = [];
foreach ($elements as $element) {
$type = $element['type']; // image|object|link
if (!in_array($type, ['image', 'object'])) {
continue;
}
$target = 'word/media/' . $element['target'];
// Retrive GD image content or get local media
if (isset($element['isMemImage']) && $element['isMemImage']) {
$imageContents = $element['imageString'];
$zip->addFromString($target, $imageContents);
} else {
$this->addFileToPackage($zip, $element['source'], $target);
}
if ($type === 'image') {
$types[$element['imageExtension']] = $element['imageType'];
}
}
$types = array_map(function ($value, $key) {
return str_replace(['{ext}', '{type}'], [$key, $value], '<Default Extension="{ext}" ContentType="{type}"/>');
}, $types, array_keys($types));
$this->tempDocumentContentTypes = str_replace('</Types>', join("\n", $types), $this->tempDocumentContentTypes) . '</Types>';
}
/**
* Add file to package.
*
* Get the actual source from an archive image.
*
* @param ZipArchive $zipPackage
* @param string $source
* @param string $target
*
* @throws Exception
*/
protected function addFileToPackage(ZipArchive $zipPackage, string $source, string $target): void {
$isArchive = strpos($source, 'zip://') !== false;
$actualSource = null;
if ($isArchive) {
$source = substr($source, 6);
[$zipFilename, $imageFilename] = explode('#', $source);
$zip = new ZipArchive();
if ($zip->open($zipFilename) !== false) {
if ($zip->locateName($imageFilename)) {
$zip->extractTo(Settings::getTempDir(), $imageFilename);
$actualSource = Settings::getTempDir() . DIRECTORY_SEPARATOR . $imageFilename;
}
}
$zip->close();
} else {
$actualSource = $source;
}
if (null !== $actualSource) {
$zipPackage->addFile($actualSource, $target);
}
}
}
这就是如何使用扩展模板处理器来组合复杂的部分。
$templateProcessor = new ExtendedTemplateProcessor(resource_path('prints/form.docx'));
$templateProcessor->setValue('date-now', $this->date(Carbon::now()));
$templateProcessor->setValue('title', $title);
$phpWord = IOFactory::load(resource_path('prints/form.docx'));
$section = $phpWord->addSection();
$section->addTitle($title, 0);
$section->addTextBreak();
// This function adds more complicated things like addLink, addTable, addImage or even Html::addHtml($section, $value)
$this->formGroup($section, $config->children, $model, $model->toArray());
// Call this from extended template processor to setup and fix document relationships and add images to the document.
$templateProcessor->setupRelationships($phpWord);
// Next lines writes the sections, tables and other conent to the document.
$xmlWriter = new XMLWriter();
(new Container($xmlWriter, $section, false))->write();
$templateProcessor->replaceXmlBlock('content', $xmlWriter->getData());
$templateProcessor->save();
重要的部分是
$templateProcessor->setupRelationships($phpWord);
templateProcessor 正在使用的文档已使用正确的 relId 进行修复,并且媒体已添加到文档中。