diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 7a5eaf55bb..4ec443dcbd 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -319,32 +319,19 @@ public function cloneRow($search, $numberOfClones) * * @return string|null */ - public function cloneBlock($blockname, $clones = 1, $replace = true) + public function cloneBlock($blockname, $clones = 1) { - $xmlBlock = null; - preg_match( - '/(<\?xml.*)(\${' . $blockname . '}<\/w:.*?p>)(.*)()/is', - $this->tempDocumentMainPart, - $matches - ); - - if (isset($matches[3])) { - $xmlBlock = $matches[3]; - $cloned = array(); - for ($i = 1; $i <= $clones; $i++) { - $cloned[] = $xmlBlock; - } - - if ($replace) { - $this->tempDocumentMainPart = str_replace( - $matches[2] . $matches[3] . $matches[4], - implode('', $cloned), - $this->tempDocumentMainPart - ); + $dom = \DOMDocument::loadXML($this->tempDocumentMainPart); + $nodeSets = $this->findBlocks($blockname, $dom, 'inner'); + foreach ($nodeSets as $nodeSet) { + for ($i = 1; $i < $clones; $i++ ) { + foreach ($nodeSet as $node) { + $nodeSet[0]->parentNode->insertBefore($node->cloneNode(true), $nodeSet[0]); + } } } - - return $xmlBlock; + $this->deleteNodeSets($this->findBlocks($blockname, $dom, 'outer')); + $this->tempDocumentMainPart = $dom->saveXML(); } /** @@ -355,19 +342,19 @@ public function cloneBlock($blockname, $clones = 1, $replace = true) */ public function replaceBlock($blockname, $replacement) { - preg_match( - '/(<\?xml.*)(\${' . $blockname . '}<\/w:.*?p>)(.*)()/is', - $this->tempDocumentMainPart, - $matches - ); - - if (isset($matches[3])) { - $this->tempDocumentMainPart = str_replace( - $matches[2] . $matches[3] . $matches[4], - $replacement, - $this->tempDocumentMainPart - ); + $dom = \DOMDocument::loadXML($this->tempDocumentMainPart); + $nodeSets = $this->findBlocks($blockname, $dom); + foreach ($nodeSets as $nodeSet) { + $newNode = $dom->createElement('t:marker'); + $nodeSet[0]->parentNode->insertBefore($newNode, $nodeSet[0]); } + $this->deleteNodeSets($nodeSets); + $xml = $dom->saveXML(); + $this->tempDocumentMainPart = str_replace( + '', + $replacement, + $xml + ); } /** @@ -377,7 +364,17 @@ public function replaceBlock($blockname, $replacement) */ public function deleteBlock($blockname) { - $this->replaceBlock($blockname, ''); + $dom = \DOMDocument::loadXML($this->tempDocumentMainPart); + $this->deleteNodeSets($this->findBlocks($blockname, $dom)); + $this->tempDocumentMainPart = $dom->saveXML(); + } + + private function deleteNodeSets($nodeSets) { + foreach ($nodeSets as $nodeSet) { + foreach ($nodeSet as $node) { + $node->parentNode->removeChild($node); + } + } } /** @@ -573,4 +570,46 @@ protected function getSlice($startPosition, $endPosition = 0) return substr($this->tempDocumentMainPart, $startPosition, ($endPosition - $startPosition)); } + + private function findBlocks($blockname, $domDoc, $type = 'complete') + { + $domXpath = new \DOMXpath($domDoc); + $max = $domXpath->query('//w:p[contains(., "${'.$blockname.'}")]')->length; + $nodeLists = array(); + for ($i = 1; $i <= $max; $i++) { + $query = join(' | ', self::getQueryByType($type)); + + $data = array( + 'BLOCKNAME' => $blockname, + 'INDEX' => $i + ); + $findFromTo = str_replace(array_keys($data), array_values($data), $query); + $nodelist = $domXpath->query($findFromTo); + $nodeLists[] = $nodelist; + } + return $nodeLists; + } + + private static function getQueryByType($type) + { + $parts = array( + '//w:p[contains(., "${BLOCKNAME}")][INDEX]', + // https://stackoverflow.com/questions/3428104/selecting-siblings-between-two-nodes-using-xpath + '//w:p[contains(., "${BLOCKNAME}")][INDEX]/ + following-sibling::w:p[contains(., "${/BLOCKNAME}")][1]/ + preceding-sibling::w:p[ + preceding-sibling::w:p[contains(., "${BLOCKNAME}")][INDEX] + ]', + '//w:p[contains(., "${/BLOCKNAME}")][INDEX]' + ); + switch ($type) { + case 'complete': + return $parts; + case 'inner': + return array($parts[1]); + case 'outer': + return array($parts[0], $parts[2]); + } + } + } diff --git a/tests/PhpWord/TemplateProcessorTest.php b/tests/PhpWord/TemplateProcessorTest.php index ea7395610d..01c42cd215 100644 --- a/tests/PhpWord/TemplateProcessorTest.php +++ b/tests/PhpWord/TemplateProcessorTest.php @@ -210,14 +210,21 @@ public function testCloneDeleteBlock() { $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/clone-delete-block.docx'); - $this->assertEquals( - array('DELETEME', '/DELETEME', 'CLONEME', '/CLONEME'), - $templateProcessor->getVariables() - ); - $docName = 'clone-delete-block-result.docx'; $templateProcessor->cloneBlock('CLONEME', 3); + $templateProcessor->cloneBlock('CLONEMEONCE'); $templateProcessor->deleteBlock('DELETEME'); + $templateProcessor->replaceBlock('REPLACEME', ' + + + + + + + + You have been replaced! + + '); $templateProcessor->saveAs($docName); $docFound = file_exists($docName); unlink($docName); diff --git a/tests/PhpWord/_files/templates/clone-delete-block.docx b/tests/PhpWord/_files/templates/clone-delete-block.docx index 049d5ca415..f74552a7bf 100644 Binary files a/tests/PhpWord/_files/templates/clone-delete-block.docx and b/tests/PhpWord/_files/templates/clone-delete-block.docx differ