Phpword: setValue Multi-Line Value

Created on 6 Jun 2014  路  18Comments  路  Source: PHPOffice/PHPWord

Thank you for the amazing library! This has made my life much easier!

I am using your library to automatically fill out a packing slip. In order to make it easier to maintain and change, I am loading a Word Document as a Template, and then using setValue() to update variables. The template I am using has a lot of formatting and I would really prefer to avoid doing it in code if at all possible. It would also make it practically impossible for non-coders to update the template.

The issue I'm having is that I need to insert a multi-line replacement in several places (address, order notes, etc). Are there any plans to implement this in the future? Or am I missing another way to solve this problem, such as XSLT?

I could extend the Template Class with a setValueForPartMulti() and if I understand Word's Format correctly, my xml would need to look like the following:

 <w:r>
  <w:t>Hello</w:t>
  <w:br/>
  <w:t>world</wt>
</w:r>

Could I simply have my custom function preg_replace('\r\n', '<w:br/>', $value)? Would any issues arise from doing this?


Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.

Consulting Request Template Processor

Most helpful comment

Hi @ivanlanin I have solved it! In Template.php, line 340 add:

$replace = preg_replace('~\R~u', '</w:t><w:br/><w:t>', $replace);

Interestingly enough, Word 2010 and Word 2003 do not have a problem if I just use '<w:br/>', but OpenOffice 3.4 does not recognize the newlines. If I use '</w:t><w:br/></wt>', Word 2010, Word 2003 and OpenOffice 3.4 recognize the newline character.

I think the decision from here is whether we use '~\R~u' or '~(*BSR_ANYCRLF)\R~'. The first is interpreted as (?>\r\n|\n|\r|\f|\x0b|\x85|\x{2028}|\x{2029}) while the second is interpreted as (?>\r\n|\n|\r). The first may be better because of line 338 where utf8_encode() is used. Adding ~u to the expression adds support for UTF-8 and ASCII. Source:
http://stackoverflow.com/questions/18988536/php-regex-how-to-match-r-and-n-without-using-r-n

Let me know which expression is better and I'll gladly fork to add the feature!

All 18 comments

You're welcome, @martindavis. I'm glad that PHPWord can helps you.

I haven't used template processor too much because, until now, I use PHPWord to create documents from scratch. I will start using it. In the meantime, I hope others can help answer your question.

Hi @ivanlanin I have solved it! In Template.php, line 340 add:

$replace = preg_replace('~\R~u', '</w:t><w:br/><w:t>', $replace);

Interestingly enough, Word 2010 and Word 2003 do not have a problem if I just use '<w:br/>', but OpenOffice 3.4 does not recognize the newlines. If I use '</w:t><w:br/></wt>', Word 2010, Word 2003 and OpenOffice 3.4 recognize the newline character.

I think the decision from here is whether we use '~\R~u' or '~(*BSR_ANYCRLF)\R~'. The first is interpreted as (?>\r\n|\n|\r|\f|\x0b|\x85|\x{2028}|\x{2029}) while the second is interpreted as (?>\r\n|\n|\r). The first may be better because of line 338 where utf8_encode() is used. Adding ~u to the expression adds support for UTF-8 and ASCII. Source:
http://stackoverflow.com/questions/18988536/php-regex-how-to-match-r-and-n-without-using-r-n

Let me know which expression is better and I'll gladly fork to add the feature!

Thanks @martindavis. @RomanSyroeshko, perhaps you have some view about this? Thanks.

Hi, guys.

The problem is the similar to the one described here. Briefly, I can say the following.

