Template Engine and Extensibility


A few time ago, we presented the CodeFluent Runtime’s Template Engine. Today we’ll see how to extend it to change a little bit its syntax and to add custom keywords.

Here is a simple template:

<% var now = new Date(); %>
Hello <%= Name %>!
It's <%= now.toDateString() %>
Cheers,
<%= CurrentUser %>

To use the standard Template Engine, we use this following code:

Template template = new Template();

template.LoadText(templateText, "Name", "CurrentUser");
string templateResult = template.Run(new Dictionary<string, object>
{
     { "Name", "Jone Doe" }, 
     { "CurrentUser", Environment.UserName }
}); 

Console.WriteLine(templateResult);

This will output:

Hello Jone Doe!
It’s Thu Mar 6 2014
Cheers,
The R&D Team

After some customization, the template will be more readable:

Hello {{Name}}!
It's {{Now:yyyy/MM/dd}}
Cheers,
{{CurrentUser}}

Before extending the template engine we have to understand how it works. The engine is composed of 4 classes:

  • Template: contains the configuration such as delimitation tokens and methods to load and run the template
  • ParsedTemplate: contains parameters and code or text blocks extracted while parsing the template
  • ParsedBlock / CodeBlock: contains block text and a method to transform it into JavaScript code
  • Output: contains methods to write into the output file

Let’s use the first sample to understand. First, it will parse the template and extract the following blocks:

  • CodeBlock: var now = new Date();
  • TextBlock: Hello
  • CodeBlock: = Name
  • TextBlock: It’s
  • CodeBlock: = now.toDateString()

Then it will transform blocks into JavaScript instructions. These Text blocks are transformed into “Output.Write(block content)”. When code block starts with “=” it will generate “Output.Write(parameterName)”, otherwise the JavaScript is written as is.

The generated JavaScript is:

ActiveXObject=null;
function __run(Output, Name, CurrentUser) {
 var now = new Date(); 
Output.WriteBlock(1);
Output.Write( Name );
Output.WriteBlock(3);
Output.Write( now.toDateString() );
Output.WriteBlock(5);
Output.Write( CurrentUser );
}

Now we understand the template engine, let’s extend it.
We would like to change to {{ and }}. To do so, we extend the Template class.

public class CustomTemplate : Template
{
    public CustomTemplate()
    {
        StartToken = "{{";
        EndToken = "}}";
    }
}

We also need to create a new CodeBlock to parse the content and write the appropriate JavaScript code:

public class CustomCodeBlock : CodeBlock
{
    public string Format { get; private set; }
    public string ArgumentName { get; private set; }

    public CustomCodeBlock(string code, int creationIndex)
        : base(code, creationIndex)
    {
        Parse(code);
    }

    private void Parse(string text)
    {
        if (text == null || text.StartsWith("="))
            return;

        string argumentName = text;
        string format = null;
        int formatIndex = text.IndexOf(":");
        if (formatIndex > 0)
        {
            argumentName = text.Substring(0, formatIndex);
            format = text.Substring(formatIndex + 1);
        }

        ArgumentName = argumentName;
        Format = format;
    }

    public override void BuildSourceCode(StringBuilder source, ParsedTemplate parsed)
    {
        if(!string.IsNullOrEmpty(ArgumentName))
        {
            source.Append(parsed.Template.OutputItemName);
            if (string.IsNullOrEmpty(Format))
            {
                source.Append(".Write(");
                source.Append(ArgumentName);
                source.Append(");");
            }
            else
            {
                source.Append(".WriteFormatted(");
                source.Append(ArgumentName);
                source.Append(", \"");
                source.Append(Format);
                source.Append("\");");
            }

            return;
        }

        base.BuildSourceCode(source, parsed);
    }
}

Now we have to create the WriteFormatted function in the Output class:

[ComVisible(true)] // Needed to be used by JavaScript code
public class CustomOutput : Output
{
    public CustomOutput(ParsedTemplate template, TextWriter writer)
        : base(template, writer)
    {

    }

    public void WriteFormatted(object value, string format)
    {
        if (string.IsNullOrWhiteSpace(format))
        {
            Write(string.Format("{0}", value));
        }
        else
        {
            string formattedValue = string.Format("{0:" + format + "}", value);
            Write(formattedValue);
        }
    }
}

Finally let’s say to the Template to use our custom classes instead of the default ones.

public class CustomTemplate : Template
    {
        public override CodeBlock CreateNewCodeBlock(string code, int creationIndex)
        {
            return new CustomCodeBlock(code, creationIndex);
        }

        public override Output CreateNewOutput(ParsedTemplate parsedTemplate, TextWriter writer)
        {
            return new CustomOutput(parsedTemplate, writer);
        }
    }

That’s it! The full sample is available on our GitHub.

Happy Templating

The R&D Team.

  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s