April 28, 2011

Creating a simple but flexible templating engine

Question by Starx

I am trying to build a basic templating engine. Like the template engines already available as open source, I am using search and replace techniques.

However, as the search and replace have to be hardcoded, it is not so much flexible. What I mean to say is, as an example, I am using something like this

$templateMarkup = '<div class="title">{_TITLE_}</div>';
$renderedMarkup = str_replace("{_TITLE_}",$title,$templateMarkup);
echo $renderedMarkup;

As you can see, it is hardcoded. So I have to purposely know all the placeholders to accomplish a successful render.

I am a little bit weak in regular expression. But I know, if I can develop a regex, which can match all the text starting with {_ and ending _} and get the value in between them, I just might be able to create a flexible templating engine.

I need help with the regular expression.

If I am completely going the wrong way to accomplish, please do warn me.


For those who think I am reinventing the wheel. Here is my explanation

Templating engines, that are already available are quite unnecessarily complex. 
My requirements are simple and so I am builidng my own simple engine. 

Answer by Sam

The regular expression you’re looking for is {_(w+)_}, if your template tags are only ever a single word. You’d use it a bit like this:

<?php

$replacements = array(
    "firstname" => "John",
    "lastname" => "Smith"
);

$template_markup = "Hello {_firstname_} {_lastname_}";
if(preg_match_all('/{_(w+)_}/', $template_markup, $matches)) {
    foreach($matches[1] as $m) {
        $template_markup = str_replace("{_".$m."_}", $replacements[$m], $template_markup);
    }
}
echo $template_markup;

?>

You’ll see the preg_match_all has forward slashes surrounding the regular expression, these are delimiters.

Update: If you want to expand the regular expression beyond single words, then be careful when using . to match any character. It’s better to use something like this to specify that you want to include other characters: {_([w-_]+)_}. The [w-_] means it will match either alphanumeric characters, hyphens or underscores.

(Perhaps someone can explain why using . might be a bad idea? I’m not 100% sure).

Answer by Starx

I have combined the solutions provided by both Sam and James to create a new solution.

  • Thanks to Sam for the regex part
  • Thanks to James for the array mode str_replace() part.

Here is the solution:

$replacements = array(
    "firstname" => "John",
    "lastname" => "Smith"
);

function getVal($val) {
    global $replacements;
    return $replacements[$val];
}

$template_markup = "Hello {_firstname_} {_lastname_}";
preg_match_all('/{_(w+)_}/', $template_markup, $matches);
$rendered = str_replace($matches[0], array_map("getVal",array_values($matches[1])), $template_markup);
echo $rendered;

Author: Nabin Nepal (Starx)

Hello, I am Nabin Nepal and you can call me Starx. This is my blog where write about my life and my involvements. I am a Software Developer, A Cyclist and a Realist. I hope you will find my blog interesting. Follow me on Google+

...

Please fill the form - I will response as fast as I can!