@ 2015-09-06T20:54:53Z <?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));
}
}
Enable javascript to submit You have javascript disabled. You will not be able to edit any code.
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).
Version System time (s) User time (s) Memory (MiB) 7.1.0 0.007 0.060 22.55 7.0.14 0.007 0.057 22.11 7.0.13 0.003 0.057 22.14 7.0.12 0.007 0.057 21.95 7.0.11 0.003 0.060 22.05 7.0.10 0.013 0.053 22.17 7.0.9 0.007 0.057 22.15 7.0.8 0.010 0.050 22.04 7.0.7 0.007 0.053 22.13 7.0.6 0.007 0.057 22.07 7.0.5 0.010 0.053 22.02 7.0.4 0.010 0.050 22.14 7.0.3 0.010 0.053 22.13 7.0.2 0.007 0.053 22.13 7.0.1 0.000 0.060 22.15 7.0.0 0.013 0.047 22.10 5.6.29 0.010 0.053 20.87 5.6.28 0.003 0.063 21.17 5.6.27 0.003 0.057 20.96 5.6.26 0.013 0.050 21.21 5.6.25 0.005 0.057 20.78 5.6.24 0.010 0.050 20.84 5.6.23 0.008 0.048 20.98 5.6.22 0.008 0.068 20.95 5.6.21 0.010 0.060 20.81 5.6.20 0.008 0.050 21.12 5.6.19 0.005 0.052 21.13 5.6.18 0.010 0.047 21.07 5.6.17 0.005 0.047 21.16 5.6.16 0.007 0.055 21.10 5.6.15 0.003 0.047 21.05 5.6.14 0.013 0.050 21.06 5.6.13 0.010 0.042 21.10 5.6.12 0.007 0.058 21.02 5.6.11 0.005 0.048 21.13 5.6.10 0.005 0.048 21.10 5.6.9 0.005 0.048 20.98 5.6.8 0.003 0.050 20.35 5.6.7 0.007 0.050 20.45 5.6.6 0.007 0.047 20.48 5.6.5 0.003 0.048 20.44 5.6.4 0.007 0.045 20.38 5.6.3 0.007 0.047 20.40 5.6.2 0.002 0.050 20.49 5.6.1 0.007 0.040 20.52 5.6.0 0.005 0.045 20.38 5.5.38 0.003 0.055 19.12 5.5.37 0.005 0.047 19.05 5.5.36 0.008 0.065 19.14 5.5.35 0.007 0.063 19.03 5.5.34 0.005 0.050 19.55 5.5.33 0.003 0.048 19.53 5.5.32 0.008 0.042 19.43 5.5.31 0.003 0.050 19.59 5.5.30 0.005 0.048 19.48 5.5.29 0.003 0.048 19.48 5.5.28 0.005 0.047 19.55 5.5.27 0.010 0.047 19.57 5.5.26 0.012 0.062 19.54 5.5.25 0.005 0.048 19.35 5.5.24 0.007 0.043 18.90 5.5.23 0.005 0.043 18.92 5.5.22 0.005 0.047 18.88 5.5.21 0.003 0.043 18.95 5.5.20 0.005 0.047 18.94 5.5.19 0.008 0.042 18.93 5.5.18 0.007 0.045 18.83 5.5.16 0.007 0.043 18.93 5.5.15 0.007 0.043 18.97 5.5.14 0.003 0.047 18.91 5.5.13 0.003 0.043 18.94 5.5.12 0.010 0.037 18.91 5.5.11 0.008 0.042 18.92 5.5.10 0.010 0.040 18.78 5.5.9 0.003 0.040 18.70 5.5.8 0.005 0.057 18.69 5.5.7 0.005 0.065 18.84 5.5.6 0.008 0.052 18.86 5.5.5 0.005 0.065 18.72 5.5.4 0.003 0.065 18.71 5.5.3 0.003 0.063 18.85 5.5.2 0.002 0.047 18.76 5.5.1 0.013 0.060 18.78 5.5.0 0.010 0.058 18.64 5.4.45 0.005 0.043 19.36 5.4.44 0.007 0.045 19.33 5.4.43 0.007 0.045 19.51 5.4.42 0.003 0.053 19.60 5.4.41 0.005 0.047 19.28 5.4.40 0.005 0.043 19.22 5.4.39 0.000 0.052 19.19 5.4.38 0.010 0.038 19.13 5.4.37 0.007 0.047 19.07 5.4.36 0.008 0.042 19.05 5.4.35 0.007 0.042 19.20 5.4.34 0.007 0.043 19.23 5.4.32 0.003 0.043 19.08 5.4.31 0.003 0.043 19.01 5.4.30 0.005 0.042 19.03 5.4.29 0.005 0.048 19.17 5.4.28 0.003 0.043 19.18 5.4.27 0.005 0.050 18.92 5.4.26 0.005 0.042 19.14 5.4.25 0.002 0.047 19.14 5.4.24 0.002 0.047 19.14 5.4.23 0.008 0.063 19.04 5.4.22 0.007 0.063 19.05 5.4.21 0.005 0.067 19.12 5.4.20 0.007 0.065 19.07 5.4.19 0.007 0.063 19.04 5.4.18 0.012 0.060 18.97 5.4.17 0.013 0.058 18.95 5.4.16 0.005 0.055 19.21 5.4.15 0.003 0.075 18.94 5.4.14 0.005 0.062 16.53 5.4.13 0.002 0.047 16.57 5.4.12 0.003 0.047 16.42 5.4.11 0.003 0.043 16.40 5.4.10 0.005 0.062 16.53 5.4.9 0.007 0.055 16.60 5.4.8 0.008 0.058 16.42 5.4.7 0.008 0.058 16.40 5.4.6 0.008 0.058 16.46 5.4.5 0.003 0.045 16.51 5.4.4 0.005 0.062 16.40 5.4.3 0.005 0.045 16.38 5.4.2 0.003 0.060 16.50 5.4.1 0.007 0.060 16.44 5.4.0 0.008 0.053 15.94
preferences:dark mode live preview
147.9 ms | 1398 KiB | 7 Q