Match block inside block with pcre - php

In this regex101 demo, I'm trying to get the contents of each block:
{% block works %}
This works
{% endblock %}
{% block main_block %}
{% block sub_block %}
Does not work
{% endblock %} #ends here
This is not covered
{% endblock %}
It works right if a block has no blocks inside, but, in the second block (main_block) it fails to match all of its content because one block is found inside.
I need a regex pattern that captures both main_block and sub_block blocks, without ending on the first {% endblock %} tag.
Right now, my expression looks like this: \{\%\s*block\s?([a-z0-9\_]*?)\s?\%\}(.*?)(?>(?:{\%\s*block\s?([a-z0-9\_]*?)\s?\%\}|(?R))*\{\%\s?endblock\s?\1?\s?\%\}\is
EDIT: my question was marked as duplicate, but I don’t think it has to do with that problem, mine is in a different scenario where blocks could be inside of blocks indefinitely.

Regex Used: '/(\ *){%\sblock.*\s%}((?:.*|\n)*?)\1{%\sendblock.*\s%}/'
For this to work, it does assume that the blocks are properly formatted, but if they are formatted correctly, this will work in most cases (it uses the spaces before each block to determine whether the tags match or not).
Try this out for size:
<?php
function getBlockText($blockText)
{
$regex = '/(\ *){%\sblock.*\s%}((?:.*|\n)*?)\1{%\sendblock.*\s%}/';
$recursedMatches= [];
preg_match_all($regex, $blockText, $matches);
for ($i = 0; $i < count($matches[2]); $i++) {
if(preg_match($regex, $matches[2][$i])){
array_push($recursedMatches, getBlockText($matches[2][$i]));
} else {
array_push($recursedMatches, $matches[2][$i]);
}
}
return $recursedMatches;
}
$str = '{% block works %}
This works
{% endblock %}
{% block main_block %}
{% block sub_block %}
Does not work
{% endblock %} #ends here
This is not covered
{% block sub_block %}
Does not work
{% endblock %}
{% endblock %}';
print_r(getBlockText($str));
Output:
Array
(
[0] =>
This works
[1] => Array
(
[0] =>
Does not work
[1] =>
Does not work
)
)

Related

Add several paths in the path function with twig [duplicate]

I have a twig template where I would like to test if an item begins with a certain value
{% if item.ContentTypeId == '0x0120' %}
<td><a href='?parentId={{ item.Id }}'>{{ item.BaseName }}</a><br /></td>
{% else %}
<td><a href='?{{ item.UrlPrefix }}'>{{ item.LinkFilename }}</a></td>
{% endif %}
The 0x0120 can look like that or be more complex like this 0x0120D52000D430D2B0D8DD6F4BBB16123680E4F78700654036413B65C740B168E780DA0FB4BX. The only thing I want to do is to ensure that it starts with the 0x0120.
The ideal solution would be to solve this by using regex but I'm not aware if Twig supports this?
Thanks
You can do that directly in Twig now:
{% if 'World' starts with 'F' %}
{% endif %}
"Ends with" is also supported:
{% if 'Hello' ends with 'n' %}
{% endif %}
Other handy keywords also exist:
Complex string comparisons:
{% if phone matches '{^[\\d\\.]+$}' %} {% endif %}
(Note: double backslashes are converted to one backslash by twig)
String contains:
{{ 'cd' in 'abcde' }}
{{ 1 in [1, 2, 3] }}
See more information here: http://twig.sensiolabs.org/doc/templates.html#comparisons
Yes, Twig supports regular expressions in comparisons: http://twig.sensiolabs.org/doc/templates.html#comparisons
In your case it would be:
{% if item.ContentTypeId matches '/^0x0120.*/' %}
...
{% else %}
...
{% endif %}
You can just use the slice filter. Simply do:
{% if item.ContentTypeId[:6] == '0x0120' %}
{% endif %}
You can always make your own filter that performs the necessary comparison.
As per the docs:
When called by Twig, the PHP callable receives the left side of the filter (before the pipe |) as the first argument and the extra arguments passed to the filter (within parentheses ()) as extra arguments.
So here is a modified example.
Creating a filter is as simple as associating a name with a PHP
callable:
// an anonymous function
$filter = new Twig_SimpleFilter('compareBeginning', function ($longString, $startsWith) {
/* do your work here */
});
Then, add the filter to your Twig environment:
$twig = new Twig_Environment($loader);
$twig->addFilter($filter);
And here is how to use it in a template:
{% if item.ContentTypeId | compareBeginning('0x0120') == true %}
{# not sure of the precedence of | and == above, may need parentheses #}
I'm not a PHP guy, so I don't know how PHP does regexes, but the anonymous function above is designed to return true if $longString begins with $startsWith. I'm sure you'll find that trivial to implement.

converting a php line to twig [duplicate]

I'm working on a template and I need to check if something is an array. How do I do that in Twig?
I've tried
{% if my_var is iterable %}
{% for v in my_var %}
...
{% endfor %}
{% else %}
{{ my_var }}
{% endif %}
but it always prints my_var, even when my_var is really an array, as evidenced when it prints out
Array
Array
myusername
../data/table.sqlite3
Another way :
{% if my_var.count()>1 %}
If you don't want to create a custom filter use iterable, as per the docs :
iterable checks if a variable is an array or a traversable object
{% if myVar is iterable %} ... {% endif %}
Just add a custom filter:
$twig->addFilter('is_array', new \Twig_Filter_Function('is_array'));
Then use it like this:
{% if my_var|is_array %}

"While" and "repeat" loops in Twig

Are there any nice ways to use while and repeat loops in Twig? It is such a simple task, but without macros I can't find anything nice and simple.
At least do an infinite cycle and then break it in a condition?
EDIT:
I mean something like
do {
// loop code
} while (condition)
or
while (condition) {
// loop code
}
Edit 2:
Looks like it is not supported natively by twig same reason as it is not supported neither continue; or break; statements.
https://github.com/twigphp/Twig/issues/654
You can emulate it with for ... in ... if by using a sufficiently-high loop limit (10000?)
while
PHP:
$precondition = true;
while ($precondition) {
$precondition = false;
}
Twig:
{% set precondition = true %}
{% for i in 0..10000 if precondition %}
{% set precondition = false %}
{% endfor %}
do while
PHP:
do {
$condition = false;
} while ($condition)
Twig:
{% set condition = true %} {# you still need this to enter the loop#}
{% for i in 0..10000 if condition %}
{% set condition = false %}
{% endfor %}
I was able to implement a simple for loop in twig. So the following php statement:
for ($x = 0; $x <= 10; $x++) {
echo "The number is: $x <br>";
}
when translated to twig is:
{% for i in 0..10 %}
* {{ i }}
{% endfor %}
It's not a while loop but a potential workaround. The best suggestion is to leave business logic like this out of the template layer.
In a nutshell: no. This functionality implies advanced logic, which should be in your business logic, not in the template layer. It's a prime example of the separation of concerns in MVC.
Twig supports for-loops completely, which should suffice if you code correctly - being that complex conditional decisions on which data to display are taken in the business logic where they belong, which then pass a resulting array 'ready to render' to the templates. Twig then supports all nice features only needed for rendering.
This is possible, but a little bit complicated.
You can use {% include ... %} to process nested arrays, which from the comments I read is what you need to do.
Consider the following code:
nested_array_display.html
<ul>
{% for key, val in arr %}
<li>
{{ key }}:
{% if val is iterable %}
{% include 'nested_array_display.html' %}
{% else %}
{{ val }}
{% endif %}
</li>
{% endfor %}
</ul>
Warning with the top solution with "high loop limit" : the loop doesn't break when the condition returns false, it just doesn't enter the loop. So the loop runs up to the high indice

TWIG: Render Multiple blocks using extends and renderblock

I have 2 TWIG renderblock's working on a base and standard template. The two blocks render on the page, everything before {% extends "base.php" %} and after {% block body %}{% endblock %} in base.htm does not render, I see why as I have used renderblock not render which should render the whole template. 1 how do I get whole template to render and 2. {% block head %} will not render unless I use a for loop, I am sure there is a better way of doing this. Edit 1: added $data2.
API
$template = $twig->loadTemplate('event.htm');
echo $template->renderBlock('head', (array('heads' => $data2)));
echo $template->renderBlock('content', (array('events' => $data)));
base.htm
<html>
<head>
{% block head %}
{% for head in heads %}
<title>{{ head.title }}</title>
<meta charset="UTF-8">
</head>
<body>
<h1>{{ head.title2 }}</h1>
{% endfor %}
{% endblock %}
{% block body %}{% endblock %}
</body>
</html>
event.htm
{% extends "base.php" %}
{% block content %}
{% for event in events %}
{{event.uri}}
{{event.desc}}
{% else %}
no events!
{% endfor %}
{% endblock %}
$data2
Array ( [0] => Array ( [id] => 1 [uri] => /event1/1 [title] => some title ) )
1/ As soon as you are rendering a block, you get the content of that block, nothing more.
You need to render the whole template using:
$template = $twig->loadTemplate('event.htm');
echo $template->render(array(
'heads' => $data2,
'events' => $data,
));
2/ You need to use a loop because there are big chances $data2 contains an array or an object instead of the expected header. You should use a string instead, or know in which index you can access your header. This is difficult to help you as I don't know what does contain your $data2 variable, but an ugly solution could be to use the reset function this way:
echo $template->render(array(
'heads' => reset($data2),
'events' => $data,
));
2/ If you know which index (in this case 0) as suggested by Alain
echo $template->render(array(
'heads' => $data2[0],
'events' => $data,
));

Loop till the value of variable is not zero

I have been stuck at this for a while now. I am new to twig and i am trying to iterate a code untill my variable becomes zero. I have tried this:
{% set total = 5%}
{% set i=1 %}
{% for total %}
{{i}}
{%set i=i+1%}
{% set total = total -1%}
{% endfor %}
and this
{% set i=1 %}
{% for total > 1%}
{{i}}
{%set i=i+1%}
{% set total = total - 1%}
{% endfor %}
but none are working.. What am i doing wrong?
Twig fors are more akin to PHP's foreachs (they are for iterating over a traversable). To achieve what you are describing you would do:
{% set nums = range(1, 5) %}
{% for num in nums|reverse %}
{{ num }}
{% endfor %}
In practice, you could set nums from your controller logic. Also note from the Twig manual:
Unlike in PHP, it's not possible to break or continue in a loop.
You can, however, skip elements with if. Manual example:
{% for user in users if user.active %}

Categories