Skip to content

Commit

Permalink
fix block handling in template processor
Browse files Browse the repository at this point in the history
- more relaxed but reliable regexp to detect blocks
- support multiple blocks with same name
  • Loading branch information
glaszig committed Aug 31, 2023
1 parent c17c4c7 commit 50e23b2
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 23 deletions.
38 changes: 15 additions & 23 deletions src/PhpWord/TemplateProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)<w:p\b(?:(?!<w:p\b).)*?\{{' . $blockname . '}<\/w:.*?p>))(.*)((?s)<w:p\b(?:(?!<w:p\b).)[^$]*?\{{\/' . $blockname . '}<\/w:.*?p>)/is',
'/(.*((?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',
//'/(.*((?s)<w:p\b(?:(?!<w:p\b).)*?\\'. $escapedMacroOpeningChars . $blockname . '}<\/w:.*?p>))(.*)((?s)<w:p\b(?:(?!<w:p\b).)[^$]*?\\'.$escapedMacroOpeningChars.'\/' . $blockname . '}<\/w:.*?p>)/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 = '(<w:p\b(?:(?!<w:p\b).)*?' . $open . '.*?<\/w:p>)';
$endRe = '(<w:p\b(?:(?!<w:p\b).)*?' . $close . '.*?<\/w:p>)';
$betweenRe = '.*?(<w:p\b(?:(?!<w:p\b).)*?.*?<\/w:p>).*?';
$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;
}

/**
Expand Down
36 changes: 36 additions & 0 deletions tests/PhpWordTests/TemplateProcessorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '<w:p>
<w:r>
<w:rPr></w:rPr>
<w:t>${CLONEME}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t xml:space="preserve">This block will be cloned with ${variable}</w:t>
</w:r>
</w:p>
<w:p>
<w:r w:rsidRPr="00204FED">
<w:t>${/CLONEME}</w:t>
</w:r>
</w:p>';
$blocks = array_fill(0, 2, $block);
$mainPart = '<?xml version="1.0" encoding="UTF-8"?>' . 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
*/
Expand Down

0 comments on commit 50e23b2

Please sign in to comment.