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. <w:p>...${macro}...</w:p>.
      *
      * 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. <w:p>...${macro}...</w:p>.
+     *
+     * 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 = '<w:r>
+                    <w:rPr>
+                        <w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:cs="Calibri"/>
+                        <w:lang w:val="en-GB"/>
+                    </w:rPr>
+                    <w:t>This is the first ${macro}</w:t>
+                </w:r>';
+
+        $mainPart = '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
+            <w:p>
+                <w:r>
+                    <w:rPr>
+                        <w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:cs="Calibri"/>
+                        <w:lang w:val="en-GB"/>
+                    </w:rPr>
+                    <w:t>Some text without macro</w:t>
+                </w:r>
+            </w:p>
+            <w:p>' . $toFind . '</w:p>
+            <w:p>
+                <w:r>
+                    <w:rPr>
+                        <w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:cs="Calibri"/>
+                        <w:lang w:val="en-GB"/>
+                    </w:rPr>
+                    <w:t>This is the second ${macro}</w:t>
+                </w:r>
+            </w:p>
+        </w:document>';
+
+        $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 = '<w:r>
+                    <w:rPr>
+                        <w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:cs="Calibri"/>
+                        <w:lang w:val="en-GB"/>
+                    </w:rPr>
+                    <w:t>This is the second ${macro}</w:t>
+                </w:r>';
+
+        self::assertEquals($expectedSecond, $templateProcessor->getSlice($secondOccurrence['start'], $secondOccurrence['end']));
+    }
+
+    /**
+     * @covers ::replaceXmlBlock
+     */
+    public function testReplaceXmlBlock(): void
+    {
+        $originalXml = '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
+            <w:p>${macro}</w:p>
+            <w:p>Some text</w:p>
+            <w:p>${macro}</w:p>
+        </w:document>';
+
+        $expectedXml = '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
+            <w:p>New content</w:p>
+            <w:p>Some text</w:p>
+            <w:p>${macro}</w:p>
+        </w:document>';
+
+        $templateProcessor = new TestableTemplateProcesor($originalXml);
+        $templateProcessor->replaceXmlBlock('${macro}', '<w:p>New content</w:p>');
+
+        self::assertEquals($expectedXml, $templateProcessor->getMainPart());
+    }
+
+    /**
+     * @covers ::replaceMultipleXmlBlocks
+     */
+    public function testReplaceMultipleXmlBlocks(): void
+    {
+        $originalXml = '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
+            <w:p>${macro}</w:p>
+            <w:p>Some text</w:p>
+            <w:p>${macro}</w:p>
+        </w:document>';
+
+        $expectedXml = '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
+            <w:p>New content</w:p>
+            <w:p>Some text</w:p>
+            <w:p>New content</w:p>
+        </w:document>';
+
+        $templateProcessor = new TestableTemplateProcesor($originalXml);
+        $templateProcessor->replaceMultipleXmlBlocks('${macro}', '<w:p>New content</w:p>');
+
+        self::assertEquals($expectedXml, $templateProcessor->getMainPart());
+    }
+
     public function testShouldMakeFieldsUpdateOnOpen(): void
     {
         $settingsPart = '<w:settings xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
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);