Tinebase_Export_Doc - replace block replace implementation
authorPaul Mehrer <p.mehrer@metaways.de>
Wed, 9 Aug 2017 12:40:28 +0000 (14:40 +0200)
committerPaul Mehrer <p.mehrer@metaways.de>
Wed, 9 Aug 2017 13:53:49 +0000 (15:53 +0200)
PhpOffice implemenation of regex is sloppy... and regex ops on xml...
what can you say? replaced regex with str(r)pos and substr

Change-Id: Ib9b014858c4ee83fdbf2b4a38aca2dc296edbb29
Reviewed-on: http://gerrit.tine20.com/customers/5432
Tested-by: Jenkins CI (http://ci.tine20.com/) <tine20-jenkins@metaways.de>
Reviewed-by: Paul Mehrer <p.mehrer@metaways.de>
Tested-by: Paul Mehrer <p.mehrer@metaways.de>
tine20/Tinebase/Export/Doc.php
tine20/Tinebase/Export/Richtext/TemplateProcessor.php

index c1eee31..c5890c9 100644 (file)
@@ -532,15 +532,14 @@ class Tinebase_Export_Doc extends Tinebase_Export_Abstract implements Tinebase_R
     {
         foreach ($this->_getTemplateVariables() as $placeholder) {
             if (strpos($placeholder, 'DATASOURCE') === 0 && preg_match('/DATASOURCE_(.*)/', $placeholder, $match)) {
-                if (null === ($dataSource = $this->_docTemplate->cloneBlock($placeholder, 1, false))) {
-                    throw new Tinebase_Exception_UnexpectedValue('clone block for ' . $placeholder . ' failed');
+                if (null === ($dataSource = $this->_docTemplate->findBlock($placeholder, '${' . $match[0] . '}'))) {
+                    throw new Tinebase_Exception_UnexpectedValue('find&replace block for ' . $placeholder . ' failed');
                 }
 
                 $dataSource = '<?xml' . $dataSource;
                 $processor = new Tinebase_Export_Richtext_TemplateProcessor($dataSource, true,
                     Tinebase_Export_Richtext_TemplateProcessor::TYPE_DATASOURCE);
                 $this->_dataSources[$match[1]] = $processor;
-                $this->_docTemplate->replaceBlock($match[0], '${' . $match[0] . '}');
 
                 $this->_findAndReplaceGroup($processor);
             }
@@ -554,8 +553,7 @@ class Tinebase_Export_Doc extends Tinebase_Export_Abstract implements Tinebase_R
     {
         $config = array();
 
-        if (null !== ($group = $_templateProcessor->cloneBlock('GROUP_BLOCK', 1, false))) {
-            $_templateProcessor->replaceBlock('GROUP_BLOCK', '${GROUP_BLOCK}');
+        if (null !== ($group = $_templateProcessor->findBlock('GROUP_BLOCK', '${GROUP_BLOCK}'))) {
             $groupProcessor = new Tinebase_Export_Richtext_TemplateProcessor('<?xml' . $group, true,
                 Tinebase_Export_Richtext_TemplateProcessor::TYPE_GROUP, $_templateProcessor);
 
@@ -564,16 +562,13 @@ class Tinebase_Export_Doc extends Tinebase_Export_Abstract implements Tinebase_R
             } else {
                 $config['record'] = $recordProcessor;
             }
-            if (null !== ($groupHeader = $_templateProcessor->cloneBlock('GROUP_HEADER', 1, false))) {
-                $_templateProcessor->replaceBlock('GROUP_HEADER', '');
+            if (null !== ($groupHeader = $_templateProcessor->findBlock('GROUP_HEADER', ''))) {
                 $config['groupHeader'] = $groupHeader;
             }
-            if (null !== ($groupFooter = $_templateProcessor->cloneBlock('GROUP_FOOTER', 1, false))) {
-                $_templateProcessor->replaceBlock('GROUP_FOOTER', '');
+            if (null !== ($groupFooter = $_templateProcessor->findBlock('GROUP_FOOTER', ''))) {
                 $config['groupFooter'] = $groupFooter;
             }
-            if (null !== ($groupSeparator = $_templateProcessor->cloneBlock('GROUP_SEPARATOR', 1, false))) {
-                $_templateProcessor->replaceBlock('GROUP_SEPARATOR', '');
+            if (null !== ($groupSeparator = $_templateProcessor->findBlock('GROUP_SEPARATOR', ''))) {
                 $config['groupSeparator'] = $groupSeparator;
             }
             if (isset($config['recordRow']) && (isset($config['groupHeader']) || isset($config['groupFooter']) ||
@@ -601,8 +596,7 @@ class Tinebase_Export_Doc extends Tinebase_Export_Abstract implements Tinebase_R
      */
     protected function _findAndReplaceRecord(Tinebase_Export_Richtext_TemplateProcessor $_templateProcessor)
     {
-        if (null !== ($recordBlock = $_templateProcessor->cloneBlock('RECORD_BLOCK', 1, false))) {
-            $_templateProcessor->replaceBlock('RECORD_BLOCK', '${RECORD_BLOCK}');
+        if (null !== ($recordBlock = $_templateProcessor->findBlock('RECORD_BLOCK', '${RECORD_BLOCK}'))) {
             $processor = new Tinebase_Export_Richtext_TemplateProcessor('<?xml' . $recordBlock, true,
                 Tinebase_Export_Richtext_TemplateProcessor::TYPE_RECORD, $_templateProcessor);
             $this->_findAndReplaceSubGroup($processor);
@@ -611,18 +605,15 @@ class Tinebase_Export_Doc extends Tinebase_Export_Abstract implements Tinebase_R
             );
             $processor->setMainPart('<?xml');
 
-            if (null !== ($recordHeader = $_templateProcessor->cloneBlock('RECORD_HEADER', 1, false))) {
-                $_templateProcessor->replaceBlock('RECORD_HEADER', '');
+            if (null !== ($recordHeader = $_templateProcessor->findBlock('RECORD_HEADER', ''))) {
                 $config['header'] = $recordHeader;
             }
 
-            if (null !== ($recordFooter = $_templateProcessor->cloneBlock('RECORD_FOOTER', 1, false))) {
-                $_templateProcessor->replaceBlock('RECORD_FOOTER', '');
+            if (null !== ($recordFooter = $_templateProcessor->findBlock('RECORD_FOOTER', ''))) {
                 $config['footer'] = $recordFooter;
             }
 
-            if (null !== ($recordSeparator = $_templateProcessor->cloneBlock('RECORD_SEPARATOR', 1, false))) {
-                $_templateProcessor->replaceBlock('RECORD_SEPARATOR', '');
+            if (null !== ($recordSeparator = $_templateProcessor->findBlock('RECORD_SEPARATOR', ''))) {
                 $config['separator'] = $recordSeparator;
             }
             $processor->setConfig(array_merge($processor->getConfig(), $config));
@@ -694,9 +685,8 @@ class Tinebase_Export_Doc extends Tinebase_Export_Abstract implements Tinebase_R
 
             $config = array();
             if (null !== $foundGroup) {
-                if (null !== ($group = $_templateProcessor->cloneBlock($foundGroup, 1, false))) {
+                if (null !== ($group = $_templateProcessor->findBlock($foundGroup, '${R' . $foundGroup . '}'))) {
                     list(,$propertyName) = explode('_', $foundGroup);
-                    $_templateProcessor->replaceBlock($foundGroup, '${R' . $foundGroup . '}');
                     $groupProcessor = new Tinebase_Export_Richtext_TemplateProcessor('<?xml' . $group, true,
                         Tinebase_Export_Richtext_TemplateProcessor::TYPE_SUBGROUP, $_templateProcessor);
 
@@ -705,16 +695,13 @@ class Tinebase_Export_Doc extends Tinebase_Export_Abstract implements Tinebase_R
                     } else {
                         $config['record'] = $recordProcessor;
                     }
-                    if (null !== ($groupHeader = $_templateProcessor->cloneBlock('SUBG_HEADER_' . $propertyName, 1, false))) {
-                        $_templateProcessor->replaceBlock('SUBG_HEADER_' . $propertyName, '');
+                    if (null !== ($groupHeader = $_templateProcessor->findBlock('SUBG_HEADER_' . $propertyName, ''))) {
                         $config['groupHeader'] = $groupHeader;
                     }
-                    if (null !== ($groupFooter = $_templateProcessor->cloneBlock('SUBG_FOOTER_' . $propertyName, 1, false))) {
-                        $_templateProcessor->replaceBlock('SUBG_FOOTER_' . $propertyName, '');
+                    if (null !== ($groupFooter = $_templateProcessor->findBlock('SUBG_FOOTER_' . $propertyName, ''))) {
                         $config['groupFooter'] = $groupFooter;
                     }
-                    if (null !== ($groupSeparator = $_templateProcessor->cloneBlock('SUBG_SEPARATOR_' . $propertyName, 1, false))) {
-                        $_templateProcessor->replaceBlock('SUBG_SEPARATOR_' . $propertyName, '');
+                    if (null !== ($groupSeparator = $_templateProcessor->findBlock('SUBG_SEPARATOR_' . $propertyName, ''))) {
                         $config['groupSeparator'] = $groupSeparator;
                     }
                     $config['groupXml'] = $this->_cutXml($groupProcessor->getMainPart());
@@ -722,7 +709,7 @@ class Tinebase_Export_Doc extends Tinebase_Export_Abstract implements Tinebase_R
                     $groupProcessor->setConfig($config);
                     $parentConfig['subgroups'][$propertyName] = $groupProcessor;
                 } else {
-                    throw new Tinebase_Exception('clone block failed after subgroup was found: ' . $foundGroup);
+                    throw new Tinebase_Exception('find&replace block failed after subgroup was found: ' . $foundGroup);
                 }
             } elseif (null !== $foundRecord) {
                 if (null === ($recordProcessor = $this->_findAndReplaceSubRecord($_templateProcessor))) {
@@ -763,9 +750,8 @@ class Tinebase_Export_Doc extends Tinebase_Export_Abstract implements Tinebase_R
             return null;
         }
 
-        if (null !== ($recordBlock = $_templateProcessor->cloneBlock($foundRecord, 1, false))) {
+        if (null !== ($recordBlock = $_templateProcessor->findBlock($foundRecord, '${R' . $foundRecord . '}'))) {
             list(,$propertyName) = explode('_', $foundRecord);
-            $_templateProcessor->replaceBlock($foundRecord, '${R' . $foundRecord . '}');
             $processor = new Tinebase_Export_Richtext_TemplateProcessor('<?xml', true,
                 Tinebase_Export_Richtext_TemplateProcessor::TYPE_SUBRECORD, $_templateProcessor);
             $config = array(
@@ -773,24 +759,21 @@ class Tinebase_Export_Doc extends Tinebase_Export_Abstract implements Tinebase_R
                 'name'          => $foundRecord,
             );
 
-            if (null !== ($recordHeader = $_templateProcessor->cloneBlock('SUBR_HEADER_' . $propertyName, 1, false))) {
-                $_templateProcessor->replaceBlock('SUBR_HEADER_' . $propertyName, '');
+            if (null !== ($recordHeader = $_templateProcessor->findBlock('SUBR_HEADER_' . $propertyName, ''))) {
                 $config['header'] = $recordHeader;
             }
 
-            if (null !== ($recordFooter = $_templateProcessor->cloneBlock('SUBR_FOOTER_' . $propertyName, 1, false))) {
-                $_templateProcessor->replaceBlock('SUBR_FOOTER_' . $propertyName, '');
+            if (null !== ($recordFooter = $_templateProcessor->findBlock('SUBR_FOOTER_' . $propertyName, ''))) {
                 $config['footer'] = $recordFooter;
             }
 
-            if (null !== ($recordSeparator = $_templateProcessor->cloneBlock('SUBR_SEPARATOR_' . $propertyName, 1, false))) {
-                $_templateProcessor->replaceBlock('SUBR_SEPARATOR_' . $propertyName, '');
+            if (null !== ($recordSeparator = $_templateProcessor->findBlock('SUBR_SEPARATOR_' . $propertyName, ''))) {
                 $config['separator'] = $recordSeparator;
             }
             $processor->setConfig($config);
             return $processor;
         } else {
-            throw new Tinebase_Exception('clone block failed after subrecord was found: ' . $foundRecord);
+            throw new Tinebase_Exception('find&replace block failed after subrecord was found: ' . $foundRecord);
         }
     }
 
index 9de40a5..6fdd3c7 100644 (file)
@@ -235,80 +235,114 @@ class Tinebase_Export_Richtext_TemplateProcessor extends \PhpOffice\PhpWord\Temp
     }
 
     /**
-     * @param string $row
-     * @param int $num
-     * @param string $where
+     * Find the start position of the nearest table row before $offset.
      *
-    public function insertRow($row, $num, $where)
+     * @param integer $offset
+     * @return integer
+     */
+    protected function findRowStart($offset)
+    {
+        return $this->findTag('<w:tr', $offset, false);
+    }
+
+    /**
+     * @param string $tag
+     * @param int $offset
+     * @param bool $forward
+     * @return int
+     * @throws Tinebase_Exception_NotFound
+     */
+    protected function findTag($tag, $offset, $forward = true)
     {
-        $row = preg_replace('/\$\{(.*?)\}/', '\${\\1#' . $num . '}', $row);
+        if (true === $forward) {
+            $strpos = 'strpos';
+            $minmax = 'min';
+        } else {
+            $strpos = 'strrpos';
+            $minmax = 'max';
+            $offset = (strlen($this->tempDocumentMainPart) - $offset) * -1;
+        }
+
+        $result1 = $strpos($this->tempDocumentMainPart, $tag . ' ', $offset);
+        $result2 = $strpos($this->tempDocumentMainPart, $tag . '>', $offset);
+
+        if (false === $result1) {
+            if (false === $result2) {
+                throw new Tinebase_Exception_NotFound('Can not find the start position of the tag: ' . $tag);
+            }
+            return (int)$result2;
+        }
+        if (false === $result2) {
+            return (int)$result1;
+        }
 
-        $this->setValue($where, $row . $where);
-    }*/
+        return (int)$minmax($result1, $result2);
+    }
 
     /**
-     * Clone a block.
+     * Find a block (optionally replace it)
      *
-     * @param string $blockname
-     * @param integer $clones
-     * @param boolean $replace
+     * @param string $blockName
+     * @param string $replacement
      *
      * @return string|null
      */
-    public function cloneBlock($blockname, $clones = 1, $replace = true)
+    public function findBlock($blockName, $replacement = null)
     {
-        $xmlBlock = null;
-        preg_match(
-            '/(<\?xml.*)(<w:p(\s+[^>]*)?>.*?\${' . $blockname . '}.*?<\/w:p>)(.*)(<w:p(\s+[^>]*)?>.*?\${\/' . $blockname . '}.*?<\/w:p>)/is',
-            $this->tempDocumentMainPart,
-            $matches
-        );
-
-        if (isset($matches[4])) {
-            $xmlBlock = $matches[4];
-            $cloned = array();
-            for ($i = 1; $i <= $clones; $i++) {
-                $cloned[] = $xmlBlock;
-            }
+        $openBlock = '${' . $blockName . '}';
+        if (false === ($openBlockPos = strpos($this->tempDocumentMainPart, $openBlock))) {
+            return null;
+        }
+        $openBlockPos = $this->findTag('<w:p', $openBlockPos, false);
+        if (false === ($endOpenBlockPos = strpos($this->tempDocumentMainPart, '</w:p>', $openBlockPos))) {
+            return null;
+        }
+        $endOpenBlockPos += 6;
 
-            if ($replace) {
-                if (($pos = strrpos($matches[2], '<w:p>')) !== 0) {
-                    $matches[2] = substr($matches[2], $pos);
-                }
-                $this->tempDocumentMainPart = str_replace(
-                    $matches[2] . $matches[4] . $matches[5],
-                    implode('', $cloned),
-                    $this->tempDocumentMainPart
-                );
-            }
+        $closeBlock = '${/' . $blockName . '}';
+        if (false === ($closeBlockPos = strpos($this->tempDocumentMainPart, $closeBlock, $endOpenBlockPos))) {
+            return null;
+        }
+        $closeBlockPos = $this->findTag('<w:p', $closeBlockPos, false);
+        if (false === ($endCloseBlockPos = strpos($this->tempDocumentMainPart, '</w:p>', $closeBlockPos))) {
+            return null;
+        }
+        $endCloseBlockPos += 6;
+
+        $xmlBlock = substr($this->tempDocumentMainPart, $endOpenBlockPos, $closeBlockPos - $endOpenBlockPos);
+
+        if (null !== $replacement) {
+            $this->tempDocumentMainPart = substr($this->tempDocumentMainPart, 0, $openBlockPos) . $replacement .
+                substr($this->tempDocumentMainPart, $endCloseBlockPos);
         }
 
         return $xmlBlock;
     }
 
     /**
+     * Clone a block.
+     *
+     * @param string $blockname
+     * @param integer $clones
+     * @param boolean $replace
+     * @return string|null
+     * @throws Tinebase_Exception_NotImplemented
+     */
+    public function cloneBlock($blockname, $clones = 1, $replace = true)
+    {
+        throw new Tinebase_Exception_NotImplemented('do not use this function! ' . __METHOD__);
+    }
+
+    /**
      * Replace a block.
      *
      * @param string $blockname
      * @param string $replacement
-     *
-     * @return void
+     * @throws Tinebase_Exception_NotImplemented
      */
     public function replaceBlock($blockname, $replacement)
     {
-        preg_match(
-            '/(<\?xml.*)(<w:p(\s+[^>]*)?>.*?\${' . $blockname . '}.*?<\/w:p>)(.*)(<w:p(\s+[^>]*)?>.*?\${\/' . $blockname . '}.*?<\/w:p>)/is',
-            $this->tempDocumentMainPart,
-            $matches
-        );
-
-        if (isset($matches[4])) {
-            $this->tempDocumentMainPart = str_replace(
-                $matches[2] . $matches[4] . $matches[5],
-                $replacement,
-                $this->tempDocumentMainPart
-            );
-        }
+        throw new Tinebase_Exception_NotImplemented('do not use this function! ' . __METHOD__);
     }
 
     /**