Forum


Replies: 4   Views: 398
Bulkprocessing replace all variables in the same text tag
Topic closed:
Please note this is an old forum thread. Information in this post may be out-to-date and/or erroneous.
Every phpdocx version includes new features and improvements. Previously unsupported features may have been added to newer releases, or past issues may have been corrected.
We encourage you to download the current phpdocx version and check the Documentation available.

Posted by michael.stubenvoll  · 09-11-2021 - 16:47

I'm trying to use the replaceText function from BulkProcessing.php to replace a bunch of placeholders in a template docx file. This works fine unless there are multiple placeholders in a single <w:t> node in the WordprocessingML data of the docx file. Then only the first found placeholder will be replaced by its value while the other placeholders in the same node can't be replaced.

So for example if I have a docx file with a line such as (using Word 2019):

Hello $firstname$ $lastname$

The corresponding node in the WML data looks something like:

<w:t>Hello $firstname$ $lastname$</w:t>

And after a call to the replaceText method:

$bulk = new BulkProcessing($docx);
$bulk->replaceText([['firstname' => 'John', 'lastname' => 'Doe']])

the final document looks like:

Hello John $lastname$

 

Now if I insert the following lines into the variable2Text function in BulkProcessing.php it works just fine:

// PhpDocx 12 premium
// BulkProcessing.php, line 1025
protected function variable2Text($variables, $dom, $options)
    {
        // ...
        foreach ($variables as $variable => $value) {
            // ...
            foreach ($foundNodes as $node) {
                // ...
                $newNode = $dom->createDocumentFragment();
                @$newNode->appendXML($strNode);
                $dom->importNode($newNode, true);
                $node->parentNode->replaceChild($newNode, $node);

                /* My added code */
                $dom = $this->generateDomDocument($dom->saveXML());
                $xpath = new \DOMXPath($dom);
            }
        }
// ...

I wondered if there is a more elegant way of doing this, and if this is something that could be considered to be added to a future update. Else one might have to call the replaceText method once for each individual placeholder just to make sure it will be replaced. 

Thank you for your time and effort! 

Posted by admin  · 09-11-2021 - 18:28

Hello,

Thanks for the detailed post about your issue. Yes, as your code illustrates, the current version of BulkProcessing (class available only in Premium licenses) doesn't allow changing two placeholders in the same w:t tag. Template methods included in CreateDocxFromTemplate such as replaceVariableByText don't have this limitation.

We have moved your update to the dev team to be added in the stable release of phpdocx (adding $xpath->registerNamespace ):

$dom = $this->generateDomDocument($dom->saveXML());
$xpath = new DOMXPath($dom);
$xpath->registerNamespace('w', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main');

 The code you have added is the best approach to refresh the DOMDocument contents and get other placeholders in the same w:t tag that need to be replaced too.

Regards.

Posted by admin  · 11-11-2021 - 06:34

Hello,

The same code has been added to the current testing branch.

Regards.

Posted by michael.stubenvoll  · 18-11-2021 - 10:28

Thank you very much!

While testing we now found a problem when using a variable multiple times in a document. Because there can be multiple nodes with the same variable the inner foreach loop should not refresh the document. That's because the nodes that are being iterated and modified still belong to the original document. So the added lines should be inserted a few lines below at the end of the outer foreach loop.  

foreach ($variables as $variable => $value) {
            // ...
         foreach ($foundNodes as $node) {
               // ...
         }
         $dom = $this->generateDomDocument($dom->saveXML());
         $xpath = new \DOMXPath($dom);
         $xpath->registerNamespace('w', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main');
}

Is this problem known? And is this a viable fix or is there a different solution to the underlying problem?

Regards.

Posted by admin  · 18-11-2021 - 10:58

Hello,

Thanks for your feedback. The current code of the testing branch of that method in BulkProcessing is the following:

protected function variable2Text($variables, $dom, $options)
{
    if (PHP_VERSION_ID < 80000) {
        $optionEntityLoader = libxml_disable_entity_loader(true);
    }
    $dom = simplexml_load_string($dom->saveXML());
    if (PHP_VERSION_ID < 80000) {
        libxml_disable_entity_loader($optionEntityLoader);
    }
    $dom->registerXPathNamespace('w', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main');

    if (isset($options['firstMatch'])) {
        $firstMatch = $options['firstMatch'];
    } else {
        $firstMatch = false;
    }
    foreach ($variables as $variable => $value) {
        $search = $this->templateSymbolStart . $variable . $this->templateSymbolEnd;
        $query = '//w:t[text()[contains(., "' . $search . '")]]';
        if ($firstMatch) {
            $query = '(' . $query . ')[1]';
        }
        $foundNodes = $dom->xpath($query);
        foreach ($foundNodes as $node) {
            $strNode = (string) $node;
            if ($options['parseLineBreaks']) {
                //  replace line breaks
                $value = str_replace(array('\n\r', '\r\n', '\n', '\r', "\n\r", "\r\n", "\n", "\r"), '__LINEBREAK__', $value);
            }

            $strNode = str_replace($search, $value, $strNode);
            $node[0] = $strNode;
        }
    }

    return $dom;
}

to avoid reloading the DOM content for each variable.

Regards.