Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PhpWord doesn't replace text

Tags:

php

docx

phpword

I have a docx file and I need to replace some text. This is done inside codeigniter framework; here is the code:

$this->load->library('word');       
$template = $this->word->loadTemplate($_SERVER['DOCUMENT_ROOT'].'/doc/assets/doc3.docx');
$template->setValue('replacename', 'new');
$template->save($_SERVER['DOCUMENT_ROOT'].'/doc/assets/helloWorld.docx');

When I open the new file I still get "replacename" instad of "new". "replacename" is formatted with Verdana font, 9pt font size (no underline or bold). Why it doesn't work? Removing ${ } from setValue function (and from doc file) it works

like image 788
pindol Avatar asked May 28 '13 14:05

pindol


5 Answers

It turns out that when you add text to a word file, it sometimes creates extra tags in the underlying xml file. So (pseudo code) ${NAME} can become <tag1>${</tag1><tag2>NAME</tag2><tag3>}</tag3>

Because of this, phpword can't find your needle.

I'm using a mac with microsoft word and i can just cut all the contents of the word file and paste them again.

Word will than recreate the underlying xml file in which ${NAME} is 1 tag instead of 3.

like image 98
Marc Avatar answered Nov 10 '22 23:11

Marc


here i try and working well,

setValue function does not work because it include the format of word after generate to web. so the text you might replace not found.

the solution is just type your ${replaceKey} to notepad and copy it to your_template.docx it working for me.

notepad make all word format not included to the template so the function setValue can found it as expected.

Hope it Helping you guys.

like image 36
wiqi Avatar answered Nov 10 '22 21:11

wiqi


I found the solution in http://phpword.codeplex.com/workitem/57... the problem seems to be when word generates his own code.

for the solution, just rename extension your template to zip, check in a file named 'document.xml' into 'word' folder, and search your code value... it should be like this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 wp14">
    <w:body>
        <w:p w:rsidR="00AC49DC" w:rsidRDefault="00243DD6">
            <w:r>
                <w:t>${test1}</w:t>
            </w:r>
        </w:p>
        <w:p w:rsidR="00243DD6" w:rsidRDefault="00243DD6">
            <w:r>
                <w:t>${test2}</w:t>
            </w:r>
        </w:p>
        <w:p w:rsidR="00243DD6" w:rsidRDefault="00243DD6">
            <w:r>
                <w:t>${test3}</w:t>
            </w:r>
        </w:p>
        <w:p w:rsidR="00243DD6" w:rsidRDefault="00243DD6">
            <w:r>
                <w:t>${test4}</w:t>
            </w:r>
        </w:p>
        <w:p w:rsidR="00243DD6" w:rsidRDefault="00243DD6">
            <w:r>
                <w:t>${</w:t>
            </w:r>
            <w:bookmarkStart w:id="0" w:name="_GoBack"/>
            <w:bookmarkEnd w:id="0"/>
            <w:r>
                <w:t>test5}</w:t>
            </w:r>
        </w:p>
        <w:sectPr w:rsidR="00243DD6" w:rsidSect="00C107D0">
            <w:pgSz w:w="12240" w:h="15840" w:code="1"/>
            <w:pgMar w:top="907" w:right="907" w:bottom="907" w:left="907" w:header="720" w:footer="720" w:gutter="0"/>
            <w:cols w:space="720"/>
            <w:docGrid w:linePitch="272"/>
        </w:sectPr>
    </w:body>
</w:document>
like image 29
alec Avatar answered Nov 10 '22 22:11

alec


Just add 2 function to the file "TemplateProcessor.php"

public  function setValueAdvanced($search_replace)
    {
        foreach ($this->tempDocumentHeaders as $index => $headerXML) {
            $this->tempDocumentHeaders[$index] = $this->setValueForPartAdvanced($this->tempDocumentHeaders[$index], $search_replace);
        }

        $this->tempDocumentMainPart = $this->setValueForPartAdvanced($this->tempDocumentMainPart, $search_replace);

        foreach ($this->tempDocumentFooters as $index => $headerXML) {
            $this->tempDocumentFooters[$index] = $this->setValueForPartAdvanced($this->tempDocumentFooters[$index], $search_replace);
        }
    }
