Skip to content

Commit 86bf46e

Browse files
committed
fix block handling in template processor
- more relaxed but reliable regexp to detect blocks - support multiple blocks with same name
1 parent 72a76b8 commit 86bf46e

File tree

2 files changed

+51
-23
lines changed

2 files changed

+51
-23
lines changed

src/PhpWord/TemplateProcessor.php

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -853,46 +853,38 @@ public function cloneRowAndSetValues($search, $values): void
853853
* @param bool $replace
854854
* @param bool $indexVariables If true, any variables inside the block will be indexed (postfixed with #1, #2, ...)
855855
* @param array $variableReplacements Array containing replacements for macros found inside the block to clone
856-
*
857-
* @return null|string
858856
*/
859-
public function cloneBlock($blockname, $clones = 1, $replace = true, $indexVariables = false, $variableReplacements = null)
857+
public function cloneBlock($blockname, $clones = 1, $replace = true, $indexVariables = false, $variableReplacements = null): void
860858
{
861-
$xmlBlock = null;
862-
$matches = [];
863-
$escapedMacroOpeningChars = self::$macroOpeningChars;
864-
$escapedMacroClosingChars = self::$macroClosingChars;
865-
preg_match(
866-
//'/(.*((?s)<w:p\b(?:(?!<w:p\b).)*?\{{' . $blockname . '}<\/w:.*?p>))(.*)((?s)<w:p\b(?:(?!<w:p\b).)[^$]*?\{{\/' . $blockname . '}<\/w:.*?p>)/is',
867-
'/(.*((?s)<w:p\b(?:(?!<w:p\b).)*?\\' . $escapedMacroOpeningChars . $blockname . $escapedMacroClosingChars . '<\/w:.*?p>))(.*)((?s)<w:p\b(?:(?!<w:p\b).)[^$]*?\\' . $escapedMacroOpeningChars . '\/' . $blockname . $escapedMacroClosingChars . '<\/w:.*?p>)/is',
868-
//'/(.*((?s)<w:p\b(?:(?!<w:p\b).)*?\\'. $escapedMacroOpeningChars . $blockname . '}<\/w:.*?p>))(.*)((?s)<w:p\b(?:(?!<w:p\b).)[^$]*?\\'.$escapedMacroOpeningChars.'\/' . $blockname . '}<\/w:.*?p>)/is',
869-
$this->tempDocumentMainPart,
870-
$matches
871-
);
859+
$open = preg_quote(self::ensureMacroCompleted($blockname));
860+
$close = str_replace('/', '\/', preg_quote(self::ensureMacroCompleted("/$blockname")));
872861

873-
if (isset($matches[3])) {
874-
$xmlBlock = $matches[3];
862+
$beginRe = '(<w:p\b(?:(?!<w:p\b).)*?' . $open . '.*?<\/w:p>)';
863+
$endRe = '(<w:p\b(?:(?!<w:p\b).)*?' . $close . '.*?<\/w:p>)';
864+
$betweenRe = '.*?(<w:p\b(?:(?!<w:p\b).)*?.*?<\/w:p>).*?';
865+
$re = "/$beginRe$betweenRe$endRe/is";
866+
867+
$blockMatches = [];
868+
preg_match_all($re, $this->tempDocumentMainPart, $blockMatches, \PREG_SET_ORDER);
869+
870+
foreach ($blockMatches as $matches) {
871+
$xmlBlock = $matches[2];
875872
if ($indexVariables) {
876873
$cloned = $this->indexClonedVariables($clones, $xmlBlock);
877874
} elseif ($variableReplacements !== null && is_array($variableReplacements)) {
878875
$cloned = $this->replaceClonedVariables($variableReplacements, $xmlBlock);
879876
} else {
880-
$cloned = [];
881-
for ($i = 1; $i <= $clones; ++$i) {
882-
$cloned[] = $xmlBlock;
883-
}
877+
$cloned = array_fill(0, max(0, $clones), $xmlBlock);
884878
}
885879

886880
if ($replace) {
887881
$this->tempDocumentMainPart = str_replace(
888-
$matches[2] . $matches[3] . $matches[4],
882+
$matches[0],
889883
implode('', $cloned),
890884
$this->tempDocumentMainPart
891885
);
892886
}
893887
}
894-
895-
return $xmlBlock;
896888
}
897889

898890
/**

tests/PhpWordTests/TemplateProcessorTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,42 @@ public function testCloneBlock(): void
923923
self::assertEquals(3, substr_count($templateProcessor->getMainPart(), 'This block will be cloned with ${variable}'));
924924
}
925925

926+
/**
927+
* @covers ::cloneBlock
928+
*/
929+
public function testCloneBlockWithMultipleOccurrencesOfSameBlock(): void
930+
{
931+
$block = '<w:p>
932+
<w:r>
933+
<w:rPr></w:rPr>
934+
<w:t>${CLONEME}</w:t>
935+
</w:r>
936+
</w:p>
937+
<w:p>
938+
<w:r>
939+
<w:t xml:space="preserve">This block will be cloned with ${variable}</w:t>
940+
</w:r>
941+
</w:p>
942+
<w:p>
943+
<w:r w:rsidRPr="00204FED">
944+
<w:t>${/CLONEME}</w:t>
945+
</w:r>
946+
</w:p>';
947+
$blocks = array_fill(0, 2, $block);
948+
$mainPart = '<?xml version="1.0" encoding="UTF-8"?>' . implode("\n", $blocks);
949+
950+
$replacements = [
951+
['variable' => 'PHPWord'],
952+
['variable' => 'PhpOffice']
953+
];
954+
955+
$templateProcessor = new TestableTemplateProcesor($mainPart);
956+
$templateProcessor->cloneBlock('CLONEME', 0, true, false, $replacements);
957+
958+
self::assertEquals(2, substr_count($templateProcessor->getMainPart(), 'This block will be cloned with PHPWord'));
959+
self::assertEquals(2, substr_count($templateProcessor->getMainPart(), 'This block will be cloned with PhpOffice'));
960+
}
961+
926962
/**
927963
* @covers ::cloneBlock
928964
*/

0 commit comments

Comments
 (0)