From d68bb3c65931b047e8ab6b86dbb55719d122087a Mon Sep 17 00:00:00 2001 From: FBnil Date: Mon, 25 Sep 2017 16:55:10 +0200 Subject: [PATCH] Fixes Block functions with documents edited by LibreOffice I removed the regexp (although I had a working regexp, it seems PHP7 does not like multiple non-greedy patterns. The need to anchor the query to is very dirty). So I implemented the findBlockStart() and findBlockEnd() that search upto a paragraph change , This has been tested with LibreOffice generated docx (that features ) and real MSOffice documents (that features extra parameters, nb: ). As well as a few new testcases to cover the new functionality. What is new? * Blocks with variables inside now expand like Rows, with #n at the end (and you can get the old behavior back with an extra parameter) * Block functions now return sensible information instead of void * you can throw exceptions if you can not find a block (enabled by an extra parameter) * getBlock() implemented See also TemplateProcessorTest.php for the additional test cases --- src/PhpWord/TemplateProcessor.php | 146 +++++++++++++++++++++++------- 1 file changed, 111 insertions(+), 35 deletions(-) diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 2f6d6258a3..3749f96d41 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -321,60 +321,101 @@ public function cloneRow($search, $numberOfClones) * @param string $blockname * @param integer $clones * @param boolean $replace + * @param boolean $incrementVariables + * @param boolean $throwexception * * @return string|null */ - public function cloneBlock($blockname, $clones = 1, $replace = true) + public function cloneBlock($blockname, $clones = 1, $replace = true, $incrementVariables = true, $throwexception = false) { - $xmlBlock = null; - preg_match( - '/(<\?xml.*)(\${' . $blockname . '}<\/w:.*?p>)(.*)()/is', - $this->tempDocumentMainPart, - $matches - ); + $S_search = '${' . $blockname . '}'; + $E_search = '${/' . $blockname . '}'; + + $S_tagPos = strpos($this->tempDocumentMainPart, $S_search); + $E_tagPos = strpos($this->tempDocumentMainPart, $E_search, $S_tagPos); + if (!$S_tagPos || !$E_tagPos) { + if($throwexception) + throw new Exception("Can not find block '$blockname', template variable not found or variable contains markup."); + else + return null; # Block not found, return null + } + + $S_blockStart = $this->findBlockStart($S_tagPos); + $S_blockEnd = $this->findBlockEnd($S_tagPos); + #$xmlStart = $this->getSlice($S_blockStart, $S_blockEnd); - if (isset($matches[3])) { - $xmlBlock = $matches[3]; - $cloned = array(); + $E_blockStart = $this->findBlockStart($E_tagPos); + $E_blockEnd = $this->findBlockEnd($E_tagPos); + #$xmlEnd = $this->getSlice($E_blockStart, $E_blockEnd); + + $xmlBlock = $this->getSlice($S_blockEnd, $E_blockStart); + + if($replace){ + $result = $this->getSlice(0, $S_blockStart); for ($i = 1; $i <= $clones; $i++) { - $cloned[] = $xmlBlock; + if($incrementVariables) + $result .= preg_replace('/\$\{(.*?)\}/', '\${\\1#' . $i . '}', $xmlBlock); + else + $result .= $xmlBlock; } + $result .= $this->getSlice($E_blockEnd); - if ($replace) { - $this->tempDocumentMainPart = str_replace( - $matches[2] . $matches[3] . $matches[4], - implode('', $cloned), - $this->tempDocumentMainPart - ); - } + $this->tempDocumentMainPart = $result; } return $xmlBlock; } + /** + * Get a block. (first block found) + * + * @param string $blockname + * @param boolean $throwexception + * + * @return string|null + */ + public function getBlock($blockname, $throwexception = false){ + return $this->cloneBlock($blockname, 1, false, $throwexception); + } /** * Replace a block. * * @param string $blockname * @param string $replacement + * @param boolean $throwexception * - * @return void + * @return false on no replacement, true on replacement */ - public function replaceBlock($blockname, $replacement) + public function replaceBlock($blockname, $replacement, $throwexception = false) { - 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 - ); + $S_search = '${' . $blockname . '}'; + $E_search = '${/' . $blockname . '}'; + + $S_tagPos = strpos($this->tempDocumentMainPart, $S_search); + $E_tagPos = strpos($this->tempDocumentMainPart, $E_search, $S_tagPos); + if (!$S_tagPos || !$E_tagPos) { + if($throwexception) + throw new Exception("Can not find block '$blockname', template variable not found or variable contains markup."); + else return false; } + + $S_blockStart = $this->findBlockStart($S_tagPos); + $S_blockEnd = $this->findBlockEnd($S_tagPos); + #$xmlStart = $this->getSlice($S_blockStart, $S_blockEnd); + + $E_blockStart = $this->findBlockStart($E_tagPos); + $E_blockEnd = $this->findBlockEnd($E_tagPos); + #$xmlEnd = $this->getSlice($E_blockStart, $E_blockEnd); + + $xmlBlock = $this->getSlice($S_blockEnd, $E_blockStart); + + $result = $this->getSlice(0, $S_blockStart); + $result .= $replacement; + $result .= $this->getSlice($E_blockEnd); + + $this->tempDocumentMainPart = $result; + + return true; } /** @@ -382,11 +423,11 @@ public function replaceBlock($blockname, $replacement) * * @param string $blockname * - * @return void + * @return true on block found and deleted, false on block not found. */ public function deleteBlock($blockname) { - $this->replaceBlock($blockname, ''); + return $this->replaceBlock($blockname, '', false); } /** @@ -557,7 +598,30 @@ protected function findRowStart($offset) } /** - * Find the end position of the nearest table row after $offset. + * Find the start position of the nearest paragraph () before $offset. + * + * @param integer $offset + * + * @return integer + * + * @throws \PhpOffice\PhpWord\Exception\Exception + */ + protected function findBlockStart($offset) + { + $blockStart = strrpos($this->tempDocumentMainPart, 'tempDocumentMainPart) - $offset) * -1)); + + if (!$blockStart) { + $blockStart = strrpos($this->tempDocumentMainPart, '', ((strlen($this->tempDocumentMainPart) - $offset) * -1)); + } + if (!$blockStart) { + throw new Exception('Can not find the start position of the row to clone.'); + } + + return $blockStart; + } + + /** + * Find the end position of the nearest paragraph () after $offset. * * @param integer $offset * @@ -568,6 +632,18 @@ protected function findRowEnd($offset) return strpos($this->tempDocumentMainPart, '', $offset) + 7; } + /** + * Find the end position of the nearest table row after $offset. + * + * @param integer $offset + * + * @return integer + */ + protected function findBlockEnd($offset) + { + return strpos($this->tempDocumentMainPart, '', $offset) + 6; + } + /** * Get a slice of a string. *