From 763de347df1aad981e98f041f46d71079955d37c Mon Sep 17 00:00:00 2001 From: Dave Gudgeon Date: Tue, 1 Apr 2014 13:25:05 +0100 Subject: [PATCH 1/3] Add support for document headers and footers to Template class. --- src/PhpWord/Template.php | 113 ++++++++++++++++-- tests/PhpWord/Tests/TemplateTest.php | 20 ++++ .../Tests/_files/templates/header-footer.docx | Bin 0 -> 4495 bytes 3 files changed, 125 insertions(+), 8 deletions(-) create mode 100644 tests/PhpWord/Tests/_files/templates/header-footer.docx diff --git a/src/PhpWord/Template.php b/src/PhpWord/Template.php index f54d729e..e38e2a5a 100644 --- a/src/PhpWord/Template.php +++ b/src/PhpWord/Template.php @@ -32,6 +32,13 @@ class Template */ private $_tempFileName; + /** + * Document header XML + * + * @var string[] + */ + private $_headerXMLs = array(); + /** * Document XML * @@ -39,6 +46,12 @@ class Template */ private $_documentXML; + /** + * Document footer XML + * + * @var string[] + */ + private $_footerXMLs = array(); /** * Create a new Template Object @@ -62,9 +75,41 @@ class Template $this->_objZip = new $zipClass(); $this->_objZip->open($this->_tempFileName); + // Find and load up to three headers and footers + for ($i = 1; $i <= 3; $i++) { + $headerName = $this->getHeaderName($i); + $footerName = $this->getFooterName($i); + if ($this->_objZip->locateName($headerName) !== false) { + $this->_headerXMLs[$i] = $this->_objZip->getFromName($headerName); + } + if ($this->_objZip->locateName($footerName) !== false) { + $this->_footerXMLs[$i] = $this->_objZip->getFromName($footerName); + } + } + $this->_documentXML = $this->_objZip->getFromName('word/document.xml'); } + /** + * Get the name of the footer file for $index + * @param integer $index + * @return string + */ + private function getFooterName($index) + { + return sprintf('word/footer%d.xml', $index); + } + + /** + * Get the name of the header file for $index + * @param integer $index + * @return string + */ + private function getHeaderName($index) + { + return sprintf('word/header%d.xml', $index); + } + /** * Applies XSL style sheet to template's parts * @@ -97,20 +142,22 @@ class Template } /** - * Set a Template value + * Find and replace placeholders in the given XML section. * - * @param mixed $search + * @param string $documentPartXML + * @param string $search * @param mixed $replace * @param integer $limit + * @return string */ - public function setValue($search, $replace, $limit = -1) + protected function setValueForPart($documentPartXML, $search, $replace, $limit) { $pattern = '|\$\{([^\}]+)\}|U'; - preg_match_all($pattern, $this->_documentXML, $matches); + preg_match_all($pattern, $documentPartXML, $matches); foreach ($matches[0] as $value) { $valueCleaned = preg_replace('/<[^>]+>/', '', $value); $valueCleaned = preg_replace('/<\/[^>]+>/', '', $valueCleaned); - $this->_documentXML = str_replace($value, $valueCleaned, $this->_documentXML); + $documentPartXML = str_replace($value, $valueCleaned, $documentPartXML); } if (substr($search, 0, 2) !== '${' && substr($search, -1) !== '}') { @@ -130,16 +177,58 @@ class Template $regExpDelim = '/'; $escapedSearch = preg_quote($search, $regExpDelim); - $this->_documentXML = preg_replace("{$regExpDelim}{$escapedSearch}{$regExpDelim}u", $replace, $this->_documentXML, $limit); + return preg_replace("{$regExpDelim}{$escapedSearch}{$regExpDelim}u", $replace, $documentPartXML, $limit); + } + + /** + * Set a Template value + * + * @param mixed $search + * @param mixed $replace + * @param integer $limit + */ + public function setValue($search, $replace, $limit = -1) + { + foreach ($this->_headerXMLs as $index => $headerXML) { + $this->_headerXMLs[$index] = $this->setValueForPart($this->_headerXMLs[$index], $search, $replace, $limit); + } + + $this->_documentXML = $this->setValueForPart($this->_documentXML, $search, $replace, $limit); + + foreach ($this->_footerXMLs as $index => $headerXML) { + $this->_footerXMLs[$index] = $this->setValueForPart($this->_footerXMLs[$index], $search, $replace, $limit); + } + } + + /** + * Find all variables in $documentPartXML + * @param string $documentPartXML + * @return string[] + */ + protected function getVariablesForPart($documentPartXML) + { + preg_match_all('/\$\{(.*?)}/i', $documentPartXML, $matches); + + return $matches[1]; } /** * Returns array of all variables in template + * @return string[] */ public function getVariables() { - preg_match_all('/\$\{(.*?)}/i', $this->_documentXML, $matches); - return $matches[1]; + $variables = $this->getVariablesForPart($this->_documentXML); + + foreach ($this->_headerXMLs as $headerXML) { + $variables = array_merge($variables, $this->getVariablesForPart($headerXML)); + } + + foreach ($this->_footerXMLs as $footerXML) { + $variables = array_merge($variables, $this->getVariablesForPart($footerXML)); + } + + return array_unique($variables); } /** @@ -251,8 +340,16 @@ class Template */ public function save() { + foreach ($this->_headerXMLs as $index => $headerXML) { + $this->_objZip->addFromString($this->getHeaderName($index), $this->_headerXMLs[$index]); + } + $this->_objZip->addFromString('word/document.xml', $this->_documentXML); + foreach ($this->_footerXMLs as $index => $headerXML) { + $this->_objZip->addFromString($this->getFooterName($index), $this->_footerXMLs[$index]); + } + // Close zip file if ($this->_objZip->close() === false) { throw new Exception('Could not close zip file.'); diff --git a/tests/PhpWord/Tests/TemplateTest.php b/tests/PhpWord/Tests/TemplateTest.php index 669ae946..848d7736 100644 --- a/tests/PhpWord/Tests/TemplateTest.php +++ b/tests/PhpWord/Tests/TemplateTest.php @@ -156,4 +156,24 @@ final class TemplateTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expectedVar, $actualVar); $this->assertTrue($docFound); } + + public function testVariablesCanBeReplacedInHeaderAndFooter() + { + $template = __DIR__ . "/_files/templates/header-footer.docx"; + $expectedVar = array('documentContent', 'headerValue', 'footerValue'); + $docName = 'header-footer-test-result.docx'; + + $document = new Template($template); + $actualVar = $document->getVariables(); + $document->setValue('headerValue', 'Header Value'); + $document->setValue('documentContent', 'Document text.'); + $document->setValue('footerValue', 'Footer Value'); + $document->saveAs($docName); + $docFound = file_exists($docName); + unlink($docName); + + $this->assertEquals($expectedVar, $actualVar); + $this->assertTrue($docFound); + + } } diff --git a/tests/PhpWord/Tests/_files/templates/header-footer.docx b/tests/PhpWord/Tests/_files/templates/header-footer.docx new file mode 100644 index 0000000000000000000000000000000000000000..647d52222d612ef33a0113e8ea3ad84ad3c96085 GIT binary patch literal 4495 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 Date: Tue, 1 Apr 2014 22:13:03 +0100 Subject: [PATCH 2/3] Load any number of headers and footers --- src/PhpWord/Template.php | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/PhpWord/Template.php b/src/PhpWord/Template.php index e38e2a5a..7d933755 100644 --- a/src/PhpWord/Template.php +++ b/src/PhpWord/Template.php @@ -75,16 +75,17 @@ class Template $this->_objZip = new $zipClass(); $this->_objZip->open($this->_tempFileName); - // Find and load up to three headers and footers - for ($i = 1; $i <= 3; $i++) { - $headerName = $this->getHeaderName($i); - $footerName = $this->getFooterName($i); - if ($this->_objZip->locateName($headerName) !== false) { - $this->_headerXMLs[$i] = $this->_objZip->getFromName($headerName); - } - if ($this->_objZip->locateName($footerName) !== false) { - $this->_footerXMLs[$i] = $this->_objZip->getFromName($footerName); - } + // Find and load headers and footers + $i = 1; + while ($this->_objZip->locateName($this->getHeaderName($i)) !== false) { + $this->_headerXMLs[$i] = $this->_objZip->getFromName($this->getHeaderName($i)); + $i++; + } + + $i = 1; + while ($this->_objZip->locateName($this->getFooterName($i)) !== false) { + $this->_footerXMLs[$i] = $this->_objZip->getFromName($this->getFooterName($i)); + $i++; } $this->_documentXML = $this->_objZip->getFromName('word/document.xml'); From 51a8628209016a7e8e6fd2550a2bee2ce238e590 Mon Sep 17 00:00:00 2001 From: Ivan Lanin Date: Wed, 2 Apr 2014 18:57:34 +0700 Subject: [PATCH 3/3] Merge #190, reorder methods (public, protected, private), and add example to Sample_07_TemplateCloneRow --- CHANGELOG.md | 1 + samples/Sample_07_TemplateCloneRow.php | 8 +- .../resources/Sample_07_TemplateCloneRow.docx | Bin 17939 -> 22835 bytes src/PhpWord/Template.php | 370 +++++++++--------- tests/PhpWord/Tests/TemplateTest.php | 3 + 5 files changed, 194 insertions(+), 188 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c785dbf4..08ef04fb 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This is the changelog between releases of PHPWord. Releases are listed in revers - Table: Add `exactHeight` to row style to define whether row height should be exact or atLeast - @jcarignan GH-168 - Element: New `CheckBox` element for sections and table cells - @ozilion GH-156 - Settings: Ability to use PCLZip as alternative to ZipArchive - @bskrtich @ivanlanin GH-106 GH-140 GH-185 +- Template: Ability to find & replace variables in headers & footers - @dgudgeon GH-190 ### Bugfixes diff --git a/samples/Sample_07_TemplateCloneRow.php b/samples/Sample_07_TemplateCloneRow.php index 4344fafc..72464d84 100755 --- a/samples/Sample_07_TemplateCloneRow.php +++ b/samples/Sample_07_TemplateCloneRow.php @@ -7,6 +7,11 @@ $phpWord = new \PhpOffice\PhpWord\PhpWord(); $document = $phpWord->loadTemplate('resources/Sample_07_TemplateCloneRow.docx'); +// Variables on different parts of document +$document->setValue('weekday', date('l')); // On section/content +$document->setValue('time', date('H:i')); // On footer +$document->setValue('serverName', $_SERVER['SERVER_NAME']); // On header + // Simple table $document->cloneRow('rowValue', 10); @@ -32,9 +37,6 @@ $document->setValue('rowNumber#8', '8'); $document->setValue('rowNumber#9', '9'); $document->setValue('rowNumber#10', '10'); -$document->setValue('weekday', date('l')); -$document->setValue('time', date('H:i')); - // Table with a spanned cell $document->cloneRow('userId', 3); diff --git a/samples/resources/Sample_07_TemplateCloneRow.docx b/samples/resources/Sample_07_TemplateCloneRow.docx index 25a8c418b6795873777334de254fae7081e1fa14..9ac6c2a18d8979b98e86006dafd1a3f65546bfd6 100755 GIT binary patch delta 18508 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 = tempnam(sys_get_temp_dir(), ''); - if ($this->_tempFileName === false) { + $this->tempFileName = tempnam(sys_get_temp_dir(), ''); + if ($this->tempFileName === false) { throw new Exception('Could not create temporary file with unique name in the default temporary directory.'); } // Copy the source File to the temp File - if (!copy($strFilename, $this->_tempFileName)) { - throw new Exception("Could not copy the template from {$strFilename} to {$this->_tempFileName}."); + if (!copy($strFilename, $this->tempFileName)) { + throw new Exception("Could not copy the template from {$strFilename} to {$this->tempFileName}."); } $zipClass = Settings::getZipClass(); - $this->_objZip = new $zipClass(); - $this->_objZip->open($this->_tempFileName); + $this->zipClass = new $zipClass(); + $this->zipClass->open($this->tempFileName); // Find and load headers and footers $i = 1; - while ($this->_objZip->locateName($this->getHeaderName($i)) !== false) { - $this->_headerXMLs[$i] = $this->_objZip->getFromName($this->getHeaderName($i)); + while ($this->zipClass->locateName($this->getHeaderName($i)) !== false) { + $this->headerXMLs[$i] = $this->zipClass->getFromName($this->getHeaderName($i)); $i++; } $i = 1; - while ($this->_objZip->locateName($this->getFooterName($i)) !== false) { - $this->_footerXMLs[$i] = $this->_objZip->getFromName($this->getFooterName($i)); + while ($this->zipClass->locateName($this->getFooterName($i)) !== false) { + $this->footerXMLs[$i] = $this->zipClass->getFromName($this->getFooterName($i)); $i++; } - $this->_documentXML = $this->_objZip->getFromName('word/document.xml'); - } - - /** - * Get the name of the footer file for $index - * @param integer $index - * @return string - */ - private function getFooterName($index) - { - return sprintf('word/footer%d.xml', $index); - } - - /** - * Get the name of the header file for $index - * @param integer $index - * @return string - */ - private function getHeaderName($index) - { - return sprintf('word/header%d.xml', $index); + $this->documentXML = $this->zipClass->getFromName('word/document.xml'); } /** @@ -117,7 +97,7 @@ class Template * @param \DOMDocument $xslDOMDocument * @param array $xslOptions * @param string $xslOptionsURI - * @throws \PhpOffice\PhpWord\Exceptions\Exception + * @throws Exception */ public function applyXslStyleSheet(&$xslDOMDocument, $xslOptions = array(), $xslOptionsURI = '') { @@ -130,7 +110,7 @@ class Template } $xmlDOMDocument = new \DOMDocument(); - if ($xmlDOMDocument->loadXML($this->_documentXML) === false) { + if ($xmlDOMDocument->loadXML($this->documentXML) === false) { throw new Exception('Could not load XML from the given template.'); } @@ -139,7 +119,143 @@ class Template throw new Exception('Could not transform the given XML document.'); } - $this->_documentXML = $xmlTransformed; + $this->documentXML = $xmlTransformed; + } + + /** + * Set a Template value + * + * @param mixed $search + * @param mixed $replace + * @param integer $limit + */ + public function setValue($search, $replace, $limit = -1) + { + foreach ($this->headerXMLs as $index => $headerXML) { + $this->headerXMLs[$index] = $this->setValueForPart($this->headerXMLs[$index], $search, $replace, $limit); + } + + $this->documentXML = $this->setValueForPart($this->documentXML, $search, $replace, $limit); + + foreach ($this->footerXMLs as $index => $headerXML) { + $this->footerXMLs[$index] = $this->setValueForPart($this->footerXMLs[$index], $search, $replace, $limit); + } + } + + /** + * Returns array of all variables in template + * @return string[] + */ + public function getVariables() + { + $variables = $this->getVariablesForPart($this->documentXML); + + foreach ($this->headerXMLs as $headerXML) { + $variables = array_merge($variables, $this->getVariablesForPart($headerXML)); + } + + foreach ($this->footerXMLs as $footerXML) { + $variables = array_merge($variables, $this->getVariablesForPart($footerXML)); + } + + return array_unique($variables); + } + + /** + * Clone a table row in a template document + * + * @param string $search + * @param int $numberOfClones + * @throws Exception + */ + public function cloneRow($search, $numberOfClones) + { + if (substr($search, 0, 2) !== '${' && substr($search, -1) !== '}') { + $search = '${' . $search . '}'; + } + + $tagPos = strpos($this->documentXML, $search); + if (!$tagPos) { + throw new Exception("Can not clone row, template variable not found or variable contains markup."); + } + + $rowStart = $this->findRowStart($tagPos); + $rowEnd = $this->findRowEnd($tagPos); + $xmlRow = $this->getSlice($rowStart, $rowEnd); + + // Check if there's a cell spanning multiple rows. + if (preg_match('##', $xmlRow)) { + $extraRowStart = $rowEnd; + $extraRowEnd = $rowEnd; + while (true) { + $extraRowStart = $this->findRowStart($extraRowEnd + 1); + $extraRowEnd = $this->findRowEnd($extraRowEnd + 1); + + // If extraRowEnd is lower then 7, there was no next row found. + if ($extraRowEnd < 7) { + break; + } + + // If tmpXmlRow doesn't contain continue, this row is no longer part of the spanned row. + $tmpXmlRow = $this->getSlice($extraRowStart, $extraRowEnd); + if (!preg_match('##', $tmpXmlRow) && !preg_match('##', $tmpXmlRow)) { + break; + } + // This row was a spanned row, update $rowEnd and search for the next row. + $rowEnd = $extraRowEnd; + } + $xmlRow = $this->getSlice($rowStart, $rowEnd); + } + + $result = $this->getSlice(0, $rowStart); + for ($i = 1; $i <= $numberOfClones; $i++) { + $result .= preg_replace('/\$\{(.*?)\}/', '\${\\1#' . $i . '}', $xmlRow); + } + $result .= $this->getSlice($rowEnd); + + $this->documentXML = $result; + } + + /** + * Save XML to temporary file + * + * @return string + * @throws Exception + */ + public function save() + { + foreach ($this->headerXMLs as $index => $headerXML) { + $this->zipClass->addFromString($this->getHeaderName($index), $this->headerXMLs[$index]); + } + + $this->zipClass->addFromString('word/document.xml', $this->documentXML); + + foreach ($this->footerXMLs as $index => $headerXML) { + $this->zipClass->addFromString($this->getFooterName($index), $this->footerXMLs[$index]); + } + + // Close zip file + if ($this->zipClass->close() === false) { + throw new Exception('Could not close zip file.'); + } + + return $this->tempFileName; + } + + /** + * Save XML to defined name + * + * @param string $strFilename + */ + public function saveAs($strFilename) + { + $tempFilename = $this->save(); + + if (\file_exists($strFilename)) { + unlink($strFilename); + } + + rename($tempFilename, $strFilename); } /** @@ -181,26 +297,6 @@ class Template return preg_replace("{$regExpDelim}{$escapedSearch}{$regExpDelim}u", $replace, $documentPartXML, $limit); } - /** - * Set a Template value - * - * @param mixed $search - * @param mixed $replace - * @param integer $limit - */ - public function setValue($search, $replace, $limit = -1) - { - foreach ($this->_headerXMLs as $index => $headerXML) { - $this->_headerXMLs[$index] = $this->setValueForPart($this->_headerXMLs[$index], $search, $replace, $limit); - } - - $this->_documentXML = $this->setValueForPart($this->_documentXML, $search, $replace, $limit); - - foreach ($this->_footerXMLs as $index => $headerXML) { - $this->_footerXMLs[$index] = $this->setValueForPart($this->_footerXMLs[$index], $search, $replace, $limit); - } - } - /** * Find all variables in $documentPartXML * @param string $documentPartXML @@ -214,22 +310,23 @@ class Template } /** - * Returns array of all variables in template - * @return string[] + * Get the name of the footer file for $index + * @param integer $index + * @return string */ - public function getVariables() + private function getFooterName($index) { - $variables = $this->getVariablesForPart($this->_documentXML); + return sprintf('word/footer%d.xml', $index); + } - foreach ($this->_headerXMLs as $headerXML) { - $variables = array_merge($variables, $this->getVariablesForPart($headerXML)); - } - - foreach ($this->_footerXMLs as $footerXML) { - $variables = array_merge($variables, $this->getVariablesForPart($footerXML)); - } - - return array_unique($variables); + /** + * Get the name of the header file for $index + * @param integer $index + * @return string + */ + private function getHeaderName($index) + { + return sprintf('word/header%d.xml', $index); } /** @@ -237,13 +334,13 @@ class Template * * @param int $offset * @return int - * @throws \PhpOffice\PhpWord\Exceptions\Exception + * @throws Exception */ - private function _findRowStart($offset) + private function findRowStart($offset) { - $rowStart = strrpos($this->_documentXML, "_documentXML) - $offset) * -1)); + $rowStart = strrpos($this->documentXML, "documentXML) - $offset) * -1)); if (!$rowStart) { - $rowStart = strrpos($this->_documentXML, "", ((strlen($this->_documentXML) - $offset) * -1)); + $rowStart = strrpos($this->documentXML, "", ((strlen($this->documentXML) - $offset) * -1)); } if (!$rowStart) { throw new Exception("Can not find the start position of the row to clone."); @@ -257,9 +354,9 @@ class Template * @param int $offset * @return int */ - private function _findRowEnd($offset) + private function findRowEnd($offset) { - $rowEnd = strpos($this->_documentXML, "", $offset) + 7; + $rowEnd = strpos($this->documentXML, "", $offset) + 7; return $rowEnd; } @@ -270,108 +367,11 @@ class Template * @param int $endPosition * @return string */ - private function _getSlice($startPosition, $endPosition = 0) + private function getSlice($startPosition, $endPosition = 0) { if (!$endPosition) { - $endPosition = strlen($this->_documentXML); + $endPosition = strlen($this->documentXML); } - return substr($this->_documentXML, $startPosition, ($endPosition - $startPosition)); - } - - /** - * Clone a table row in a template document - * - * @param string $search - * @param int $numberOfClones - * @throws \PhpOffice\PhpWord\Exceptions\Exception - */ - public function cloneRow($search, $numberOfClones) - { - if (substr($search, 0, 2) !== '${' && substr($search, -1) !== '}') { - $search = '${' . $search . '}'; - } - - $tagPos = strpos($this->_documentXML, $search); - if (!$tagPos) { - throw new Exception("Can not clone row, template variable not found or variable contains markup."); - } - - $rowStart = $this->_findRowStart($tagPos); - $rowEnd = $this->_findRowEnd($tagPos); - $xmlRow = $this->_getSlice($rowStart, $rowEnd); - - // Check if there's a cell spanning multiple rows. - if (preg_match('##', $xmlRow)) { - $extraRowStart = $rowEnd; - $extraRowEnd = $rowEnd; - while (true) { - $extraRowStart = $this->_findRowStart($extraRowEnd + 1); - $extraRowEnd = $this->_findRowEnd($extraRowEnd + 1); - - // If extraRowEnd is lower then 7, there was no next row found. - if ($extraRowEnd < 7) { - break; - } - - // If tmpXmlRow doesn't contain continue, this row is no longer part of the spanned row. - $tmpXmlRow = $this->_getSlice($extraRowStart, $extraRowEnd); - if (!preg_match('##', $tmpXmlRow) && !preg_match('##', $tmpXmlRow)) { - break; - } - // This row was a spanned row, update $rowEnd and search for the next row. - $rowEnd = $extraRowEnd; - } - $xmlRow = $this->_getSlice($rowStart, $rowEnd); - } - - $result = $this->_getSlice(0, $rowStart); - for ($i = 1; $i <= $numberOfClones; $i++) { - $result .= preg_replace('/\$\{(.*?)\}/', '\${\\1#' . $i . '}', $xmlRow); - } - $result .= $this->_getSlice($rowEnd); - - $this->_documentXML = $result; - } - - /** - * Save XML to temporary file - * - * @return string - * @throws \PhpOffice\PhpWord\Exceptions\Exception - */ - public function save() - { - foreach ($this->_headerXMLs as $index => $headerXML) { - $this->_objZip->addFromString($this->getHeaderName($index), $this->_headerXMLs[$index]); - } - - $this->_objZip->addFromString('word/document.xml', $this->_documentXML); - - foreach ($this->_footerXMLs as $index => $headerXML) { - $this->_objZip->addFromString($this->getFooterName($index), $this->_footerXMLs[$index]); - } - - // Close zip file - if ($this->_objZip->close() === false) { - throw new Exception('Could not close zip file.'); - } - - return $this->_tempFileName; - } - - /** - * Save XML to defined name - * - * @param string $strFilename - */ - public function saveAs($strFilename) - { - $tempFilename = $this->save(); - - if (\file_exists($strFilename)) { - unlink($strFilename); - } - - rename($tempFilename, $strFilename); + return substr($this->documentXML, $startPosition, ($endPosition - $startPosition)); } } diff --git a/tests/PhpWord/Tests/TemplateTest.php b/tests/PhpWord/Tests/TemplateTest.php index 848d7736..13ed7646 100644 --- a/tests/PhpWord/Tests/TemplateTest.php +++ b/tests/PhpWord/Tests/TemplateTest.php @@ -157,6 +157,9 @@ final class TemplateTest extends \PHPUnit_Framework_TestCase $this->assertTrue($docFound); } + /** + * Replace variables in header and footer + */ public function testVariablesCanBeReplacedInHeaderAndFooter() { $template = __DIR__ . "/_files/templates/header-footer.docx";