递归正则表达式匹配smarty if else语句

I want to match the if else statements from a smarty template. And insert there my own tags. In my example <OPEN> and </CLOSE>.

Here is my code:

$value =
<<<SMARTY
{if}
    text1
    {if}
        text2
    {/if}
    text3
{/if}


{if}
    text1
{else}
    text2
{/if}


{if}
    text1
    {if}
        text2
    {else}
        text3
    {/if}
    text
{/if}
SMARTY;

echo filter($value);
function filter($value)
{
    $pattern =
        '(\{(?:if|else).*?\})'.  // open
        '((?:'.

        '[^{]'.
        '|\{(?!/?if|else)'.
        '|(?R)'.

        ')+)'.
        '(\{(?:\/if|else)\})';   // close

    return preg_replace_callback('#'.$pattern.'#', 'filter_callback', $value);
}
function filter_callback($matches)
{
    $m2 = $matches;
    $m2[0] = preg_replace(array('/[
]/','/ +/','/^ /'),array('',' ',''), $m2[0]);
    $m2[2] = preg_replace(array('/[
]/','/ +/','/^ /'),array('',' ',''), $m2[2]);
    print_r($m2);

    return $matches[1].'<OPEN>'.filter($matches[2]).'</CLOSE>'.$matches[3];
}

But it dosent work correctly.

For example I want to have the follow output:

{if}<OPEN>
    text1
</CLOSE>{else}<OPEN>
    text2
</CLOSE>{/if}

and

{if}<OPEN>
    text1
    {if}<OPEN>
        text2
    </CLOSE>{else}<OPEN>
        text3
    </CLOSE>{/if}
    text
</CLOSE>{/if}

If anybody have a idea?

You don't need a recursive regex for this task. The problem with recursive regexes in PHP is that you can't easily have an access to the captures of the different recursion levels. And for a replacement task, you can forget it.

An alternative solution:

$searches = array('{if}', '{else}', '{/if}');
$reps = array('{if}<OPEN>', '</CLOSE>{else}<OPEN>', '</CLOSE>{/if}');

$result = str_replace($searches, $reps, $value); // probably the faster way

or

$patterns = array('~{if}~', '~{else}~', '~{/if}~');
$reps = array('$0<OPEN>', '</CLOSE>$0<OPEN>', '</CLOSE>$0');

$result = preg_replace($patterns, $reps, $value);

print_r($result);

Note: You can avoid one pass using:

$patterns = array('~(?<={if}|{else})~', '~(?={(?:/if|else)})~');
$reps = array('<OPEN>', '<CLOSE>');

EDIT: if you want to extract params and reuse these as a kind of tag attributes, you can do it using the preg_replace way (no need to use preg_replace_callback):

$patterns = array('~{if(?:( )[^(}]+\(([^)]+)\))?}~', '~{else}~', '~{/if}~');
$reps = array('$0<OPEN$1$2>', '<CLOSE>$0<OPEN>', '<CLOSE>$0');

$result = preg_replace($patterns, $reps, $value);

Note: The subpattern [^)]+ that describes the params can be replaced by
(?s)(?>[^)\']++|\'(?>[^\'\\]++|\\{2}|\\.)*\')+ to allow closing parenthesis inside parameters with single quotes.

Here is the concrete example to match special if-statements:

$value =
<<<SMARTY
{if}
    text1
    {if \$user->isAllowed(null,'favourites','read')}
        text2
    {/if}
    text3
{/if}


{if \$user->isAllowed(null,'favourites','read')}
    text1
{else}
    text2
{/if}


{if \$user->isAllowed(null,'favourites','read')}
    text1
    {if}
        text2
    {else}
        text3
    {/if}
    text
{/if}
SMARTY;

echo filter($value);
function filter($value)
{
    $pattern =
        '(\{(?:if|else).*?\})'.  // open
        '((?:'.

        '[^{]'.
        '|\{(?!/?if|else)'.
        '|(?R)'.

        ')+)'.
        '(\{(?:\/if|else)\})';   // close

    return preg_replace_callback('#'.$pattern.'#', 'filter_callback', $value);
}
function filter_callback($matches)
{
    if(preg_match('#\{if.*?\$user\-\>isAllowed\((.*?)\)[^\}]*?\}#', $matches[1], $params)) {
        return $matches[1].'<OPEN '.$params[1].'>'.filter($matches[2]).'</CLOSE>'.$matches[3];
    }

    return $matches[1].filter($matches[2]).$matches[3];
}