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);