Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

setValue Multi-Line Value #268

Closed
martindavis opened this issue Jun 6, 2014 · 20 comments · Fixed by #2522
Closed

setValue Multi-Line Value #268

martindavis opened this issue Jun 6, 2014 · 20 comments · Fixed by #2522

Comments

@martindavis
Copy link

martindavis commented Jun 6, 2014

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.

@ivanlanin
Copy link
Contributor

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.

@ivanlanin ivanlanin added this to the Later milestone Jun 7, 2014
@martindavis
Copy link
Author

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:
https://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!

@ivanlanin
Copy link
Contributor

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

@ghost
Copy link

ghost commented Jun 17, 2014

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. :(

@ghost
Copy link

ghost commented Jul 11, 2014

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.

@lekoala
Copy link

lekoala commented Jul 25, 2014

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

@AndyWaters
Copy link

Both of these lines worked for me.

$replace = preg_replace('\Ru', '/w:t<w:br/><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

@simogeo simogeo mentioned this issue Jun 15, 2015
@kuamatzin
Copy link

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"));

@maninderx
Copy link

@kuamatzin

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

@kuamatzin
Copy link

@maninderx No yet =(

@smandpartners-sipuni
Copy link

@kuamatzin
this worked for me
`

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

`

@maninderx
Copy link

@smandpartners-sipuni Thank you. This works.

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

@mhollander
Copy link

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?

@FBnil
Copy link

FBnil commented Sep 27, 2017

@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?

@mhollander
Copy link

@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.

@FBnil
Copy link

FBnil commented Sep 28, 2017

@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.

@marios88
Copy link

marios88 commented Mar 5, 2018

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

@dennisvandenende
Copy link

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.

@github-actions
Copy link

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
If this is still an issue for you, please try to help by debugging it further and sharing your results.
Thank you for your contributions.

@github-actions github-actions bot added the Stale label Nov 18, 2022
@Progi1984 Progi1984 removed the Stale label Nov 18, 2022
@ThBM
Copy link

ThBM commented Sep 5, 2023

I used the solution from @FBnil which seems to work fine.

Below is the extended class I use :

use PhpOffice\PhpWord\TemplateProcessor;

class VTemplateProcessor extends TemplateProcessor
{
    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);
        }

        return parent::setValueForPart($search, $replace, $documentPartXML, $limit);
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging a pull request may close this issue.