phpvariablesreplacetemplatingcurly-braces

replace php templating variables between double curly braces that are nested inside two-character code delimiters


I am working on a custom minimalist PHP templating engine for use in personal projects, I need to solve how to make some replacements using a regular expression. I have this string:

$string = 'sometext {% {{ var1 }} middletext {{ var2 }} %} moretext {{ varx }} {% {{ var3 }} %}';

I want to replace all variables between {{ }} delimiters with their php equivalent, but only if they are nested inside {% %} delimiters

This is the desired output:

sometext {% $var1 middletext $var2 %} moretext {{ varx }} {% $var3 %}

So far I have tried this:

echo preg_replace('#\{\{\s*(.*?)\s*\}\}#', '\$$1', $string);

I have even tried this Regex with lookaheads:

echo preg_replace('#(?=.*\{%?)\{\{\s*(.*?)\s*\}\}(?=.*%\})(?!=.*\{%.*%\})#', '\$$1', $string);

But, in both cases I get the same output: All variables in the string are replaced irrespective of being inside {% %} delimiters or not (note how $varx is also undesirably replaced), my incorrect output is:

sometext {% $var1 middletext $var2 %} moretext $varx {% $var3 %}

I have solved it and gotten the output I desire by using foreach loops to match {% %} delimiters first and then replace the variables in the string by using arrays in preg_replace(), like this:

<?php
$string = 'sometext {% {{ var1 }} middletext {{ var2 }} %} moretext {{ varx }} {% {{ var3 }} %}';
$phpcode = preg_match_all('#{%.*?%}#', $string, $matches);
if ($phpcode) {
    foreach ($matches as $match) {
        if (is_array($match)) {
            foreach ($match as $value) {
                $replacements[] = preg_replace('/{{\s*(.*?)\s*}}/', '\$$1', $value);
                $patterns[] = "/" . preg_replace('/([\[\]\{\}\.\*\+\$\/\?\^])/', '\\\$1', $value) . "/";
            }
        }
    }
}
echo preg_replace($patterns, $replacements, $string);

the output can be tested here

But ... I'm pretty sure theres a simple one-line of preg_replace() regex code that can achieve the same desired output. Any ideas?


Solution

  • Thanks for the advices and for the alternatives, I finally came up with a solution though. This is the Regular expression that worked for me:

    {{\s*(\w*)\s*}}(?=[^%]*%})
    

    Explanation:

    {{\s*(\w*)\s*}} Matches anything inside the {{ }} delimiters including any number of leading or trailing blank spaces inside which makes the templating engine more forgiving
    (\w*) matches words with common variables naming convention (i.e. letters, digits or underscores), and puts it in a capture group
    (?=[^%]*%}) looks ahead to match ONLY if the condition is true (i.e. it matches only if the {{variable}} is nested inside the {% %} delimiters)
    [^%]* matches each of the rest of the characters in the string to check that ARE NOT a % (percent character)...
    %} matches the end of the {% %} code block

    This is the final SINGLE LINE preg_replace() statement achieving the originally desired output:

    echo preg_replace('#{{\s*(\w*)\s*}}(?=[^%]*%})#', '\$$1', $string);
    

    You can confirm the regex here and the replacements here
    I tested this regex in my templating engine and I assert that I can now have any number of {{variables}} both inside and outside of {% ... %} code block delimiters :)