@@ -318,33 +318,19 @@ public function cloneRow($search, $numberOfClones)
318318 *
319319 * @return string|null
320320 */
321- public function cloneBlock ($ blockname , $ clones = 1 , $ replace = true )
321+ public function cloneBlock ($ blockname , $ clones = 1 )
322322 {
323- $ xmlBlock = null ;
324-
325- $ matches = $ this ->findBlocks ($ blockname );
326-
327- foreach ($ matches as $ match ) {
328- if (isset ($ match [1 ])) {
329- $ xmlBlock = $ match [1 ];
330-
331- $ cloned = array ();
332-
333- for ($ i = 1 ; $ i <= $ clones ; $ i ++) {
334- $ cloned [] = preg_replace ('/\${(.*?)}/ ' , '${$1_ ' . $ i . '} ' , $ xmlBlock );
335- }
336-
337- if ($ replace ) {
338- $ this ->tempDocumentMainPart = str_replace (
339- $ match [0 ],
340- implode ('' , $ cloned ),
341- $ this ->tempDocumentMainPart
342- );
323+ $ dom = \DOMDocument::loadXML ($ this ->tempDocumentMainPart );
324+ $ nodeSets = $ this ->findBlocks ($ blockname , $ dom , 'inner ' );
325+ foreach ($ nodeSets as $ nodeSet ) {
326+ for ($ i = 1 ; $ i < $ clones ; $ i ++ ) {
327+ foreach ($ nodeSet as $ node ) {
328+ $ nodeSet [0 ]->parentNode ->insertBefore ($ node ->cloneNode (true ), $ nodeSet [0 ]);
343329 }
344330 }
345331 }
346-
347- return $ xmlBlock ;
332+ $ this -> deleteNodeSets ( $ this -> findBlocks ( $ blockname , $ dom , ' outer ' ));
333+ $ this -> tempDocumentMainPart = $ dom -> saveXML () ;
348334 }
349335
350336 /**
@@ -355,17 +341,19 @@ public function cloneBlock($blockname, $clones = 1, $replace = true)
355341 */
356342 public function replaceBlock ($ blockname , $ replacement )
357343 {
358- $ matches = $ this ->findBlocks ($ blockname );
359-
360- foreach ($ matches as $ match ) {
361- if (isset ($ match [1 ])) {
362- $ this ->tempDocumentMainPart = str_replace (
363- $ match [0 ],
364- $ replacement ,
365- $ this ->tempDocumentMainPart
366- );
367- }
368- }
344+ $ dom = \DOMDocument::loadXML ($ this ->tempDocumentMainPart );
345+ $ nodeSets = $ this ->findBlocks ($ blockname , $ dom );
346+ foreach ($ nodeSets as $ nodeSet ) {
347+ $ newNode = $ dom ->createElement ('t:marker ' );
348+ $ nodeSet [0 ]->parentNode ->insertBefore ($ newNode , $ nodeSet [0 ]);
349+ }
350+ $ this ->deleteNodeSets ($ nodeSets );
351+ $ xml = $ dom ->saveXML ();
352+ $ this ->tempDocumentMainPart = str_replace (
353+ '<t:marker/> ' ,
354+ $ replacement ,
355+ $ xml
356+ );
369357 }
370358
371359 /**
@@ -375,7 +363,17 @@ public function replaceBlock($blockname, $replacement)
375363 */
376364 public function deleteBlock ($ blockname )
377365 {
378- $ this ->replaceBlock ($ blockname , '' );
366+ $ dom = \DOMDocument::loadXML ($ this ->tempDocumentMainPart );
367+ $ this ->deleteNodeSets ($ this ->findBlocks ($ blockname , $ dom ));
368+ $ this ->tempDocumentMainPart = $ dom ->saveXML ();
369+ }
370+
371+ private function deleteNodeSets ($ nodeSets ) {
372+ foreach ($ nodeSets as $ nodeSet ) {
373+ foreach ($ nodeSet as $ node ) {
374+ $ node ->parentNode ->removeChild ($ node );
375+ }
376+ }
379377 }
380378
381379 /**
@@ -572,107 +570,45 @@ protected function getSlice($startPosition, $endPosition = 0)
572570 return substr ($ this ->tempDocumentMainPart , $ startPosition , ($ endPosition - $ startPosition ));
573571 }
574572
575- private function findBlocks ($ blockname )
573+ private function findBlocks ($ blockname, $ domDoc , $ type = ' complete ' )
576574 {
577- // Parse the XML
578- $ xml = new \SimpleXMLElement ($ this ->tempDocumentMainPart );
579-
580- // Find the starting and ending tags
581- $ startNode = false ;
582- $ endNode = false ;
583- $ state = 'outside ' ;
584- $ pairs = array ();
585- foreach ($ xml ->xpath ('//w:t ' ) as $ node ) {
586- if (strpos ($ node , '${ ' . $ blockname . '} ' ) !== false ) {
587- $ startNode = $ node ;
588- $ state = 'inside ' ;
589- continue ;
590- }
591-
592- if ($ state === 'inside ' && strpos ($ node , '${/ ' . $ blockname . '} ' ) !== false ) {
593- $ endNode = $ node ;
594- $ pairs [] = array ($ startNode , $ endNode );
595- $ startNode = false ;
596- $ endNode = false ;
597- $ state = 'outside ' ;
598- }
599- }
600-
601- // Make sure we found the tags
602- if (count ($ pairs ) === 0 ) {
603- return null ;
604- }
605-
606- $ result = array ();
607- foreach ($ pairs as $ pair ) {
608- $ result [] = $ this ->findEnclosing ($ pair [0 ], $ pair [1 ], $ xml );
575+ $ domXpath = new \DOMXpath ($ domDoc );
576+ $ max = $ domXpath ->query ('//w:p[contains(., "${ ' .$ blockname .'}")] ' )->length ;
577+ $ nodeLists = array ();
578+ for ($ i = 1 ; $ i <= $ max ; $ i ++) {
579+ $ query = join (' | ' , self ::getQueryByType ($ type ));
580+
581+ $ data = array (
582+ 'BLOCKNAME ' => $ blockname ,
583+ 'INDEX ' => $ i
584+ );
585+ $ findFromTo = str_replace (array_keys ($ data ), array_values ($ data ), $ query );
586+ $ nodelist = $ domXpath ->query ($ findFromTo );
587+ $ nodeLists [] = $ nodelist ;
609588 }
610-
611- return $ result ;
589+ return $ nodeLists ;
612590 }
613591
614- private static function getParentByName ( $ node , $ name )
592+ private static function getQueryByType ( $ type )
615593 {
616- while ($ node ->getName () !== $ name ) {
617- // $node = $node->parent();
618- $ node = $ node ->xpath ('.. ' )[0 ];
594+ $ parts = array (
595+ '//w:p[contains(., "${BLOCKNAME}")][INDEX] ' ,
596+ // https://stackoverflow.com/questions/3428104/selecting-siblings-between-two-nodes-using-xpath
597+ '//w:p[contains(., "${BLOCKNAME}")][INDEX]/
598+ following-sibling::w:p[contains(., "${/BLOCKNAME}")][1]/
599+ preceding-sibling::w:p[
600+ preceding-sibling::w:p[contains(., "${BLOCKNAME}")][INDEX]
601+ ] ' ,
602+ '//w:p[contains(., "${/BLOCKNAME}")][INDEX] '
603+ );
604+ switch ($ type ) {
605+ case 'complete ' :
606+ return $ parts ;
607+ case 'inner ' :
608+ return array ($ parts [1 ]);
609+ case 'outer ' :
610+ return array ($ parts [0 ], $ parts [2 ]);
619611 }
620-
621- return $ node ;
622612 }
623613
624- private function findEnclosing ($ startNode , $ endNode , $ xml )
625- {
626- // Find the parent <w:p> nodes for startNode & endNode
627- $ startNode = self ::getParentByName ($ startNode , 'p ' );
628- $ endNode = self ::getParentByName ($ endNode , 'p ' );
629-
630- /*
631- * NOTE: Because SimpleXML reduces empty tags to "self-closing" tags.
632- * We need to replace the original XML with the version of XML as
633- * SimpleXML sees it. The following example should show the issue
634- * we are facing.
635- *
636- * This is the XML that my document contained orginally.
637- *
638- * ```xml
639- * <w:p>
640- * <w:pPr>
641- * <w:pStyle w:val="TextBody"/>
642- * <w:rPr></w:rPr>
643- * </w:pPr>
644- * <w:r>
645- * <w:rPr></w:rPr>
646- * <w:t>${CLONEME}</w:t>
647- * </w:r>
648- * </w:p>
649- * ```
650- *
651- * This is the XML that SimpleXML returns from asXml().
652- *
653- * ```xml
654- * <w:p>
655- * <w:pPr>
656- * <w:pStyle w:val="TextBody"/>
657- * <w:rPr/>
658- * </w:pPr>
659- * <w:r>
660- * <w:rPr/>
661- * <w:t>${CLONEME}</w:t>
662- * </w:r>
663- * </w:p>
664- * ```
665- */
666-
667- $ this ->tempDocumentMainPart = $ xml ->asXml ();
668-
669- // Find the xml in between the tags
670- preg_match (
671- '/ ' . preg_quote ($ startNode ->asXml (), '/ ' ) . '(.*?) ' . preg_quote ($ endNode ->asXml (), '/ ' ) . '/is ' ,
672- $ this ->tempDocumentMainPart ,
673- $ matches
674- );
675-
676- return $ matches ;
677- }
678614}
0 commit comments