Skip to content

Commit 795f2ec

Browse files
committed
Replace macros by splitting existing text runs/paragraphs instead of replacing them
1 parent 8b891bb commit 795f2ec

File tree

1 file changed

+83
-0
lines changed

1 file changed

+83
-0
lines changed

src/PhpWord/TemplateProcessor.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,46 @@ public function setComplexBlock($search, Element\AbstractElement $complexType):
315315
$this->replaceXmlBlock($search, $xmlWriter->getData(), 'w:p');
316316
}
317317

318+
/**
319+
* Replaces a search string (macro) with a set of rendered elements, splitting
320+
* surrounding texts, text runs or paragraphs before and after the macro,
321+
* depending on the types of elements to insert.
322+
*
323+
* @param \PhpOffice\PhpWord\Element\AbstractElement[] $elements
324+
*/
325+
public function setElementsValue(string $search, array $elements): void
326+
{
327+
$elementsData = '';
328+
$hasParagraphs = false;
329+
foreach ($elements as $element) {
330+
$elementName = substr(
331+
get_class($element),
332+
(int) strrpos(get_class($element), '\\') + 1
333+
);
334+
$objectClass = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Element\\' . $elementName;
335+
336+
// For inline elements, do not create a new paragraph.
337+
$withParagraph = Writer\Word2007\Element\Text::class !== $objectClass;
338+
$hasParagraphs = $hasParagraphs || $withParagraph;
339+
340+
$xmlWriter = new XMLWriter();
341+
/** @var \PhpOffice\PhpWord\Writer\Word2007\Element\AbstractElement $elementWriter */
342+
$elementWriter = new $objectClass($xmlWriter, $element, !$withParagraph);
343+
$elementWriter->write();
344+
$elementsData .= $xmlWriter->getData();
345+
}
346+
$blockType = $hasParagraphs ? 'w:p' : 'w:r';
347+
$where = $this->findContainingXmlBlockForMacro($search, $blockType);
348+
if (is_array($where)) {
349+
/** @phpstan-var array{start: int, end: int} $where */
350+
$block = $this->getSlice($where['start'], $where['end']);
351+
$parts = $hasParagraphs ? $this->splitParagraphIntoParagraphs($block) : $this->splitTextIntoTexts($block);
352+
$this->replaceXmlBlock($search, $parts, $blockType);
353+
$search = static::ensureMacroCompleted($search);
354+
$this->replaceXmlBlock($search, $elementsData, $blockType);
355+
}
356+
}
357+
318358
/**
319359
* @param mixed $search
320360
* @param mixed $replace
@@ -1464,6 +1504,49 @@ protected function splitTextIntoTexts($text)
14641504
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);
14651505
}
14661506

1507+
/**
1508+
* Splits a w:p into a list of w:p where each ${macro} is in a separate w:p.
1509+
*/
1510+
public function splitParagraphIntoParagraphs(string $paragraph): string
1511+
{
1512+
$matches = [];
1513+
if (1 === preg_match('/(<w:pPr.*<\/w:pPr>)/i', $paragraph, $matches)) {
1514+
$extractedStyle = $matches[0];
1515+
} else {
1516+
$extractedStyle = '';
1517+
}
1518+
if (null === $paragraph = preg_replace('/>\s+</', '><', $paragraph)) {
1519+
throw new Exception('Error processing PhpWord document.');
1520+
}
1521+
$result = str_replace(
1522+
[
1523+
'${',
1524+
'}',
1525+
],
1526+
[
1527+
'</w:t></w:r></w:p><w:p>' . $extractedStyle . '<w:r><w:t xml:space="preserve">${',
1528+
'}</w:t></w:r></w:p><w:p>' . $extractedStyle . '<w:r><w:t xml:space="preserve">',
1529+
],
1530+
$paragraph
1531+
);
1532+
1533+
// Remove empty paragraphs that might have been created before/after the
1534+
// macro.
1535+
$result = str_replace(
1536+
[
1537+
'<w:p>' . $extractedStyle . '<w:r><w:t xml:space="preserve"></w:t></w:r></w:p>',
1538+
'<w:p><w:r><w:t xml:space="preserve"></w:t></w:r></w:p>',
1539+
],
1540+
[
1541+
'',
1542+
'',
1543+
],
1544+
$result
1545+
);
1546+
1547+
return $result;
1548+
}
1549+
14671550
/**
14681551
* Returns true if string contains a macro that is not in it's own w:r.
14691552
*

0 commit comments

Comments
 (0)