@@ -315,6 +315,46 @@ public function setComplexBlock($search, Element\AbstractElement $complexType):
315
315
$ this ->replaceXmlBlock ($ search , $ xmlWriter ->getData (), 'w:p ' );
316
316
}
317
317
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
+
318
358
/**
319
359
* @param mixed $search
320
360
* @param mixed $replace
@@ -1464,6 +1504,49 @@ protected function splitTextIntoTexts($text)
1464
1504
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 );
1465
1505
}
1466
1506
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
+
1467
1550
/**
1468
1551
* Returns true if string contains a macro that is not in it's own w:r.
1469
1552
*
0 commit comments