Skip to content

Allow to customize macro delimiters in TemplateProcessor #2261

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion docs/templates-processing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Templates processing
====================

You can create an OOXML document template with included search-patterns (macros) which can be replaced by any value you wish. Only single-line values can be replaced.
Macros are defined like this: ``${search-pattern}``.
By default Macros are defined like this: ``${search-pattern}`` but you can define custom macros.
To load a template file, create a new instance of the TemplateProcessor.

.. code-block:: php
Expand Down Expand Up @@ -35,6 +35,30 @@ You can also set multiple values by passing all of them in an array.

$templateProcessor->setValues(array('firstname' => 'John', 'lastname' => 'Doe'));

setMacroOpeningChars
""""""""
You can define a custom opening macro. The following will set ``{#`` as the opening search pattern.

.. code-block:: php

$templateProcessor->setMacroOpeningChars('{#');

setMacroClosingChars
""""""""
You can define a custom closing macro. The following will set ``#}`` as the closing search pattern.

.. code-block:: php

$templateProcessor->setMacroClosingChars('#}');

setMacroChars
""""""""
You can define a custom opening and closing macro at the same time . The following will set the search-pattern like this: ``{#search-pattern#}`` .

.. code-block:: php

$templateProcessor->setMacroChars('{#', '#}');

setImageValue
"""""""""""""
The search-pattern model for images can be like:
Expand Down
57 changes: 48 additions & 9 deletions src/PhpWord/TemplateProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ class TemplateProcessor
*/
protected $tempDocumentNewImages = [];

protected static $macroOpeningChars = '${';

protected static $macroClosingChars = '}';

/**
* @since 0.12.0 Throws CreateTemporaryFileException and CopyFileException instead of Exception
*
Expand Down Expand Up @@ -238,8 +242,8 @@ public function applyXslStyleSheet($xslDomDocument, $xslOptions = [], $xslOption
*/
protected static function ensureMacroCompleted($macro)
{
if (substr($macro, 0, 2) !== '${' && substr($macro, -1) !== '}') {
$macro = '${' . $macro . '}';
if (substr($macro, 0, 2) !== self::$macroOpeningChars && substr($macro, -1) !== self::$macroClosingChars) {
$macro = self::$macroOpeningChars . $macro . self::$macroClosingChars;
}

return $macro;
Expand Down Expand Up @@ -792,8 +796,12 @@ public function cloneBlock($blockname, $clones = 1, $replace = true, $indexVaria
{
$xmlBlock = null;
$matches = [];
$escapedMacroOpeningChars = self::$macroOpeningChars;
$escapedMacroClosingChars = self::$macroClosingChars;
preg_match(
'/(.*((?s)<w:p\b(?:(?!<w:p\b).)*?\${' . $blockname . '}<\/w:.*?p>))(.*)((?s)<w:p\b(?:(?!<w:p\b).)[^$]*?\${\/' . $blockname . '}<\/w:.*?p>)/is',
//'/(.*((?s)<w:p\b(?:(?!<w:p\b).)*?\{{' . $blockname . '}<\/w:.*?p>))(.*)((?s)<w:p\b(?:(?!<w:p\b).)[^$]*?\{{\/' . $blockname . '}<\/w:.*?p>)/is',
'/(.*((?s)<w:p\b(?:(?!<w:p\b).)*?\\' . $escapedMacroOpeningChars . $blockname . $escapedMacroClosingChars . '<\/w:.*?p>))(.*)((?s)<w:p\b(?:(?!<w:p\b).)[^$]*?\\' . $escapedMacroOpeningChars . '\/' . $blockname . $escapedMacroClosingChars . '<\/w:.*?p>)/is',
//'/(.*((?s)<w:p\b(?:(?!<w:p\b).)*?\\'. $escapedMacroOpeningChars . $blockname . '}<\/w:.*?p>))(.*)((?s)<w:p\b(?:(?!<w:p\b).)[^$]*?\\'.$escapedMacroOpeningChars.'\/' . $blockname . '}<\/w:.*?p>)/is',
$this->tempDocumentMainPart,
$matches
);
Expand Down Expand Up @@ -832,8 +840,10 @@ public function cloneBlock($blockname, $clones = 1, $replace = true, $indexVaria
public function replaceBlock($blockname, $replacement): void
{
$matches = [];
$escapedMacroOpeningChars = preg_quote(self::$macroOpeningChars);
$escapedMacroClosingChars = preg_quote(self::$macroClosingChars);
preg_match(
'/(<\?xml.*)(<w:p.*>\${' . $blockname . '}<\/w:.*?p>)(.*)(<w:p.*\${\/' . $blockname . '}<\/w:.*?p>)/is',
'/(<\?xml.*)(<w:p.*>' . $escapedMacroOpeningChars . $blockname . $escapedMacroClosingChars . '<\/w:.*?p>)(.*)(<w:p.*' . $escapedMacroOpeningChars . '\/' . $blockname . $escapedMacroClosingChars . '<\/w:.*?p>)/is',
$this->tempDocumentMainPart,
$matches
);
Expand Down Expand Up @@ -949,8 +959,12 @@ public function saveAs($fileName): void
*/
protected function fixBrokenMacros($documentPart)
{
$brokenMacroOpeningChars = substr(self::$macroOpeningChars, 0, 1);
$endMacroOpeningChars = substr(self::$macroOpeningChars, 1);
$macroClosingChars = self::$macroClosingChars;

return preg_replace_callback(
'/\$(?:\{|[^{$]*\>\{)[^}$]*\}/U',
'/\\' . $brokenMacroOpeningChars . '(?:\\' . $endMacroOpeningChars . '|[^{$]*\>\{)[^' . $macroClosingChars . '$]*\}/U',
function ($match) {
return strip_tags($match[0]);
},
Expand Down Expand Up @@ -989,7 +1003,10 @@ protected function setValueForPart($search, $replace, $documentPartXML, $limit)
protected function getVariablesForPart($documentPartXML)
{
$matches = [];
preg_match_all('/\$\{(.*?)}/i', $documentPartXML, $matches);
$escapedMacroOpeningChars = preg_quote(self::$macroOpeningChars);
$escapedMacroClosingChars = preg_quote(self::$macroClosingChars);

preg_match_all("/$escapedMacroOpeningChars(.*?)$escapedMacroClosingChars/i", $documentPartXML, $matches);

return $matches[1];
}
Expand Down Expand Up @@ -1141,8 +1158,11 @@ protected function getSlice($startPosition, $endPosition = 0)
protected function indexClonedVariables($count, $xmlBlock)
{
$results = [];
$escapedMacroOpeningChars = preg_quote(self::$macroOpeningChars);
$escapedMacroClosingChars = preg_quote(self::$macroClosingChars);

for ($i = 1; $i <= $count; ++$i) {
$results[] = preg_replace('/\$\{([^:]*?)(:.*?)?\}/', '\${\1#' . $i . '\2}', $xmlBlock);
$results[] = preg_replace("/$escapedMacroOpeningChars([^:]*?)(:.*?)?$escapedMacroClosingChars/", self::$macroOpeningChars . '\1#' . $i . '\2' . self::$macroClosingChars, $xmlBlock);
}

return $results;
Expand Down Expand Up @@ -1297,7 +1317,7 @@ protected function splitTextIntoTexts($text)
}

$unformattedText = preg_replace('/>\s+</', '><', $text);
$result = str_replace(['${', '}'], ['</w:t></w:r><w:r>' . $extractedStyle . '<w:t xml:space="preserve">${', '}</w:t></w:r><w:r>' . $extractedStyle . '<w:t xml:space="preserve">'], $unformattedText);
$result = str_replace([self::$macroOpeningChars, self::$macroClosingChars], ['</w:t></w:r><w:r>' . $extractedStyle . '<w:t xml:space="preserve">' . self::$macroOpeningChars, self::$macroClosingChars . '</w:t></w:r><w:r>' . $extractedStyle . '<w:t xml:space="preserve">'], $unformattedText);

return str_replace(['<w:r>' . $extractedStyle . '<w:t xml:space="preserve"></w:t></w:r>', '<w:r><w:t xml:space="preserve"></w:t></w:r>', '<w:t>'], ['', '', '<w:t xml:space="preserve">'], $result);
}
Expand All @@ -1311,6 +1331,25 @@ protected function splitTextIntoTexts($text)
*/
protected function textNeedsSplitting($text)
{
return preg_match('/[^>]\${|}[^<]/i', $text) == 1;
$escapedMacroOpeningChars = preg_quote(self::$macroOpeningChars);
$escapedMacroClosingChars = preg_quote(self::$macroClosingChars);

return 1 === preg_match('/[^>]' . $escapedMacroOpeningChars . '|' . $escapedMacroClosingChars . '[^<]/i', $text);
}

public function setMacroOpeningChars(string $macroOpeningChars): void
{
self::$macroOpeningChars = $macroOpeningChars;
}

public function setMacroClosingChars(string $macroClosingChars): void
{
self::$macroClosingChars = $macroClosingChars;
}

public function setMacroChars(string $macroOpeningChars, string $macroClosingChars): void
{
self::$macroOpeningChars = $macroOpeningChars;
self::$macroClosingChars = $macroClosingChars;
}
}
Loading