views:

811

answers:

5

PHP (among others) will execute the deepest function first, working its way out. For example,

$text = strtoupper(str_replace('_', ' ', file_get_contents('file.txt')));

I'm doing something very similar to the above example for a template parser. It looks for the tags

{@tag_name}

and replaces it with a variable of the name $tag_name. One more example:

$a = 'hello';
$b = ' world';
INPUT = 'This is my test {@a{@b}} string.';
OUTPUT (step 1) = 'This is my test {@a} world string.';
OUTPUT output = 'This is my test hello world string.';

How can I go about doing this? Does this make sense? If not, I can try explaining better.

+1  A: 

This is not a trivial task. You need to parse the string manually and do your own logical substitutions. There's no magic function or functionality that will do this for you.

My own template engine does about that (and more) and only the core (no template macros) weighs in at 600+ LOC

Henrik Paul
A: 

At least in the given example, I don't understand why the @b token would be nested in the @a token. The two don't seem to have any need for each other. Are you trying to do something like this in Perl?

$a = "b";
$b = "Hello World!";

print $$a;

output would then be "Hello World"

theraccoonbear
+1  A: 

Use a stack - put on top array_push each opened element and evaluate topmost array_pop on first closing mark.

Tomasz Tybulewicz
+4  A: 

I'm not sure I understand the nesting in your example, as the example doesn't demonstrate a purpose behind nesting. Your example input could very easily be

'This is my test {@a} {@b} string.'

And using arrays in str_replace would handle this very simply and quickly.

$aVars = array('{@a}' => 'hello', '{@b}' => 'world');
$sString = 'This is my test {@a} {@b} string.';

echo str_replace(array_keys($aVars), array_values($aVars), $sString);

Which gives us

This is my test hello world string.

Now, a recursive function for this isn't too difficult, though I'm not sure I understand how useful it would be. Here's a working example:

function template($sText, $aVars) {
    if (preg_match_all('/({@([^{}]+)})/',
                       $sText, $aMatches, PREG_SET_ORDER)) {
        foreach($aMatches as $aMatch) {
            echo '<pre>' . print_r($aMatches, 1) . '</pre>';

            if (array_key_exists($aMatch[2], $aVars)) {
                // replace the guy inside
                $sText = str_replace($aMatch[1], $aVars[$aMatch[2]], $sText);

                // now run through the text again since we have new variables
                $sText = template($sText, $aVars);
            }
        }
    }

    return $sText;
}

That print_r will show you what the matches look like so you can follow the function through its paces. Now lets try it out...

$aVars = array('a' => 'hello', 'b' => 'world');
$sStringOne = 'This is my test {@a} {@b} string.';
$sStringTwo = 'This is my test {@a{@b}} string.';

echo template($sStringOne, $aVars) . '<br />';

First Result:

This is my test hello world string.

Now lets try String Two

echo template($sStringTwo, $aVars) . '<br />';

Second Result:

This is my test {@aworld} string.

That may very well be what you're looking for. Obviously you would need an aworld variable for this to work recursively...

$aVars = array('a' => '', 'b' => '2', 'a2' => 'hello world');

echo template($sStringTwo, $aVars) . '<br />';

And our result.

This is my test hello world string.

And just for some fun with the recursion...

$aVars = array('a3' => 'hello world', 'b2' => '3', 'c1' => '2', 'd' => '1');
$sStringTre = 'This is my test {@a{@b{@c{@d}}}} string.';

echo template($sStringTre, $aVars) . '<br />';

This is my test hello world string.

Not sure if this is what you're asking for...

enobrev
A: 

enobrev,

The last example was pretty darn close to what I was looking for. Thanks everyone for the tips!