Recently I've been working on a PHP project with my old small framework, parts of which, including the template engine, are from Discuz!. An unexpected output from the template engine surprised me.
<?php
$input = '<html>
<table>
<thead>{loop $thread $head}<td>$head</td>{/loop}</thead>
{loop $list2 $node}
<tr>$node</tr>
{/loop}
</table>';
$output = preg_replace('/\{loop\s+(\S+)\s+(\S+)\}[\r\n\t]*(.+?)[\r\n\t]*\{\/loop\}/is', '<?php foreach(\\1 as \\2){?>\\3<?php }?>', $input);
echo htmlspecialchars($output);
$input = '<html>
<table>
<thead>{loop $thread $head}<td>$head</td>{/loop}</thead>
{loop $list2 $node}
<tr>$node</tr>
{/loop}
</table>';
echo '<br />';
$output = preg_replace('/\{loop\s+(\S+)\s+(\S+)\}[\r\n\t]*(.+)[\r\n\t]*\{\/loop\}/is', '<?php foreach(\\1 as \\2){?>\\3<?php }?>', $input);
echo htmlspecialchars($output);
?>
The situation was just like the code above. It's noticed that a non-greedy operator "?" is expected to match the first {loop $thread $head} and the first {/loop}. But unfortunately it matches the first {loop $thread $head} and the second {/loop}. Non-greedy mode seems not working. Because output1 and output2 are exactly the same.
Finally I found my solution as follows.
<?php
$input = '<html>
<table>
<thead>
{loop $thread $head}
<td>$head</td>
{/loop}
</thead>
{loop $list2 $node}
<tr>$node</tr>
{/loop}
</table>';
$output = preg_replace('/\{loop\s+(\S+)\s+(\S+)\}[\r\n\t]*(.+?)[\r\n\t]*\{\/loop\}/is', '<?php foreach(\\1 as \\2){?>\\3<?php }?>', $input);
echo htmlspecialchars($output);
?>
The non-greedy mode is kind of disordered because of the following [\r\n\t]*, which was what I didn't notice before. The regular expression engine tries to match as many as subexpressions.