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

Conditions in Templates #1394

Open
1 of 2 tasks
DIDoS opened this issue Jun 1, 2018 · 16 comments
Open
1 of 2 tasks

Conditions in Templates #1394

DIDoS opened this issue Jun 1, 2018 · 16 comments

Comments

@DIDoS
Copy link

DIDoS commented Jun 1, 2018

This is:

  • a bug report
  • a feature request

Expected Behavior

Being able to put conditions in a template block rather than in the code.
Current macros only support replacement and repeats (clone, replace, set).
I want to start a discussion about this before I might implement it and send a PR.

I guess it would look something like that:

In the docx "template":

Guests:
${IF GUESTS>3}
Don't forget to make a special arrangement for more than 3 guests!
${/ENDIF}

In the code:

$templateProcessor->setVariable('GUESTS', 4);

This could be extended by several operands (<, >, <=, >=, !=, ==) or even nested logical expressions.

Pro:
More transparent templates (view logic where it belongs)

Con:
More complexity in template processor

@rkorebrits
Copy link

Why do you want to put this in the template file? I think this kind of logic is fairly "edge-case" and to implement this level of conditional logic into the library is a bit hectic and will add to maintenance complexity.

Doing this yourself in the current code is quite easy:

Guests:
${MORE_THAN_3_GUESTS_BLOCK}
Don't forget to make a special arrangement for more than 3 guests!
${/MORE_THAN_3_GUESTS_BLOCK}

PHP

if(count($guests) > 3) {
    $templateProcessor->cloneBlock('MORE_THAN_3_GUESTS_BLOCK', 1);
} else {
    $templateProcessor->deleteBlock('MORE_THAN_3_GUESTS_BLOCK');
}

Not sure how many cases you have where you'd need this kind of conditional logic, but I'm pretty sure it will be easier to manage the code in your controller than trying to implement it into the library and your templates.

@gruessung
Copy link

I support this request.
We create different PHP Sources for documents and create different documents out of one source.
So it's easier to create conditions in the template and not in php code.

@icy2003
Copy link

icy2003 commented Jan 28, 2019

I support this request.
We create different PHP Sources for documents and create different documents out of one source.
So it's easier to create conditions in the template and not in php code.

I agree with @rkorebrits ,and @gruessung can fit your requirement with this way:
set a rule like "greater_books_3", they are separated by "_", the first part means "condition", it can be some values such as "greater", "equals". Second part means your variable, in this case, it means "books", third part means numbers, and that you can code your php with this rule

@DIDoS
Copy link
Author

DIDoS commented Jan 28, 2019

@icy2003 @rkorebrits Ofc I am fully aware how this can be done in the code rather than in the template (as mentioned in my original PR "...rather than in the code."). The reason why this makes sense is the same reason templating languages exist at all: separation of concerns.
You want to get your data together in the code and feed it to a template that executes the "presentation" logic. This way it's easier to debug, maintain and it's separated in modules by function. It also enforces a good programming practice by keeping business logic away from presentation logic.

@troosan
Copy link
Contributor

troosan commented Jan 30, 2019

Better

Guests:
${IF GUESTS>3}
Don't forget to make a special arrangement for more than 3 guests!
${/ENDIF}

or

Guests:
{IF $GUESTS>3}
Don't forget to make a special arrangement for more than 3 guests!
{/ENDIF}

To avoid mixing variables and functions.

@icy2003
Copy link

icy2003 commented Feb 1, 2019

Better

Guests:
${IF GUESTS>3}
Don't forget to make a special arrangement for more than 3 guests!
${/ENDIF}

or

Guests:
{IF $GUESTS>3}
Don't forget to make a special arrangement for more than 3 guests!
{/ENDIF}

To avoid mixing variables and functions.

is this the next version function?

@DIDoS
Copy link
Author

DIDoS commented Feb 1, 2019

Better

Guests:
${IF GUESTS>3}
Don't forget to make a special arrangement for more than 3 guests!
${/ENDIF}

or

Guests:
{IF $GUESTS>3}
Don't forget to make a special arrangement for more than 3 guests!
{/ENDIF}

To avoid mixing variables and functions.

I don't see the difference in the first one? Did you mean the first "or" the second one or the second one instead of the first one.
The reason why I did it that way was because of the way the Template Processor is written right now. Every macro must start with a Dollar sign. The additional logic would be quiet easy to add with a whitelist of key word (functions such as IF) and a check of operands for this function (as regex for example). Also an open/close sanity check (e.g. IF/ENDIF) would have to be added.

Any other issues someone sees with this feature?

@AidanHak
Copy link

AidanHak commented Dec 2, 2019

Why do you want to put this in the template file? I think this kind of logic is fairly "edge-case" and to implement this level of conditional logic into the library is a bit hectic and will add to maintenance complexity.

