Philipp
Philipp

Reputation: 197

Read template with nested loops by regex

I have a template structure, which resembles that of Twig. I divide this currently successful with regex.

{% for array as item %}
    {% item.party %}
    {% item %}
{% else %}
    // If empty...
{% endfor %}

{% if !var %}
   // Full
{% else %}
   // Empty
{% endif %}

// Is var full, replace block whit var
{% block var %}
   Some Code
{% endblock %}

Regex preg_match_all('/(?:{% (for|if|block) )(.*?)(?: %})(.*?)({% else %}(.*?))?(?:{% end\1 %})/is', $content, $data);

Now I would like that can also nest. The only problem is that the loop always take the wrong end. The outer loop takes the inner end because it is first.

{% for array as item %} // From here on
   {% item.title %}
   {% for item.sub as sub %}
      {% sub.title %}
   {% endfor %} // To here
{% endfor %}

Do you know how I get the regex to choose the right end? On the content of the first level, I can also re-apply the entire function. But it must be the regex to use the correct end.

Upvotes: 0

Views: 649

Answers (1)

MikeM
MikeM

Reputation: 13641

The following seems to meet your requirements.

It uses (?R) to allow recursive matching of the whole expression within a block.
See Recursive patterns and PCRE.

preg_match_all(
    '/(?:{% (for|if|block) )(.*?)(?: %})(?:(?R)|(.*?)({% else %}(.*?))?)*(?:{% end\1 %})/is',
     $content, $data
);

The only changes I made to your expression were additions to surround the block's inner content sub-pattern in a non-capturing group, and to add the (R) alternative to it:

start(?:(?R)|inner)end

The (?R) attempts to match the whole regular expression, thereby matching any other blocks within the outer block.

You could also surround the (?R) with parentheses, i.e. ((?R)), so these inner blocks will be available in the third capture group.

Upvotes: 1

Related Questions