Hardcoding XML tags in such way is a bad design. We need to let TemplateProcessor load documents as a bunch of objects (it deals with XML for now), but we have no volunteers who can suggest good implementation. I would like to implement this, but have no time because of job search. :(

Hi,
@ivanlanin is right.

ON Function : protected function setValueForPart($documentPartXML, $search, $replace, $limit)

AFTER : $replace = htmlspecialchars($replace);
$replace = preg_replace('~(*BSR_ANYCRLF)\R~', '< w : b r / >', $replace);

Will do the trick.

I have used @martindavis solution and it works like a charm. A real improvement over current situation in my opinion.

Both of these lines worked for me.

$replace = preg_replace('~\R~u', '/w:t', $replace);
$replace = preg_replace('~(*BSR_ANYCRLF)\R~', '< w : b r / >', $replace);

However, I had to add into TemplateProcessor.php at line 352 because of a later version.

Thanks Andy

Not working for me!

Any suggestions?

protected function setValueForPart($documentPartXML, $search, $replace, $limit)
{
if (substr($search, 0, 2) !== '${' && substr($search, -1) !== '}') {
$search = '${' . $search . '}';
}

    if (!String::isUTF8($replace)) {
        $replace = utf8_encode($replace);
    }

    //Lines added
    $replace = preg_replace('~\R~u', '/w:t', $replace);
    $replace = preg_replace('~(*BSR_ANYCRLF)\R~', '< w : b r / >', $replace);

    // Note: we can't use the same function for both cases here, because of performance considerations.
    if (self::MAXIMUM_REPLACEMENTS_DEFAULT === $limit) {
        return str_replace($search, $replace, $documentPartXML);
    } else {
        $regExpDelim = '/';
        $escapedSearch = preg_quote($search, $regExpDelim);
        return preg_replace("{$regExpDelim}{$escapedSearch}{$regExpDelim}u", $replace, $documentPartXML, $limit);
    }
}

And i'm using it: $document->setValue("hello", "hello \n world"));

@kuamatzin

Did you get it to work? Where did you put the code?

@maninderx No yet =(

@kuamatzin
this worked for me
`

        //Lines added
        $replace = preg_replace('~\R~u', '</w:t><w:br/><w:t>', $replace);

`

@smandpartners-sipuni Thank you. This works.

//Lines added $replace = preg_replace('~\R~u', '</w:t><w:br/><w:t>', $replace);

I also found that this line works in setValueForPart():

$replace = preg_replace('~\R~u', '</w:t><w:br/><w:t>', $replace);

I put the line at the beginning of the function in v0.13.

Is there a reason that this hasn't been imported into the actual codebase?

@mhollander In version 0.13.0, the setValue('label',"value\nwith\nmultiline") works for me (linux PHP7 0.13.0 Libreoffice). Can you verify or explain to me what does not work?

@FBnil Thank you for looking into this and picking off some of the issues with this project.

I'm using 0.13.0 with PHP Version 7.0.22-0ubuntu0.16.04.1

I just reverted my version of PHPWord to not have my addtion and this still doesn't work. I am doing a setValue(setValue('label',"value\nwith\nmultiline") and I get "value\nwith\nmultiline" in the actual substitution. If I have literal newlines in my text string, I have the same problem.

@mhollander Ok, you are right, it is not PHPWord that add the w:br, it is LibreOffice that automatically fixes it after opening it. I'll do some tests if MSOffice doesn't mind the absence of revision id's (https://blogs.msdn.microsoft.com/brian_jones/2006/12/11/whats-up-with-all-those-rsids/).
I also checked shift-enter (a way to add multiline, in say, a bullet point list without going into a new bulletpoint, need to test all at the office. New testcase for the multiline are written and pass.

    protected function setValueForPart($search, $replace, $documentPartXML, $limit)
    {
        // Shift-Enter
        if (is_array($replace)) {
            foreach ($replace as &$item) {
                $item = preg_replace('~\R~u', '</w:t><w:br/><w:t>', $item);
            }
        } else {
            $replace = preg_replace('~\R~u', '</w:t><w:br/><w:t>', $replace);
        }

        // Note: we can't use the same function for both cases here, because of performance considerations.
        if (self::MAXIMUM_REPLACEMENTS_DEFAULT === $limit) {
            return str_replace($search, $replace, $documentPartXML);
        } else {
            $regExpEscaper = new RegExp();
            return preg_replace($regExpEscaper->escape($search), $replace, $documentPartXML, $limit);
        }
    }

I use arrays a lot, say:

$a = [ 
  'name' => 'Joe',
  'age' => '42'
];
$templateProcessor->setValue(array_keys($a), array_values($a));

Addendum:

Do not confuse a newline inside a text, which converts to a new paragraph. You need to handle that with cloneBlock(), and then setValue() to the myline#n:

${myblock}
${myline}
${/myblock}

with a shift-Enter, which allows, for example:

* this
  item
* another item

Even though visually, in most cases, they look the same.

Had exactly the same problem
Libreoffice: displays the files correctly probably by fixing the wrong code on the fly
Ms Office: everything is on a single line

Solutions provided here do the trick, see attached file for a simple extension class that applies the fix

Phptemplate_withnewline.php.txt

It seems the solution of @marios88 was not implemented yet, but the error still exists. After adding the code from the Phptemplate_withnewline.php.txt it works like a charm for me.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ahmednawazbutt picture ahmednawazbutt  路  3Comments

carlosvr90 picture carlosvr90  路  4Comments

Ryuzakix3 picture Ryuzakix3  路  6Comments

phuwin picture phuwin  路  3Comments

jt6a74 picture jt6a74  路  5Comments