Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IOFactory:load not preserd formate template #1594

Open
creatorness opened this issue Mar 11, 2019 · 1 comment
Open

IOFactory:load not preserd formate template #1594

creatorness opened this issue Mar 11, 2019 · 1 comment

Comments

@creatorness
Copy link

I can not use TemplateProcessor, because i want add Section to the Dokument.
Why is this library not working like PHPExcel, because it is too new ?

Originally posted by @creatorness in #882 (comment)

@davisriska
Copy link

I found myself having similar issues when trying to do a combination TemplateProcessor and IOFactory::load.

To get around the issue of losing formatting and images/relationships I made an extended template processor by copying various parts of 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:https://..." 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:https://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:https://') !== 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);
        }
    }

}

And this is how I use the extended template processor.

$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();

        if ($prependTitle) {
            $section->addTitle($title, 0);
            $section->addTextBreak();
        }

        $this->formGroup($section, $config->children, $model, $model->toArray());

        $templateProcessor->setupRelationships($phpWord);
        $xmlWriter = new XMLWriter();
        (new Container($xmlWriter, $section, false))->write();
        $templateProcessor->replaceXmlBlock('content', $xmlWriter->getData());

        return $templateProcessor->save();

The important part is $templateProcessor->setupRelationships($phpWord); were I fix relIds and add media to the document.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants