Skip to content

Instantly share code, notes, and snippets.

@jeffsrepoaccount
Last active March 15, 2022 06:28
Show Gist options
  • Save jeffsrepoaccount/1c1ded62c07b08d73c72de38b6f4ee42 to your computer and use it in GitHub Desktop.
Save jeffsrepoaccount/1c1ded62c07b08d73c72de38b6f4ee42 to your computer and use it in GitHub Desktop.
PHPOffice/PHPWord - Injecting AltChunks in word document using placeholders in template file
<?php namespace My\Namespace;
use DOMDocument;
use Parsedown;
use PhpOffice\PhpWord\TemplateProcessor as PhpWordTemplateProcessor;
class TemplateProcessor extends PhpWordTemplateProcessor
{
/**
* Injects a markdown snippet as HTML into the word document.
*
* @param string $search Template placeholder to replace
* @param string $markdown
* @return string
*/
public function replaceMarkdown($search, $markdown)
{
$parsedown = new Parsedown;
$contents = '<html><head /><body>' .
$parsedown->text($markdown)
. '</body></html>'
;
$this->registerAltChunk(
$search,
'word/' . $search . '.dat',
$contents
);
}
/**
* Registers an altChunk in the document.
*
* @param string $search Template placeholder
* @param string $target Base filename target added to archive
* @param string $contents HTML content to add to document
* @param string $type OOXML AltChunk Content Type
*/
private function registerAltChunk($search, $target, $contents, $type = 'text/html')
{
$id = 'alt_' . $search;
$this->zipClass->addFromString($target, $contents);
parent::setValue($search, '<w:altChunk r:id="' . $id . '" />');
$this
->registerContentTypeOverride($target, $type)
->registerRelationship(
$id,
$target,
'http://schemas.openxmlformats.org/officeDocument/2006/relationships/aFChunk'
)
;
// Close/Reopen ziparchive so subsequent reads will see these writes
// Probably a better way to do this.
$this->zipClass->close();
$this->zipClass->open($this->tempDocumentFilename);
}
/**
* Registers a content type override in the document in [Content_Types].xml
*
* @param string $target
* @param string $type
*/
private function registerContentTypeOverride($target, $type)
{
$contentTypes = new DOMDocument;
$contentTypes->loadXML( $this->zipClass->getFromName('[Content_Types].xml') );
$node = $contentTypes->createElement('Override');
$node->setAttribute('PartName', '/' . $target);
$node->setAttribute('ContentType', $type);
$root = $contentTypes->getElementsByTagName('Types')->item(0);
$root->appendChild($node);
$this->zipClass->addFromString('[Content_Types].xml', $contentTypes->saveXML());
return $this;
}
/**
* Registers a relationship to the main document part.
*
* @param string $id The main document ID attribute
* @param string $target Filename target inside of the archive
* @param string $type Type to register the relationship as
*/
private function registerRelationship($id, $target, $type)
{
$relationships = new DOMDocument;
$relationships->loadXML($this->zipClass->getFromName('word/_rels/document.xml.rels'));
$node = $relationships->createElement('Relationship');
$node->setAttribute('Id', $id);
$node->setAttribute('Type', $type);
$node->setAttribute('Target', '/' . $target);
$root = $relationships->getElementsByTagName('Relationships')->item(0);
$root->appendChild($node);
$this->zipClass->addFromString('word/_rels/document.xml.rels', $relationships->saveXML());
return $this;
}
}
@jeffsrepoaccount
Copy link
Author

jeffsrepoaccount commented Jun 8, 2018

Usage:

use My\Namespace\TemplateProcessor;

$templatePath = '...';
$outputPath = '...';
$search = 'replaceMe';
$markdown = "### Bulleted lists are good for:\n\n* Listing things\n* Organizing things that do not have an order\n* Keeping things in lists";

$template = new TemplateProcessor($templatePath);
$template->replaceMarkdown($search, $markdown);
$template->saveAs($outputPath);

Note that the <w:AltChunk /> element follows the same placement rules as <w:p> elements. Just placing the placeholder string ${replaceMe} in the document may not be sufficient, you may need to modify the main document part (document.xml) inside of the DOCX archive of your template so that you guarantee it is not inside of a text run or similar elemnts (e.g. <w:r> or <w:Pr>). It most likely needs to be placed as a sibling of the nearest paragraph element.

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