I have the follow string:
{% if a == 3 %}
Yes
{% else %}
{% if b == 2 %}
Maybe
{% else %}
{% if c == 1 %}
Hm... Not
{% else %}
No way!
{% endif %}
{% endif %}
{% endif %}
{% if d == 0 %}
Ok
{% endif %}
If I use this regexp
/\{%\s*if\s*(.*?)\s*%\}(.*)(\{%\s*else\s*%\}(.*))?\{%\s*endif\s*%\}/ism
(in preg_match_all function on PHP), my return is all code above. But when I use
/\{%\s*if\s*(.*?)\s*%\}(.*?)(\{%\s*else\s*%\}(.*?))?\{%\s*endif\s*%\}/ism
(just out of greedy mode), my return ends at {% endif %} of {% if c == 1 %} if (the first endif found).
I want to get the following return:
1.
{% if a == 3 %}
Yes
{% else %}
{% if b == 2 %}
Maybe
{% else %}
{% if c == 1 %}
Hm... Not
{% else %}
No way!
{% endif %}
{% endif %}
{% endif %}
2.
{% if d == 0 %}
Ok
{% endif %}
How should be my regex to archive this return?
PS. I know, if I put some name on ifs and use this name on endif, it could be easily resolved using backreference... But I DO NOT want a palliative answer
Thanks in advance.
You can use this recursive pattern:
$pattern = '~{% if [^%]+%}(?>[^{]+|(?R))*(?>{% else %}(?>[^{]+|(?R))*)?{% endif %}~';
online demo
pattern details:
~
{% if [^%]+%}
(?> # this atomic group describes the content
# between if/else/endif markups:
[^{]+ # - all that is not an opening curly bracket
| # OR
(?R) # - recursion (repeat the whole pattern)
)* # repeat the group zero or more times
(?>
{% else %} # The "else" part works exactly in the same way,
(?>[^{]+|(?R))*
)? # but is optional
{% endif %}
~
I extended this example to solve my own problem, specially to have some random {% ... %} in it: regex101 online demo
Related
I am writing a unit test with Codeception to test a twig template. However, I am unable to find a way to mock the block.richTextFields.all() function. The block.richTextField is passed by the Twig context in this case so my initial thought was to create TwigFunction to mock it, but I can't find a way to have all function inside block.richTextField namespace.
{% for text in block.richTextFields.all() %}
{% if (loop.length < 3 and loop.length > 1 ) or (loop.length == 4) or (loop.length < 3 and loop.index == 2) or (loop.length < 6 and (loop.index == 4 or loop.index == 5)) %}
{% set topRowColWidth = "col-lg-6 margin-top-10" %}
{% elseif loop.length == 1 and loop.last %}
{% set topRowColWidth = "col-lg-12 margin-top-10" %}
{% else %}
{% set topRowColWidth = "col-lg-4" %}
{% endif %}
<div class="{{ topRowColWidth }} dynamic-rich-text">
{% if text.richTextField|length %}
{{text.richTextField}}
{% endif %}
</div>
{% endfor %}
Currently I have two arrays
{% set code = [AMS, EIN, RTM] %}
{% set city = [Amsterdam, Eindhoven, Rotterdam] %}
I would like to check if the value of {{airport}} exists in the first array and if it is code[0] I would like to change {{airport}} into the value of city[0]. Is this possible with Twig?
You can loop over the code array:
{% for c in code %}
{# ... #}
{% endfor %}
Documentation: https://twig.symfony.com/doc/2.x/tags/for.html
Then if the item does match:
{# ... #}
{% if airport == c %}
{# ... #}
{% endif %}
{# ... #}
Documentation: https://twig.symfony.com/doc/2.x/tags/if.html
Replace the variable airport, at the same loop index:
{# ... #}
{% set airport = city[loop.index0] %}
{# ... #}
Documentation: https://twig.symfony.com/doc/2.x/tags/for.html#the-loop-variable
So, in full:
{% for c in code %}
{% if airport == c %}
{% set airport = city[loop. index0] %}
{% endif %}
{% endfor %}
Running fiddle: https://twigfiddle.com/xflfas/2
Out of the scope note: your arrays would be better named cities and codes.
This way, when you loop over them, you end up with meaningful naming
{% set codes = ['AMS', 'EIN', 'RTM'] %}
{% for code in codes %}
{{ code }}
{% endfor %}
{# and #}
{% set cities = ['Amsterdam', 'Eindhoven', 'Rotterdam'] %}
{% for city in cities %}
{{ city }}
{% endfor %}
Use {% if value in array %} to search from the first array and Twig's merge function to replace the value in the second array. See this https://twig.symfony.com/doc/2.x/filters/merge.html
I have the following:
{% if promo.monday_unavailable == 1 %}
not available mondays
{% elseif promo.monday_available == 1%}
available mondays
{% else %}
available mondays from {{promo.monday_start}} until {{promo.monday_end}}
{% endif %}
<br />
{% if promo.tuesday_unavailable == 1 %}
not available tuesdays
{% elseif promo.tuesday_available == 1%}
available tuesdays
{% else %}
available tuesdays from {{promo.tuesday_start}} until {{promo.tuesday_end}}
{% endif %}
<br />
...
That I would like to do for each day of the week.
I'm wondering if there is a way I can simplify the code to read
{% for i in ['monday','tuesday','wednesday','thursday','friday','saturday','sunday'] %}
{% if promo.~i~"_unavailable" == 1 %}
not available mondays
{% elseif promo.~i~"_available" == 1%}
available mondays
{% else %}
available mondays from {{promo.~i~"_start"}} until {{promo.~i~"_end"}}
{% endif %}
<br />
{% endfor %}
With Twig.
Any help would be appreciated. I'm at a loss for what keywords to search for anymore.
I know this is an old thread but twig has support for inline interpolation like:
{{i18n("language_#{langId}")}}
Important that the string to interpolated is with double-quotes.
Found answer by mashing my forehead on the keyboard.
rather than
{% if promo.~i~"_unavailable" == 1 %}
use
{% promo[i~"_unavailable"] == 1 %)
You can try using my code
{% for i in ['monday','tuesday','wednesday','thursday','friday','saturday','sunday'] %}
{% set key = i ~ '__unavailable' %}
{% if (promo[key]) eq something %}
//
{% endif %}
{% endfor}
I try to use a simple loop, in my real code this loop is more complex, and I need to break this iteration like:
{% for post in posts %}
{% if post.id == 10 %}
{# break #}
{% endif %}
<h2>{{ post.heading }}</h2>
{% endfor %}
How can I use behavior of break or continue of PHP control structures in Twig?
This can be nearly done by setting a new variable as a flag to break iterating:
{% set break = false %}
{% for post in posts if not break %}
<h2>{{ post.heading }}</h2>
{% if post.id == 10 %}
{% set break = true %}
{% endif %}
{% endfor %}
An uglier, but working example for continue:
{% set continue = false %}
{% for post in posts %}
{% if post.id == 10 %}
{% set continue = true %}
{% endif %}
{% if not continue %}
<h2>{{ post.heading }}</h2>
{% endif %}
{% if continue %}
{% set continue = false %}
{% endif %}
{% endfor %}
But there is no performance profit, only similar behaviour to the built-in break and continue statements like in flat PHP.
From docs TWIG 2.x docs:
Unlike in PHP, it's not possible to break or continue in a loop.
But still:
You can however filter the sequence during iteration which allows you to skip items.
Example 1 (for huge lists you can filter posts using slice, slice(start, length)):
{% for post in posts|slice(0,10) %}
<h2>{{ post.heading }}</h2>
{% endfor %}
Example 2 works TWIG 3.0 as well:
{% for post in posts if post.id < 10 %}
<h2>{{ post.heading }}</h2>
{% endfor %}
You can even use own TWIG filters for more complexed conditions, like:
{% for post in posts|onlySuperPosts %}
<h2>{{ post.heading }}</h2>
{% endfor %}
A way to be able to use {% break %} or {% continue %} is to write TokenParsers for them.
I did it for the {% break %} token in the code below. You can, without much modifications, do the same thing for the {% continue %}.
AppBundle\Twig\AppExtension.php:
namespace AppBundle\Twig;
class AppExtension extends \Twig_Extension
{
function getTokenParsers() {
return array(
new BreakToken(),
);
}
public function getName()
{
return 'app_extension';
}
}
AppBundle\Twig\BreakToken.php:
namespace AppBundle\Twig;
class BreakToken extends \Twig_TokenParser
{
public function parse(\Twig_Token $token)
{
$stream = $this->parser->getStream();
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
// Trick to check if we are currently in a loop.
$currentForLoop = 0;
for ($i = 1; true; $i++) {
try {
// if we look before the beginning of the stream
// the stream will throw a \Twig_Error_Syntax
$token = $stream->look(-$i);
} catch (\Twig_Error_Syntax $e) {
break;
}
if ($token->test(\Twig_Token::NAME_TYPE, 'for')) {
$currentForLoop++;
} else if ($token->test(\Twig_Token::NAME_TYPE, 'endfor')) {
$currentForLoop--;
}
}
if ($currentForLoop < 1) {
throw new \Twig_Error_Syntax(
'Break tag is only allowed in \'for\' loops.',
$stream->getCurrent()->getLine(),
$stream->getSourceContext()->getName()
);
}
return new BreakNode();
}
public function getTag()
{
return 'break';
}
}
AppBundle\Twig\BreakNode.php:
namespace AppBundle\Twig;
class BreakNode extends \Twig_Node
{
public function compile(\Twig_Compiler $compiler)
{
$compiler
->write("break;\n")
;
}
}
Then you can simply use {% break %} to get out of loops like this:
{% for post in posts %}
{% if post.id == 10 %}
{% break %}
{% endif %}
<h2>{{ post.heading }}</h2>
{% endfor %}
To go even further, you may write token parsers for {% continue X %} and {% break X %} (where X is an integer >= 1) to get out/continue multiple loops like in PHP.
From #NHG comment — works perfectly
{% for post in posts|slice(0,10) %}
I have found a good work-around for continue (love the break sample above).
Here I do not want to list "agency". In PHP I'd "continue" but in twig, I came up with alternative:
{% for basename, perms in permsByBasenames %}
{% if basename == 'agency' %}
{# do nothing #}
{% else %}
<a class="scrollLink" onclick='scrollToSpot("#{{ basename }}")'>{{ basename }}</a>
{% endif %}
{% endfor %}
OR I simply skip it if it doesn't meet my criteria:
{% for tr in time_reports %}
{% if not tr.isApproved %}
.....
{% endif %}
{% endfor %}
I have this Regexp:
/\{%\s([^else|endloop|endif][a-z0-9\.\|_]+)\s%\}/si
I use this regexp in preg_replace.
And this markup:
{# comment %}
{# comment number 2$% %}
{% variable %}
{% array.key1.key2 %}
{% array.key1.key2|escape|bold %}
{% variable|escape %}
{% loop array as item %}
My item is {% item.text %}
{% endloop %}
{% if (something): %}
do something truly
{% else: %}
nothing to do
{% endif; %}
Why this regexp is not working for {% item.text %} but works with other?
I think that I made some mistake here [^else|endloop|endif]
What I'm doing wrong?
I think you may intend:
/\{%\s((?!(else|endloop|endif))[a-z0-9\.\|_]+)\s%\}/si
The square brackets previously containing the else, endloop and endif keywords treats each individual character as an exception. Here they are treated as whole strings.