From 309e88288695b1265fd9d4a92740cbea2ae1aa82 Mon Sep 17 00:00:00 2001 From: Yoann Chevalier Date: Mon, 1 Jun 2015 10:54:44 +0200 Subject: [PATCH 1/4] Update cloneBlock and replaceBlock Implements the fonction gave by brad-jones on the issue : https://github.com/PHPOffice/PHPWord/issues/341#issuecomment-63755727 --- src/PhpWord/TemplateProcessor.php | 152 +++++++++++++++++++++++++----- 1 file changed, 128 insertions(+), 24 deletions(-) diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 72446ae744..607b5d6390 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -321,22 +321,25 @@ public function cloneRow($search, $numberOfClones) public function cloneBlock($blockname, $clones = 1, $replace = true) { $xmlBlock = null; - preg_match( - '/(<\?xml.*)(\${' . $blockname . '}<\/w:.*?p>)(.*)()/is', - $this->tempDocumentMainPart, - $matches - ); + + $matches = $this->findBlock($blockname); + + if (isset($matches[1])) + { + $xmlBlock = $matches[1]; - if (isset($matches[3])) { - $xmlBlock = $matches[3]; $cloned = array(); - for ($i = 1; $i <= $clones; $i++) { - $cloned[] = $xmlBlock; + + for ($i = 1; $i <= $clones; $i++) + { + $cloned[] = preg_replace('/\${(.*?)}/','${$1_'.$i.'}', $xmlBlock); } - if ($replace) { - $this->tempDocumentMainPart = str_replace( - $matches[2] . $matches[3] . $matches[4], + if ($replace) + { + $this->temporaryDocumentMainPart = str_replace + ( + $matches[0], implode('', $cloned), $this->tempDocumentMainPart ); @@ -354,18 +357,16 @@ 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 - ); + $matches = $this->findBlock($blockname); + + if (isset($matches[1])) + { + $this->temporaryDocumentMainPart = str_replace + ( + $matches[0], + $replacement, + $this->temporaryDocumentMainPart + ); } } @@ -572,4 +573,107 @@ protected function getSlice($startPosition, $endPosition = 0) return substr($this->tempDocumentMainPart, $startPosition, ($endPosition - $startPosition)); } + + private function findBlock($blockname) + { + // Parse the XML + $xml = new \SimpleXMLElement($this->temporaryDocumentMainPart); + + // Find the starting and ending tags + $startNode = false; $endNode = false; + foreach ($xml->xpath('//w:t') as $node) + { + if (strpos($node, '${'.$blockname.'}') !== false) + { + $startNode = $node; + continue; + } + + if (strpos($node, '${/'.$blockname.'}') !== false) + { + $endNode = $node; + break; + } + } + + // Make sure we found the tags + if ($startNode === false || $endNode === false) + { + return null; + } + + // Find the parent node for the start tag + $node = $startNode; $startNode = null; + while (is_null($startNode)) + { + $node = $node->xpath('..')[0]; + + if ($node->getName() == 'p') + { + $startNode = $node; + } + } + + // Find the parent node for the end tag + $node = $endNode; $endNode = null; + while (is_null($endNode)) + { + $node = $node->xpath('..')[0]; + + if ($node->getName() == 'p') + { + $endNode = $node; + } + } + + /* + * NOTE: Because SimpleXML reduces empty tags to "self-closing" tags. + * We need to replace the original XML with the version of XML as + * SimpleXML sees it. The following example should show the issue + * we are facing. + * + * This is the XML that my document contained orginally. + * + * ```xml + * + * + * + * + * + * + * + * ${CLONEME} + * + * + * ``` + * + * This is the XML that SimpleXML returns from asXml(). + * + * ```xml + * + * + * + * + * + * + * + * ${CLONEME} + * + * + * ``` + */ + + $this->temporaryDocumentMainPart = $xml->asXml(); + + // Find the xml in between the tags + $xmlBlock = null; + preg_match + ( + '/'.preg_quote($startNode->asXml(), '/').'(.*?)'.preg_quote($endNode->asXml(), '/').'/is', + $this->temporaryDocumentMainPart, + $matches + ); + + return $matches; + } } From 8fe99417903dc5c62fe66d0bf320ad770e268b42 Mon Sep 17 00:00:00 2001 From: Christian Zosel Date: Mon, 23 Apr 2018 10:21:57 +0200 Subject: [PATCH 2/4] Fix tests --- src/PhpWord/TemplateProcessor.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 607b5d6390..70afae9d21 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -337,7 +337,7 @@ public function cloneBlock($blockname, $clones = 1, $replace = true) if ($replace) { - $this->temporaryDocumentMainPart = str_replace + $this->tempDocumentMainPart = str_replace ( $matches[0], implode('', $cloned), @@ -361,11 +361,11 @@ public function replaceBlock($blockname, $replacement) if (isset($matches[1])) { - $this->temporaryDocumentMainPart = str_replace + $this->tempDocumentMainPart = str_replace ( $matches[0], $replacement, - $this->temporaryDocumentMainPart + $this->tempDocumentMainPart ); } } @@ -577,7 +577,7 @@ protected function getSlice($startPosition, $endPosition = 0) private function findBlock($blockname) { // Parse the XML - $xml = new \SimpleXMLElement($this->temporaryDocumentMainPart); + $xml = new \SimpleXMLElement($this->tempDocumentMainPart); // Find the starting and ending tags $startNode = false; $endNode = false; @@ -663,14 +663,14 @@ private function findBlock($blockname) * ``` */ - $this->temporaryDocumentMainPart = $xml->asXml(); + $this->tempDocumentMainPart = $xml->asXml(); // Find the xml in between the tags $xmlBlock = null; preg_match ( '/'.preg_quote($startNode->asXml(), '/').'(.*?)'.preg_quote($endNode->asXml(), '/').'/is', - $this->temporaryDocumentMainPart, + $this->tempDocumentMainPart, $matches ); From 232ab1f4089de25de0f9e8b0c9b17b3fc94d6455 Mon Sep 17 00:00:00 2001 From: Christian Zosel Date: Mon, 23 Apr 2018 18:54:48 +0200 Subject: [PATCH 3/4] Support multiple occurrences of the same placeholder --- src/PhpWord/TemplateProcessor.php | 143 +++++++++--------- .../_files/templates/clone-delete-block.docx | Bin 22510 -> 11609 bytes 2 files changed, 71 insertions(+), 72 deletions(-) diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 70afae9d21..54dffc3fdf 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -321,28 +321,26 @@ public function cloneRow($search, $numberOfClones) public function cloneBlock($blockname, $clones = 1, $replace = true) { $xmlBlock = null; - - $matches = $this->findBlock($blockname); - - if (isset($matches[1])) - { - $xmlBlock = $matches[1]; - - $cloned = array(); - - for ($i = 1; $i <= $clones; $i++) - { - $cloned[] = preg_replace('/\${(.*?)}/','${$1_'.$i.'}', $xmlBlock); - } - if ($replace) - { - $this->tempDocumentMainPart = str_replace - ( - $matches[0], - implode('', $cloned), - $this->tempDocumentMainPart - ); + $matches = $this->findBlocks($blockname); + + foreach ($matches as $match) { + if (isset($match[1])) { + $xmlBlock = $match[1]; + + $cloned = array(); + + for ($i = 1; $i <= $clones; $i++) { + $cloned[] = preg_replace('/\${(.*?)}/', '${$1_' . $i . '}', $xmlBlock); + } + + if ($replace) { + $this->tempDocumentMainPart = str_replace( + $match[0], + implode('', $cloned), + $this->tempDocumentMainPart + ); + } } } @@ -357,16 +355,16 @@ public function cloneBlock($blockname, $clones = 1, $replace = true) */ public function replaceBlock($blockname, $replacement) { - $matches = $this->findBlock($blockname); - - if (isset($matches[1])) - { - $this->tempDocumentMainPart = str_replace - ( - $matches[0], + $matches = $this->findBlocks($blockname); + + foreach ($matches as $match) { + if (isset($match[1])) { + $this->tempDocumentMainPart = str_replace( + $match[0], $replacement, $this->tempDocumentMainPart ); + } } } @@ -573,59 +571,62 @@ protected function getSlice($startPosition, $endPosition = 0) return substr($this->tempDocumentMainPart, $startPosition, ($endPosition - $startPosition)); } - - private function findBlock($blockname) + + private function findBlocks($blockname) { // Parse the XML $xml = new \SimpleXMLElement($this->tempDocumentMainPart); - + // Find the starting and ending tags - $startNode = false; $endNode = false; - foreach ($xml->xpath('//w:t') as $node) - { - if (strpos($node, '${'.$blockname.'}') !== false) - { + $startNode = false; + $endNode = false; + $state = 'outside'; + $pairs = array(); + foreach ($xml->xpath('//w:t') as $node) { + if (strpos($node, '${' . $blockname . '}') !== false) { $startNode = $node; + $state = 'inside'; continue; } - - if (strpos($node, '${/'.$blockname.'}') !== false) - { + + if ($state === 'inside' && strpos($node, '${/' . $blockname . '}') !== false) { $endNode = $node; - break; + $pairs[] = array($startNode, $endNode); + $startNode = false; + $endNode = false; + $state = 'outside'; } } - + // Make sure we found the tags - if ($startNode === false || $endNode === false) - { + if (count($pairs) === 0) { return null; } - - // Find the parent node for the start tag - $node = $startNode; $startNode = null; - while (is_null($startNode)) - { - $node = $node->xpath('..')[0]; - - if ($node->getName() == 'p') - { - $startNode = $node; - } + + $result = array(); + foreach ($pairs as $pair) { + $result[] = $this->findEnclosing($pair[0], $pair[1], $xml); } - - // Find the parent node for the end tag - $node = $endNode; $endNode = null; - while (is_null($endNode)) - { + + return $result; + } + + private static function getParentByName($node, $name) + { + while ($node->getName() !== $name) { + // $node = $node->parent(); $node = $node->xpath('..')[0]; - - if ($node->getName() == 'p') - { - $endNode = $node; - } } - + + return $node; + } + + private function findEnclosing($startNode, $endNode, $xml) + { + // Find the parent nodes for startNode & endNode + $startNode = self::getParentByName($startNode, 'p'); + $endNode = self::getParentByName($endNode, 'p'); + /* * NOTE: Because SimpleXML reduces empty tags to "self-closing" tags. * We need to replace the original XML with the version of XML as @@ -662,18 +663,16 @@ private function findBlock($blockname) * * ``` */ - + $this->tempDocumentMainPart = $xml->asXml(); - + // Find the xml in between the tags - $xmlBlock = null; - preg_match - ( - '/'.preg_quote($startNode->asXml(), '/').'(.*?)'.preg_quote($endNode->asXml(), '/').'/is', + preg_match( + '/' . preg_quote($startNode->asXml(), '/') . '(.*?)' . preg_quote($endNode->asXml(), '/') . '/is', $this->tempDocumentMainPart, $matches ); - + return $matches; } } diff --git a/tests/PhpWord/_files/templates/clone-delete-block.docx b/tests/PhpWord/_files/templates/clone-delete-block.docx index 049d5ca415074504f33050b94f3918c06a280720..9a56169d886094d7e67c91d8d0912264772afc81 100644 GIT binary patch literal 11609 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 literal 22510 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 bpN9Y4yI7jikW7? Date: Wed, 25 Apr 2018 16:41:18 +0200 Subject: [PATCH 4/4] Refactor implementation to use DOMDocument instead of SimpleXml --- src/PhpWord/TemplateProcessor.php | 196 ++++++------------ tests/PhpWord/TemplateProcessorTest.php | 17 +- .../_files/templates/clone-delete-block.docx | Bin 11609 -> 10158 bytes 3 files changed, 78 insertions(+), 135 deletions(-) diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 54dffc3fdf..58ba911760 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -318,33 +318,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; - - $matches = $this->findBlocks($blockname); - - foreach ($matches as $match) { - if (isset($match[1])) { - $xmlBlock = $match[1]; - - $cloned = array(); - - for ($i = 1; $i <= $clones; $i++) { - $cloned[] = preg_replace('/\${(.*?)}/', '${$1_' . $i . '}', $xmlBlock); - } - - if ($replace) { - $this->tempDocumentMainPart = str_replace( - $match[0], - 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,17 +341,19 @@ public function cloneBlock($blockname, $clones = 1, $replace = true) */ public function replaceBlock($blockname, $replacement) { - $matches = $this->findBlocks($blockname); - - foreach ($matches as $match) { - if (isset($match[1])) { - $this->tempDocumentMainPart = str_replace( - $match[0], - $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 + ); } /** @@ -375,7 +363,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); + } + } } /** @@ -572,107 +570,45 @@ protected function getSlice($startPosition, $endPosition = 0) return substr($this->tempDocumentMainPart, $startPosition, ($endPosition - $startPosition)); } - private function findBlocks($blockname) + private function findBlocks($blockname, $domDoc, $type = 'complete') { - // Parse the XML - $xml = new \SimpleXMLElement($this->tempDocumentMainPart); - - // Find the starting and ending tags - $startNode = false; - $endNode = false; - $state = 'outside'; - $pairs = array(); - foreach ($xml->xpath('//w:t') as $node) { - if (strpos($node, '${' . $blockname . '}') !== false) { - $startNode = $node; - $state = 'inside'; - continue; - } - - if ($state === 'inside' && strpos($node, '${/' . $blockname . '}') !== false) { - $endNode = $node; - $pairs[] = array($startNode, $endNode); - $startNode = false; - $endNode = false; - $state = 'outside'; - } - } - - // Make sure we found the tags - if (count($pairs) === 0) { - return null; - } - - $result = array(); - foreach ($pairs as $pair) { - $result[] = $this->findEnclosing($pair[0], $pair[1], $xml); + $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 $result; + return $nodeLists; } - private static function getParentByName($node, $name) + private static function getQueryByType($type) { - while ($node->getName() !== $name) { - // $node = $node->parent(); - $node = $node->xpath('..')[0]; + $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]); } - - return $node; } - private function findEnclosing($startNode, $endNode, $xml) - { - // Find the parent nodes for startNode & endNode - $startNode = self::getParentByName($startNode, 'p'); - $endNode = self::getParentByName($endNode, 'p'); - - /* - * NOTE: Because SimpleXML reduces empty tags to "self-closing" tags. - * We need to replace the original XML with the version of XML as - * SimpleXML sees it. The following example should show the issue - * we are facing. - * - * This is the XML that my document contained orginally. - * - * ```xml - * - * - * - * - * - * - * - * ${CLONEME} - * - * - * ``` - * - * This is the XML that SimpleXML returns from asXml(). - * - * ```xml - * - * - * - * - * - * - * - * ${CLONEME} - * - * - * ``` - */ - - $this->tempDocumentMainPart = $xml->asXml(); - - // Find the xml in between the tags - preg_match( - '/' . preg_quote($startNode->asXml(), '/') . '(.*?)' . preg_quote($endNode->asXml(), '/') . '/is', - $this->tempDocumentMainPart, - $matches - ); - - return $matches; - } } diff --git a/tests/PhpWord/TemplateProcessorTest.php b/tests/PhpWord/TemplateProcessorTest.php index c762a6096e..f5fc8b6ab5 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 9a56169d886094d7e67c91d8d0912264772afc81..f74552a7bfd0620322b0ab84b3d172bb29a3ef2c 100644 GIT binary patch delta 6896 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 delta 8421 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 JDbD!Z`#+59v)upy