diff --git a/docs/changes/1.x/1.4.0.md b/docs/changes/1.x/1.4.0.md
index 2afadebd5f..6be749bf4f 100644
--- a/docs/changes/1.x/1.4.0.md
+++ b/docs/changes/1.x/1.4.0.md
@@ -17,7 +17,8 @@
- Add basic ruby text (phonetic guide) support for Word2007 and HTML Reader/Writer, RTF Writer, basic support for ODT writing by [@Deadpikle](https://github.com/Deadpikle) in [#2727](https://github.com/PHPOffice/PHPWord/pull/2727)
- Reader HTML: Support font styles for h1/h6 by [@Progi1984](https://github.com/Progi1984) fixing [#2619](https://github.com/PHPOffice/PHPWord/issues/2619) in [#2737](https://github.com/PHPOffice/PHPWord/pull/2737)
- Writer EPub3: Basic support by [@Sambit003](https://github.com/Sambit003) fixing [#55](https://github.com/PHPOffice/PHPWord/issues/55) in [#2724](https://github.com/PHPOffice/PHPWord/pull/2724)
-
+- TemplateProcessor: Added a method to replace multiple XML blocks by [@sr-44](https://github.com/sr-44) in [#2741](https://github.com/PHPOffice/PHPWord/pull/2741)
+
### Bug fixes
- Writer ODText: Support for images inside a textRun by [@Progi1984](https://github.com/Progi1984) fixing [#2240](https://github.com/PHPOffice/PHPWord/issues/2240) in [#2668](https://github.com/PHPOffice/PHPWord/pull/2668)
diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php
index 643bae4cb6..56f288a900 100644
--- a/src/PhpWord/TemplateProcessor.php
+++ b/src/PhpWord/TemplateProcessor.php
@@ -1353,12 +1353,41 @@ public function replaceXmlBlock($macro, $block, $blockType = 'w:p')
return $this;
}
+ /**
+ * Replace an XML block surrounding a macro with a new block.
+ *
+ * @param string $macro Name of macro
+ * @param string $block New block content
+ * @param string $blockType XML tag type of block
+ *
+ * @return TemplateProcessor Fluent interface
+ */
+ public function replaceMultipleXmlBlocks($macro, $block, $blockType = 'w:p')
+ {
+ $offset = 0;
+ while (true) {
+ $where = $this->findAllContainingXmlBlockForMacro($macro, $blockType, $offset);
+
+ if (false === $where) {
+ break;
+ }
+
+ $this->tempDocumentMainPart = $this->getSlice(0, $where['start']) . $block . $this->getSlice($where['end']);
+
+ $offset = $where['start'] + strlen($block);
+ }
+
+ return $this;
+ }
+
/**
* Find start and end of XML block containing the given macro
* e.g. ...${macro}....
*
* Note that only the first instance of the macro will be found
*
+ * @see findAllContainingXmlBlockForMacro for finding all instances
+ *
* @param string $macro Name of macro
* @param string $blockType XML tag for block
*
@@ -1383,6 +1412,42 @@ protected function findContainingXmlBlockForMacro($macro, $blockType = 'w:p')
return ['start' => $start, 'end' => $end];
}
+ /**
+ * Find start and end of XML block containing the given macro
+ * e.g. ...${macro}....
+ *
+ * Unlike `findContainingXmlBlockForMacro`, this method searches for all occurrences
+ * of the macro starting from the specified offset.
+ *
+ * @param string $macro Name of macro
+ * @param string $blockType XML tag for block
+ * @param int $offset Position to start searching for the macro
+ *
+ * @return array{start: int, end: int}|false FALSE if not found, otherwise array with start and end
+ */
+ protected function findAllContainingXmlBlockForMacro($macro, $blockType = 'w:p', $offset = 0)
+ {
+ $macroPos = $this->findMacro($macro, $offset);
+
+ if (0 > $macroPos) {
+ return false;
+ }
+
+ $start = $this->findXmlBlockStart($macroPos, $blockType);
+
+ if (0 > $start) {
+ return false;
+ }
+
+ $end = $this->findXmlBlockEnd($start, $blockType);
+ $slice = $this->getSlice($start, $end);
+ if ($end < 0 || strpos($slice, $macro) === false) {
+ return false;
+ }
+
+ return ['start' => $start, 'end' => $end];
+ }
+
/**
* Find the position of (the start of) a macro.
*
diff --git a/tests/PhpWordTests/TemplateProcessorTest.php b/tests/PhpWordTests/TemplateProcessorTest.php
index 9ba6933e74..c01fbb9ceb 100644
--- a/tests/PhpWordTests/TemplateProcessorTest.php
+++ b/tests/PhpWordTests/TemplateProcessorTest.php
@@ -1613,6 +1613,113 @@ public function testShouldReturnFalseIfXmlBlockNotFoundWithCustomMacro(): void
self::assertFalse($result);
}
+ /**
+ * @covers ::findAllContainingXmlBlockForMacro
+ */
+ public function testFindAllContainingXmlBlockForMacro(): void
+ {
+ $toFind = '
+
+
+
+
+ This is the first ${macro}
+ ';
+
+ $mainPart = '
+
+
+
+
+
+
+ Some text without macro
+
+
+ ' . $toFind . '
+
+
+
+
+
+
+ This is the second ${macro}
+
+
+ ';
+
+ $templateProcessor = new TestableTemplateProcesor($mainPart);
+
+ $firstOccurrence = $templateProcessor->findAllContainingXmlBlockForMacro('${macro}', 'w:r');
+
+ self::assertNotFalse($firstOccurrence);
+
+ self::assertEquals(
+ $toFind,
+ $templateProcessor->getSlice($firstOccurrence['start'], $firstOccurrence['end'])
+ );
+
+ $secondOccurrence = $templateProcessor->findAllContainingXmlBlockForMacro('${macro}', 'w:r', $firstOccurrence['end']);
+
+ self::assertNotFalse($secondOccurrence);
+
+ $expectedSecond = '
+
+
+
+
+ This is the second ${macro}
+ ';
+
+ self::assertEquals($expectedSecond, $templateProcessor->getSlice($secondOccurrence['start'], $secondOccurrence['end']));
+ }
+
+ /**
+ * @covers ::replaceXmlBlock
+ */
+ public function testReplaceXmlBlock(): void
+ {
+ $originalXml = '
+ ${macro}
+ Some text
+ ${macro}
+ ';
+
+ $expectedXml = '
+ New content
+ Some text
+ ${macro}
+ ';
+
+ $templateProcessor = new TestableTemplateProcesor($originalXml);
+ $templateProcessor->replaceXmlBlock('${macro}', 'New content');
+
+ self::assertEquals($expectedXml, $templateProcessor->getMainPart());
+ }
+
+ /**
+ * @covers ::replaceMultipleXmlBlocks
+ */
+ public function testReplaceMultipleXmlBlocks(): void
+ {
+ $originalXml = '
+ ${macro}
+ Some text
+ ${macro}
+ ';
+
+ $expectedXml = '
+ New content
+ Some text
+ New content
+ ';
+
+ $templateProcessor = new TestableTemplateProcesor($originalXml);
+ $templateProcessor->replaceMultipleXmlBlocks('${macro}', 'New content');
+
+ self::assertEquals($expectedXml, $templateProcessor->getMainPart());
+ }
+
public function testShouldMakeFieldsUpdateOnOpen(): void
{
$settingsPart = '
diff --git a/tests/PhpWordTests/TestableTemplateProcesor.php b/tests/PhpWordTests/TestableTemplateProcesor.php
index 9d6eb9904e..4639c1d3e5 100644
--- a/tests/PhpWordTests/TestableTemplateProcesor.php
+++ b/tests/PhpWordTests/TestableTemplateProcesor.php
@@ -66,6 +66,11 @@ public function findContainingXmlBlockForMacro($macro, $blockType = 'w:p')
return parent::findContainingXmlBlockForMacro($macro, $blockType);
}
+ public function findAllContainingXmlBlockForMacro($macro, $blockType = 'w:p', $offset = 0)
+ {
+ return parent::findAllContainingXmlBlockForMacro($macro, $blockType, $offset);
+ }
+
public function getSlice($startPosition, $endPosition = 0)
{
return parent::getSlice($startPosition, $endPosition);