From 19ca448744edcd9319d6f5f76289b88c5caf4e74 Mon Sep 17 00:00:00 2001 From: glaszig Date: Mon, 28 Aug 2023 23:14:57 -0300 Subject: [PATCH] fix block handling in template processor - more relaxed but reliable regexp to detect blocks - support multiple blocks with same name --- src/PhpWord/TemplateProcessor.php | 38 ++++++++------------ tests/PhpWordTests/TemplateProcessorTest.php | 36 +++++++++++++++++++ 2 files changed, 51 insertions(+), 23 deletions(-) diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 6ef22fc538..c836281407 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -853,46 +853,38 @@ public function cloneRowAndSetValues($search, $values): void * @param bool $replace * @param bool $indexVariables If true, any variables inside the block will be indexed (postfixed with #1, #2, ...) * @param array $variableReplacements Array containing replacements for macros found inside the block to clone - * - * @return null|string */ - public function cloneBlock($blockname, $clones = 1, $replace = true, $indexVariables = false, $variableReplacements = null) + public function cloneBlock($blockname, $clones = 1, $replace = true, $indexVariables = false, $variableReplacements = null): void { - $xmlBlock = null; - $matches = []; - $escapedMacroOpeningChars = self::$macroOpeningChars; - $escapedMacroClosingChars = self::$macroClosingChars; - preg_match( - //'/(.*((?s)))(.*)((?s))/is', - '/(.*((?s)))(.*)((?s))/is', - //'/(.*((?s)))(.*)((?s))/is', - $this->tempDocumentMainPart, - $matches - ); + $open = preg_quote(self::ensureMacroCompleted($blockname)); + $close = str_replace('/', '\/', preg_quote(self::ensureMacroCompleted("/$blockname"))); - if (isset($matches[3])) { - $xmlBlock = $matches[3]; + $beginRe = '()'; + $endRe = '()'; + $betweenRe = '.*?().*?'; + $re = "/$beginRe$betweenRe$endRe/is"; + + $blockMatches = []; + preg_match_all($re, $this->tempDocumentMainPart, $blockMatches, \PREG_SET_ORDER); + + foreach ($blockMatches as $matches) { + $xmlBlock = $matches[2]; if ($indexVariables) { $cloned = $this->indexClonedVariables($clones, $xmlBlock); } elseif ($variableReplacements !== null && is_array($variableReplacements)) { $cloned = $this->replaceClonedVariables($variableReplacements, $xmlBlock); } else { - $cloned = []; - for ($i = 1; $i <= $clones; ++$i) { - $cloned[] = $xmlBlock; - } + $cloned = array_fill(0, max(0, $clones), $xmlBlock); } if ($replace) { $this->tempDocumentMainPart = str_replace( - $matches[2] . $matches[3] . $matches[4], + $matches[0], implode('', $cloned), $this->tempDocumentMainPart ); } } - - return $xmlBlock; } /** diff --git a/tests/PhpWordTests/TemplateProcessorTest.php b/tests/PhpWordTests/TemplateProcessorTest.php index d8ff9ace9e..874d5d1ff8 100644 --- a/tests/PhpWordTests/TemplateProcessorTest.php +++ b/tests/PhpWordTests/TemplateProcessorTest.php @@ -923,6 +923,42 @@ public function testCloneBlock(): void self::assertEquals(3, substr_count($templateProcessor->getMainPart(), 'This block will be cloned with ${variable}')); } + /** + * @covers ::cloneBlock + */ + public function testCloneBlockWithMultipleOccurrencesOfSameBlock(): void + { + $block = ' + + + ${CLONEME} + + + + + This block will be cloned with ${variable} + + + + + ${/CLONEME} + + '; + $blocks = array_fill(0, 2, $block); + $mainPart = '' . implode("\n", $blocks); + + $replacements = [ + ['variable' => 'PHPWord'], + ['variable' => 'PhpOffice'], + ]; + + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->cloneBlock('CLONEME', 0, true, false, $replacements); + + self::assertEquals(2, substr_count($templateProcessor->getMainPart(), 'This block will be cloned with PHPWord')); + self::assertEquals(2, substr_count($templateProcessor->getMainPart(), 'This block will be cloned with PhpOffice')); + } + /** * @covers ::cloneBlock */