3v4l.org

run code in 300+ PHP versions simultaneously
<?php /** * This file is part of PHPWord - A pure PHP library for reading and writing * word processing documents. * * PHPWord is free software distributed under the terms of the GNU Lesser * General Public License version 3 as published by the Free Software Foundation. * * For the full copyright and license information, please read the LICENSE * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @link https://github.com/PHPOffice/PHPWord * @copyright 2010-2014 PHPWord contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace helpers; use PhpOffice\PhpWord\Exception\CopyFileException; use PhpOffice\PhpWord\Exception\CreateTemporaryFileException; use PhpOffice\PhpWord\Exception\Exception; use PhpOffice\PhpWord\Shared\String; use PhpOffice\PhpWord\Shared\ZipArchive; use PhpOffice\PhpWord\TemplateProcessor; use PhpOffice\PhpWord\Settings; class CustomTemplateProcessor { /** * ZipArchive object. * * @var mixed */ private $zipClass; /** * @var string Temporary document filename (with path). */ private $temporaryDocumentFilename; /** * Content of main document part (in XML format) of the temporary document. * * @var string */ private $temporaryDocumentMainPart; /** * Content of headers (in XML format) of the temporary document. * * @var string[] */ private $temporaryDocumentHeaders = array(); /** * Content of footers (in XML format) of the temporary document. * * @var string[] */ private $temporaryDocumentFooters = array(); /** * @since 0.12.0 Throws CreateTemporaryFileException and CopyFileException instead of Exception. * * @param string $documentTemplate The fully qualified template filename. * @throws \PhpOffice\PhpWord\Exception\CreateTemporaryFileException * @throws \PhpOffice\PhpWord\Exception\CopyFileException */ public function __construct($documentTemplate) { // Temporary document filename initialization $this->temporaryDocumentFilename = tempnam(Settings::getTempDir(), 'PhpWord'); if (false === $this->temporaryDocumentFilename) { throw new CreateTemporaryFileException(); } // Template file cloning if (false === copy($documentTemplate, $this->temporaryDocumentFilename)) { throw new CopyFileException($documentTemplate, $this->temporaryDocumentFilename); } // Temporary document content extraction $this->zipClass = new ZipArchive(); $this->zipClass->open($this->temporaryDocumentFilename); $index = 1; while ($this->zipClass->locateName($this->getHeaderName($index)) !== false) { $content = $this->zipClass->getFromName($this->getHeaderName($index)); $this->temporaryDocumentHeaders[$index] = self::cleanParameters($content); $index++; } $index = 1; while ($this->zipClass->locateName($this->getFooterName($index)) !== false) { $content = $this->zipClass->getFromName($this->getFooterName($index)); $this->temporaryDocumentFooters[$index] = self::cleanParameters($content); $index++; } $content = $this->zipClass->getFromName('word/document.xml'); $this->temporaryDocumentMainPart = self::cleanParameters($content); } public static function cleanParameters($content) { $pattern = '|\$\{([^\}]+)\}|U'; $values = []; $replacements = []; preg_match_all($pattern, $content, $matches); foreach ($matches[0] as $value) { $valueCleaned = strip_tags($value); $valueCleaned = preg_replace('/\s+/U', '', $valueCleaned); $values[] = $value; $replacements[] = $valueCleaned; } return str_replace($values, $replacements, $content); } /** * Applies XSL style sheet to template's parts. * * @param \DOMDocument $xslDOMDocument * @param array $xslOptions * @param string $xslOptionsURI * @return void * @throws \PhpOffice\PhpWord\Exception\Exception */ public function applyXslStyleSheet($xslDOMDocument, $xslOptions = array(), $xslOptionsURI = '') { $xsltProcessor = new \XSLTProcessor(); $xsltProcessor->importStylesheet($xslDOMDocument); if (false === $xsltProcessor->setParameter($xslOptionsURI, $xslOptions)) { throw new Exception('Could not set values for the given XSL style sheet parameters.'); } $xmlDOMDocument = new \DOMDocument(); if (false === $xmlDOMDocument->loadXML($this->temporaryDocumentMainPart)) { throw new Exception('Could not load XML from the given template.'); } $xmlTransformed = $xsltProcessor->transformToXml($xmlDOMDocument); if (false === $xmlTransformed) { throw new Exception('Could not transform the given XML document.'); } $this->temporaryDocumentMainPart = $xmlTransformed; } /** * @param mixed $search * @param mixed $replace * @param integer $limit * @return void */ public function setValue($search, $replace, $limit = -1) { if (substr($search, 0, 2) !== '${' && substr($search, -1) !== '}') { $search = '${' . $search . '}'; } if (!String::isUTF8($replace)) { $replace = utf8_encode($replace); } foreach ($this->temporaryDocumentHeaders as $index => $headerXML) { $this->temporaryDocumentHeaders[$index] = $this->setValueForPart($this->temporaryDocumentHeaders[$index], $search, $replace, $limit); } $this->temporaryDocumentMainPart = $this->setValueForPart($this->temporaryDocumentMainPart, $search, $replace, $limit); foreach ($this->temporaryDocumentFooters as $index => $headerXML) { $this->temporaryDocumentFooters[$index] = $this->setValueForPart($this->temporaryDocumentFooters[$index], $search, $replace, $limit); } } /** * Returns array of all variables in template. * * @return string[] */ public function getVariables() { $variables = $this->getVariablesForPart($this->temporaryDocumentMainPart); foreach ($this->temporaryDocumentHeaders as $headerXML) { $variables = array_merge($variables, $this->getVariablesForPart($headerXML)); } foreach ($this->temporaryDocumentFooters as $footerXML) { $variables = array_merge($variables, $this->getVariablesForPart($footerXML)); } return array_unique($variables); } /** * Clone a table row in a template document. * * @param string $search * @param integer $numberOfClones * @return void * @throws \PhpOffice\PhpWord\Exception\Exception */ public function cloneRow($search, $numberOfClones) { if (substr($search, 0, 2) !== '${' && substr($search, -1) !== '}') { $search = '${' . $search . '}'; } $tagPos = strpos($this->temporaryDocumentMainPart, $search); if (!$tagPos) { throw new Exception("Can not clone row, template variable not found or variable contains markup."); } $rowStart = $this->findRowStart($tagPos); $rowEnd = $this->findRowEnd($tagPos); $xmlRow = $this->getSlice($rowStart, $rowEnd); // $cellStart = $this->findCellStart($tagPos); // $cellEnd = $this->findCellEnd($tagPos); // $xmlCell = $this->getSlice($cellStart, $cellEnd); // Check if there's a cell spanning multiple rows. if (preg_match('#<w:vMerge w:val="restart"/>#', $xmlRow)) { // $extraRowStart = $rowEnd; $extraRowEnd = $rowEnd; while (true) { $extraRowStart = $this->findRowStart($extraRowEnd + 1); $extraRowEnd = $this->findRowEnd($extraRowEnd + 1); // If extraRowEnd is lower then 7, there was no next row found. if ($extraRowEnd < 7) { break; } // If tmpXmlRow doesn't contain continue, this row is no longer part of the spanned row. $tmpXmlRow = $this->getSlice($extraRowStart, $extraRowEnd); if (!preg_match('#<w:vMerge/>#', $tmpXmlRow) && !preg_match('#<w:vMerge w:val="continue" />#', $tmpXmlRow)) { break; } // This row was a spanned row, update $rowEnd and search for the next row. $rowEnd = $extraRowEnd; } $xmlRow = $this->getSlice($rowStart, $rowEnd); } $result = $this->getSlice(0, $rowStart); for ($i = 1; $i <= $numberOfClones; $i++) { $result .= preg_replace('/\$\{(.*?)\}/', '\${\\1#' . $i . '}', $xmlRow); } $result .= $this->getSlice($rowEnd); $this->temporaryDocumentMainPart = $result; } /** * Clone a block. * * @param string $blockname * @param integer $clones * @param boolean $replace * @return string|null */ public function cloneBlock($blockname, $clones = 1, $replace = true) { $xmlBlock = null; preg_match( '/(<\?xml.*)(<w:p.*>\${' . $blockname . '}<\/w:.*?p>)(.*)(<w:p.*\${\/' . $blockname . '}<\/w:.*?p>)/is', $this->temporaryDocumentMainPart, $matches ); if (isset($matches[3])) { $xmlBlock = $matches[3]; $cloned = array(); for ($i = 1; $i <= $clones; $i++) { $cloned[] = $xmlBlock; } if ($replace) { $this->temporaryDocumentMainPart = str_replace( $matches[2] . $matches[3] . $matches[4], implode('', $cloned), $this->temporaryDocumentMainPart ); } } return $xmlBlock; } /** * Replace a block. * * @param string $blockname * @param string $replacement * @return void */ public function replaceBlock($blockname, $replacement) { preg_match( '/(<\?xml.*)(<w:p.*>\${' . $blockname . '}<\/w:.*?p>)(.*)(<w:p.*\${\/' . $blockname . '}<\/w:.*?p>)/is', $this->temporaryDocumentMainPart, $matches ); if (isset($matches[3])) { $this->temporaryDocumentMainPart = str_replace( $matches[2] . $matches[3] . $matches[4], $replacement, $this->temporaryDocumentMainPart ); } } /** * Delete a block of text. * * @param string $blockname * @return void */ public function deleteBlock($blockname) { $this->replaceBlock($blockname, ''); } /** * Saves the result document. * * @return string * @throws \PhpOffice\PhpWord\Exception\Exception */ public function save() { foreach ($this->temporaryDocumentHeaders as $index => $headerXML) { $this->zipClass->addFromString($this->getHeaderName($index), $this->temporaryDocumentHeaders[$index]); } $this->zipClass->addFromString('word/document.xml', $this->temporaryDocumentMainPart); foreach ($this->temporaryDocumentFooters as $index => $headerXML) { $this->zipClass->addFromString($this->getFooterName($index), $this->temporaryDocumentFooters[$index]); } // Close zip file if (false === $this->zipClass->close()) { throw new Exception('Could not close zip file.'); } return $this->temporaryDocumentFilename; } /** * Saves the result document to the user defined file. * * @since 0.8.0 * * @param string $fileName * @return void */ public function saveAs($fileName) { $tempFileName = $this->save(); if (file_exists($fileName)) { unlink($fileName); } rename($tempFileName, $fileName); } /** * Find and replace placeholders in the given XML section. * * @param string $documentPartXML * @param string $search * @param string $replace * @param integer $limit * @return string */ protected function setValueForPart($documentPartXML, $search, $replace, $limit) { return str_replace($search, $replace, $documentPartXML, $limit); } /** * Find all variables in $documentPartXML. * * @param string $documentPartXML * @return string[] */ protected function getVariablesForPart($documentPartXML) { preg_match_all('/\$\{(.*?)}/i', $documentPartXML, $matches); return $matches[1]; } /** * Get the name of the footer file for $index. * * @param integer $index * @return string */ private function getFooterName($index) { return sprintf('word/footer%d.xml', $index); } /** * Get the name of the header file for $index. * * @param integer $index * @return string */ private function getHeaderName($index) { return sprintf('word/header%d.xml', $index); } /** * Find the start position of the nearest table row before $offset. * * @param integer $offset * @return integer * @throws \PhpOffice\PhpWord\Exception\Exception */ private function findRowStart($offset) { $rowStart = strrpos($this->temporaryDocumentMainPart, '<w:tr ', ((strlen($this->temporaryDocumentMainPart) - $offset) * -1)); if (!$rowStart) { $rowStart = strrpos($this->temporaryDocumentMainPart, '<w:tr>', ((strlen($this->temporaryDocumentMainPart) - $offset) * -1)); } if (!$rowStart) { throw new Exception('Can not find the start position of the row to clone.'); } return $rowStart; } /** * Find the end position of the nearest table row after $offset. * * @param integer $offset * @return integer */ private function findRowEnd($offset) { return strpos($this->temporaryDocumentMainPart, '</w:tr>', $offset) + 7; } private function findCellStart($offset) { $cellStart = strrpos($this->temporaryDocumentMainPart, '<w:tc ', ((strlen($this->temporaryDocumentMainPart) - $offset) * -1)); if (!$cellStart) { $cellStart = strrpos($this->temporaryDocumentMainPart, '<w:tc>', ((strlen($this->temporaryDocumentMainPart) - $offset) * -1)); } if (!$cellStart) { throw new Exception('Can not find the start position of the cell.'); } return $cellStart; } private function findCellEnd($offset) { return strpos($this->temporaryDocumentMainPart, '</w:tc>', $offset) + 7; } /** * Get a slice of a string. * * @param integer $startPosition * @param integer $endPosition * @return string */ private function getSlice($startPosition, $endPosition = 0) { if (!$endPosition) { $endPosition = strlen($this->temporaryDocumentMainPart); } return substr($this->temporaryDocumentMainPart, $startPosition, ($endPosition - $startPosition)); } }

Here you find the average performance (time & memory) of each version. A grayed out version indicates it didn't complete successfully (based on exit-code).

VersionSystem time (s)User time (s)Memory (MiB)
7.1.00.0070.06022.55
7.0.140.0070.05722.11
7.0.130.0030.05722.14
7.0.120.0070.05721.95
7.0.110.0030.06022.05
7.0.100.0130.05322.17
7.0.90.0070.05722.15
7.0.80.0100.05022.04
7.0.70.0070.05322.13
7.0.60.0070.05722.07
7.0.50.0100.05322.02
7.0.40.0100.05022.14
7.0.30.0100.05322.13
7.0.20.0070.05322.13
7.0.10.0000.06022.15
7.0.00.0130.04722.10
5.6.290.0100.05320.87
5.6.280.0030.06321.17
5.6.270.0030.05720.96
5.6.260.0130.05021.21
5.6.250.0050.05720.78
5.6.240.0100.05020.84
5.6.230.0080.04820.98
5.6.220.0080.06820.95
5.6.210.0100.06020.81
5.6.200.0080.05021.12
5.6.190.0050.05221.13
5.6.180.0100.04721.07
5.6.170.0050.04721.16
5.6.160.0070.05521.10
5.6.150.0030.04721.05
5.6.140.0130.05021.06
5.6.130.0100.04221.10
5.6.120.0070.05821.02
5.6.110.0050.04821.13
5.6.100.0050.04821.10
5.6.90.0050.04820.98
5.6.80.0030.05020.35
5.6.70.0070.05020.45
5.6.60.0070.04720.48
5.6.50.0030.04820.44
5.6.40.0070.04520.38
5.6.30.0070.04720.40
5.6.20.0020.05020.49
5.6.10.0070.04020.52
5.6.00.0050.04520.38
5.5.380.0030.05519.12
5.5.370.0050.04719.05
5.5.360.0080.06519.14
5.5.350.0070.06319.03
5.5.340.0050.05019.55
5.5.330.0030.04819.53
5.5.320.0080.04219.43
5.5.310.0030.05019.59
5.5.300.0050.04819.48
5.5.290.0030.04819.48
5.5.280.0050.04719.55
5.5.270.0100.04719.57
5.5.260.0120.06219.54
5.5.250.0050.04819.35
5.5.240.0070.04318.90
5.5.230.0050.04318.92
5.5.220.0050.04718.88
5.5.210.0030.04318.95
5.5.200.0050.04718.94
5.5.190.0080.04218.93
5.5.180.0070.04518.83
5.5.160.0070.04318.93
5.5.150.0070.04318.97
5.5.140.0030.04718.91
5.5.130.0030.04318.94
5.5.120.0100.03718.91
5.5.110.0080.04218.92
5.5.100.0100.04018.78
5.5.90.0030.04018.70
5.5.80.0050.05718.69
5.5.70.0050.06518.84
5.5.60.0080.05218.86
5.5.50.0050.06518.72
5.5.40.0030.06518.71
5.5.30.0030.06318.85
5.5.20.0020.04718.76
5.5.10.0130.06018.78
5.5.00.0100.05818.64
5.4.450.0050.04319.36
5.4.440.0070.04519.33
5.4.430.0070.04519.51
5.4.420.0030.05319.60
5.4.410.0050.04719.28
5.4.400.0050.04319.22
5.4.390.0000.05219.19
5.4.380.0100.03819.13
5.4.370.0070.04719.07
5.4.360.0080.04219.05
5.4.350.0070.04219.20
5.4.340.0070.04319.23
5.4.320.0030.04319.08
5.4.310.0030.04319.01
5.4.300.0050.04219.03
5.4.290.0050.04819.17
5.4.280.0030.04319.18
5.4.270.0050.05018.92
5.4.260.0050.04219.14
5.4.250.0020.04719.14
5.4.240.0020.04719.14
5.4.230.0080.06319.04
5.4.220.0070.06319.05
5.4.210.0050.06719.12
5.4.200.0070.06519.07
5.4.190.0070.06319.04
5.4.180.0120.06018.97
5.4.170.0130.05818.95
5.4.160.0050.05519.21
5.4.150.0030.07518.94
5.4.140.0050.06216.53
5.4.130.0020.04716.57
5.4.120.0030.04716.42
5.4.110.0030.04316.40
5.4.100.0050.06216.53
5.4.90.0070.05516.60
5.4.80.0080.05816.42
5.4.70.0080.05816.40
5.4.60.0080.05816.46
5.4.50.0030.04516.51
5.4.40.0050.06216.40
5.4.30.0050.04516.38
5.4.20.0030.06016.50
5.4.10.0070.06016.44
5.4.00.0080.05315.94

preferences:
147.9 ms | 1398 KiB | 7 Q