protected  function setValueForPartAdvanced($documentPartXML, $search_replace)
    {
        $pattern = '/<w:t>(.*?)<\/w:t>/';
        $rplStringBeginOffcetsStack = array();
        $rplStringEndOffcetsStack = array();
        $rplCleanedStrings = array();
        $stringsToClean = array();
        preg_match_all($pattern, $documentPartXML, $words, PREG_OFFSET_CAPTURE);

        $bux_founded = false;
        $searching_started = false;
        foreach($words[1] as $key_of_words => $word)
        {
            $exploded_chars = str_split($word[0]);
            foreach($exploded_chars as $key_of_chars => $char)
            {
                if ($bux_founded)
                {
                    if ($searching_started)
                    {
                        if ($char == "}")
                        {
                            $bux_founded = false;
                            $searching_started = false;
                            array_push($rplStringEndOffcetsStack, ($word[1]+mb_strlen($word[0])+6));
                        }
                    }
                    else
                    {
                        if ($char == "{")
                        {
                            $searching_started = true;
                        }
                        else
                        {
                            $bux_founded = false;
                            array_pop($rplStringBeginOffcetsStack);
                        }
                    }
                }
                else
                {
                    if ($char == "$")
                    {
                        $bux_founded = true;
                        array_push($rplStringBeginOffcetsStack, $word[1]-5);
                    }
                }
            }
        }
        for($index=0; $index<count($rplStringEndOffcetsStack); $index++)
        {
            $string_to_clean = substr($documentPartXML, $rplStringBeginOffcetsStack[$index], ($rplStringEndOffcetsStack[$index]-$rplStringBeginOffcetsStack[$index]));
            array_push($stringsToClean, $string_to_clean);
            preg_match_all($pattern, $string_to_clean, $words_to_concat);
            $cleaned_string = implode("", $words_to_concat[1]);
            $cleaned_string = preg_replace('/[${}]+/', '', $cleaned_string);
            array_push($rplCleanedStrings, $cleaned_string);
        }
        for ($index=0; $index<count($rplCleanedStrings); $index++)
        {
            foreach($search_replace as $key_search => $replace)
            {
                if ($rplCleanedStrings[$index] == $key_search)
                {
                    $documentPartXML = str_replace($stringsToClean[$index], "<w:t>".$replace."</w:t>", $documentPartXML);
                    break;
                }
            }
        }
        return $documentPartXML;
    }

How to use: Use an array as the only parameter of function "setValueAdvanced", where "key" - a word, that we want to replace, and "value" - phrase we want to paste instead. Important: Inside a MS Word file use "${word_to_replace}" to "mark" a word that we want to replace, but key of an array should be "word_to_replace", without "${}"

Example code:

require_once 'PhpWord/Autoloader.php';

use PhpOffice\PhpWord\Autoloader;
use PhpOffice\PhpWord\Settings;

define('CLI', (PHP_SAPI == 'cli') ? true : false);
define('EOL', CLI ? PHP_EOL : '<br />');
define('SCRIPT_FILENAME', basename($_SERVER['SCRIPT_FILENAME'], '.php'));
define('IS_INDEX', SCRIPT_FILENAME == 'index');

Autoloader::register();
Settings::loadConfig();

$templateProcessor = new \PhpOffice\PhpWord\TemplateProcessor('your_file_name.docx');
$search_replace_array = array(
    'msword_hello'=>'Hello', #inside a MS Word file ${msword_hello} will change to Hello
    'msword_world'=>'World' #${msword_world} will change to World
);
$templateProcessor->setValueAdvanced($search_replace_array);


$templateProcessor->saveAs('your_file_name_changed.docx');
like image 4
andrew_jackson Avatar answered Nov 10 '22 21:11

andrew_jackson


You can use new Template class with new method setValue()

<w:p w:rsidR="00243DD6" w:rsidRDefault="00243DD6">
        <w:r>
            <w:t>{</w:t>
        </w:r>
        <w:bookmarkStart w:id="0" w:name="_GoBack"/>
        <w:bookmarkEnd w:id="0"/>
        <w:r>
            <w:t>test5}</w:t>
        </w:r>
    </w:p>

New method can replace it setValue('test5', 'MyValue');

Download new class: https://github.com/Arisse/PHPWord_CloneRow

like image 2
Korimizu Avatar answered Nov 10 '22 23:11

Korimizu