Doing this yourself in the current code is quite easy:

Guests:
${MORE_THAN_3_GUESTS_BLOCK}
Don't forget to make a special arrangement for more than 3 guests!
${/MORE_THAN_3_GUESTS_BLOCK}

PHP

if(count($guests) > 3) {
    $templateProcessor->cloneBlock('MORE_THAN_3_GUESTS_BLOCK', 1);
} else {
    $templateProcessor->deleteBlock('MORE_THAN_3_GUESTS_BLOCK');
}

Not sure how many cases you have where you'd need this kind of conditional logic, but I'm pretty sure it will be easier to manage the code in your controller than trying to implement it into the library and your templates.

This doesn't seem to work within a numbered list.

@AidanHak
Copy link

AidanHak commented Dec 4, 2019

@DIDoS any update on implementing this feature?

@troosan any idea how I could make your suggestion work with numbered lists?

@vpiskunov
Copy link

Any news on this? Would really solve a lot of hassle in multiple implementations. Python's docx templating lib supports Jinja2 - which also provides in-template-logic handling, so would be good to see it here in PhpWord

@irisda
Copy link

irisda commented Apr 22, 2021

Is any update on this issue?

@g-schmitz
Copy link

Any update? 😀 Maybe I should just start implementing myself I guess...

@yogeshwar-chaudhari-20
Copy link

yogeshwar-chaudhari-20 commented Oct 4, 2021

Hello Community,

@vpiskunov Thanks for your suggestion of using python-docx-template. It sure looks promising for handling the in-template logic.

I was wondering if we have any update on this request though?

I believe the following reasons are worth considering for developing this feature.

  1. This will surely reduce the reliance on developers to get every change pushed to the market.
  2. Any person businessperson with a basic understanding of templating logic can make changes in the text and/or conditions.
  3. If the template document is revising frequently, handling template logic in code will require frequent releases
  4. As pointed by @AidanHak, the suggested alternative implementation does not handle numbered_list.

@AidanHak
Copy link

AidanHak commented Oct 4, 2021

I moved over to OpenTBS around the time I first noticed this issue. Works like a charm for my needs.

@VoronovAlexander
Copy link

VoronovAlexander commented Oct 17, 2021

My template has boolean variables, this variables can be "Yes" or "No" for using in template.
Additional I want add dynamic text by boolean variables

I solve this problem with small function:

private function handleConditions(TemplateProcessor $templateProcessor, array &$variables, ?int $index = null): void
{
    $conditions = array_filter($templateProcessor->getVariables(), fn($variable) => Str::startsWith($variable, 'IF('));
    foreach ($conditions as $condition) {
        $rule = Str::between($condition, 'IF(', ')'); // Str its Laravel's Facade
        [$variable, $trueValue, $falseValue] = explode(';', $rule);
        $variableName = $index ? $variable . "#$index" : $variable;
        if (isset($variables[$variableName])) {
            $variables[$condition] = $variables[$variableName] === 'Yes' ? $trueValue : $falseValue;
        }
    }
}

in template I have row "IF(VARIABLE;True text;else text)"


Example of use:

$variables = [
  'Name' => $this->name,
  'Checked' => $this->isChecked ? 'Yes' : 'No',
];

$this->handleConditions($templateProcessor, $variables);
$templateProcessor->setValues($variables);

And example for cloned blocks:

$templateProcessor->cloneBlock(
   'Step',
  $this->count(),
  true,
  true,
);

foreach ($steps as $index => $step) {
  $index++;
  $stepVariables = [
    'StepName' => $step->name,
    'IsChecked' => $step->isChecked ? 'Yes' : 'No',
  ];
  $this->handleConditions($templateProcessor, $stepVariables, $index);
  $templateProcessor->setValues($stepVariables);
}

@elvis005
Copy link

elvis005 commented May 3, 2024

I also require this functionality. Using the setValue method to assign an empty value doesn't eliminate the element; rather, it merely empties the text, leaving a line in the document, which is less than ideal.
removeBlock does not work in nested block. no choice but to implement:

Create a new class that extends TemplateProcessor and incorporated a new method called removeValue.

    public function removeValue($blockname)
    {
        $xml = simplexml_load_string($this->tempDocumentMainPart);
        $paragraphs = $xml->xpath('//w:p');
        foreach ($paragraphs as $paragraph)
        {
            // Check if paragraph and text node exist
            $t = (string)$paragraph->asXML();
            if (strpos($t, $blockname) !== false) {
                // Convert SimpleXML to DOMDocument
                $dom = dom_import_simplexml($paragraph);
                $dom->parentNode->removeChild($dom);
                break;
            }
        }
        $this->tempDocumentMainPart = $xml->asXML();
    }

hope it helps!

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

No branches or pull requests