如何使用 PhpWord 中的模板处理器处理更复杂的部分,如表格、图像、链接?

问题描述 投票:0回答:1

如何使用模板处理器添加更多内容而不仅仅是简单的值?

$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
php ms-word phpword
1个回答
0
投票

目前 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 进行修复,并且媒体已添加到文档中。

© www.soinside.com 2019 - 2024. All rights reserved.