Click here to Skip to main content
15,867,686 members
Articles / Programming Languages / C#
Article

Ader Template Engine

Rate me:
Please Sign up or sign in to vote.
4.92/5 (26 votes)
30 Aug 20046 min read 168.2K   913   64   66
Library for generating text output from source template and input parameters.

Introduction

Ader TemplateEngine is a .NET class library (written in C#) for generating text output from source template and input parameters. It can be used in many scenarios: website page building, email generation, XML generation, source code generation, etc. Its idea is based on Antlr stringTemplate, but the syntax is based on ColdFusion language.

Currently, .NET Ader TemplateEngine works with .NET 2.0 only, but I have plans on making it work with .NET 1.0/1.1 and maybe Java. Ader TemplateEngine is released under GNU General Public License.

Here is a very simple template:

Thank You for your order #order.billFirstName# #order.billLastName#.
<br>
Your Order Total is: #format(order.total, "C")#

The templates can have expressions, if/elseif/else statements, foreach statement and other templates.

Templates API:

There are two classes mainly used in Template Engine: Template and TemplateManager. Template holds a single instance of a template and TemplateManager is used for executing templates. Easiest way of creating templates is by using static methods of Template or TemplateManager:

C#
Template template = Template.FromString(string name, string data);
Template template = Template.FromFile(string name, string filename);

Then you use it to instantiate TemplateManager:

C#
TemplateManager mngr = new TemplateManager(template);

or even easier:

C#
TemplateManager mngr = TemplateManager.FromFile(filename);
TemplateManager mngr = TemplateManager.FromString(template);

When using FromString method, the string passed contains template code. This method can be used to dynamically generate text without having templates in files. You use SetValue(string name, object value); to add values that can be used within the templates.

E.g.:

C#
mngr.SetValue("customer", new Customer("Tom", "Jackson"));

Then you can refer to customer within the template. You can use any type of object for value. When the value of variable is to be output, ToString() method will be called.

Expressions

Expressions are enclosed with # (hash or pound) characters:

Example:

#firstName#

This example will output value of first name. If you need to output # character, just escape it with another #.

Example:

Your SS## is #ssnumber#

Inside of expression block, you can output any variable:

#somevar#

Access property of a variable:

#somestring.Length#

Property name is not case sensitive. So you can call: #string.length# or #string.LENGTH#. Or call a function:

#trim(somename)#

There are several built in functions, and additional functions can be easily added. The built in functions are:

  • equals(obj1, obj2) - invokes equals method on obj1 with obj2 as parameter. Returns boolean value.
  • notequals(obj1, obj2) - Returns !equals(obj1, obj2). Is equivalent to calling: not(equals(obj1, obj2)).
  • iseven(num) - tests whether number is an even number.
  • isodd(num) - tests whether number is an odd number.
  • isempty(string) - tests whether string has 0 characters. Same as equals(string.Length, 0).
  • isnotempty(string) - tests whether string has at least 1 character.
  • isnumber(num) - tests whether num is of numeric type.
  • toupper(string) - converts string to upper case.
  • tolower(string) - converts string to lower case.
  • isdefined(varname) - tests whether variable named varname is defined.
  • ifdefined(varname, value) - returns value if varname is defined. Especially useful: #ifdefined("name", name)# - will output value of name if it's defined, otherwise will output nothing.
  • len(string) - returns length of string.
  • tolist(collection, property, delim) - will convert collection to string with delim as separator. If you pass property, the value of the property will be evaluated on each element of the collection. If you omit property, then the object itself will be used.

    Example: suppose you have list as:

    C#
    ArrayList list = new ArrayList();
    list.Add("one");
    list.Add("two");
    list.Add("three");
    template.SetValue("mylist", list);
    

    Then in your template:

    #toList(mylist, " & ")#

    the output will be: one & two & three

    Suppose you have list as:

    C#
    list.Add(new Customer("Tom", "Whatever"));
    list.Add(new Customer("Henry", "III"));
    list.Add(new Customer("Tom", "Jackson"));
    template.SetValue("mylist", list);
    

    Then in template:

    #toList(mylist, "firstName", ",")#

    The output will be: Tom,Henry,Tom.

  • isnull(obj) - tests whether obj is null.
  • not(boolvalue) - returns not (!) of boolean value.
  • iif(booleanExpression, iftruevalue, iffalsevalue) - same as booleanExpression ? iftruevalue : iffalsevalue in C#.

    Example:

    #iif(isodd(i), "bgcolor=yellow", "bgcolor=red")#

    will output bgcolor=yellow if i is odd number, and bgcolor=red if i is not odd number.

  • format(object, formatstring) - will call ToString(formatstring) on object. object has to implement IFormattable interface, otherwise ToString() will be called.

    Example: suppose total is decimal with value 1208.45:

    #format(total, "C")#

    will output: $1,208.45

  • trim(string) - will trim string object
  • filter(collection, booleanproperty) - will return new List from collection for those objects whose boolean property property evaluates to true.
  • gt(obj1, obj2) - will return true if obj1 > obj2. (obj1 and obj2 must implement IComparable. All numeric types do).
  • lt(obj1, obj2) - will return true if obj1 < obj2. (obj1 and obj2 must implement IComparable. All numeric types do).
  • compare(obj1, obj2) - will return -1 if obj1 < obj2, 0 if obj1 = obj2, and 1 if obj1 > obj2. (obj1 and obj2 must implement IComparable. All numeric types do).
  • or(bool1, bool2) - will return true if either bool1 or bool2 are true.

    Example:

    #or(equals(state, "IL"), equals(state, "NY"))# - 
    returns true if state is either IL or NY
  • and(bool1, bool2) - will return true if both bool1 and bool2 are true.
  • comparenocase(string1, string2) - will do case insensitive comparison of string1 and string2 and return true if they are equal.
  • stripnewlines(string) - will return all \r\n instances and replace them with space.

Built In Tags:

  • IF

    You can also conditionally output text based on some expression using special if tag:

    XML
    <ad:if test="#booleanexpression#">
    <ad:elseif test="#bool#">
    <ad:else>
    </ad:if>

    elseif and else are optional. If test of "if" evaluates to true, then block inside of "if" will be output, otherwise elseif will be tested (if exists) and then else.

    Example:

    <ad:if test="#equals(cust.country, "US"))#">
    You are US customer.
    <ad:else>
    You are from: #cust.country# country.
    </ad:if>

    If cust.country is "US" then the output will be: You are US customer.

  • FOREACH

    You can loop through collection of elements (any object that implements IEnumerable interface) using FOREACH tag.

    <ad:foreach collection="#collection#" var="cust" index="i">
    #i#: #cust.lastname#, #cust.firstname#
    </ad:foreach>

    Suppose customers is array of customer objects:

    customers = Customer("Tom", "Jackson")
    Customer("Mary", "Foo")

    The output will be:

    1. Jackson, Tom
    2. Foo, Mary

    During execution, variable name that is passed as var attribute will be assigned with element from the collection. index attribute can be omitted, and is used to represent index variable for the loop. It starts with 1 and gets incremented with each iteration.

Custom Templates:

You can also create your own templates inside of template file, that you can call. You do that using template tag:

<ad:template name="ShowCustomer">
#customer.lastname#, #customer.firstname# 
</ad:template>

<ad:showcustomer customer="#cust#" />

You can pass any attribute to the template, and you can use those inside of the template. The template can also access all variables that are defined outside of the template. When calling template, you have to put trailing slash at the end, or put closing tag:

<ad:showcustomer />

or

<ad:showcustomer></ad:showcustomer>

The template also receives special variable: innerText that is the content of executing the inner elements of calling template.

<ad:template name="bold">
<b>#innerText#</b>
</ad:template>

<ad:bold>#cust.lastname#, #cust.firstname#</ad:bold>

the output will be:

<b>Jackson, Tom</b>
(if customer is Tom Jackson)

You can also nest those:

XML
<ad:template name="italic">#innerText#</ad:template>
<ad:bold><ad:italic>This will be bold and italic</ad:italic></ad:bold>

You can also invoke templates based on the name, using apply tag:

<ad:apply template="#usetemplate#">this is content</ad:apply>

If usetemplate is "bold", then "bold" template will be called.

Templates can be nested inside other templates:

XML
<ad:template name="doit">
    <ad:template name="colorme">
      <font color=#color#>#innerText#</font>
    </ad:template>

    <ad:colorme color="blue">colorize me</ad:colorme>
</ad:template>

colorme template can only be used within doit template. Templates can also be added programmatically:

C#
TemplateManager mngr = ...;
mngr.AddTemplate(Template.FromString("bold", "<b>#innerText#</b>"));

Now, bold template can be used anywhere within processing. Here is a sample based on order confirmation:

C#
class Order
{
    string firstname, lastname, address1, city, state, zip, country;

    public string Address1
    {
        get { return this.address1; }
    }

    public string City
    {
        get { return this.city; }
    }

    public string Country
    {
        get { return this.country; }
    }

    public string Firstname
    {
        get { return this.firstname; }
    }

    public string Lastname
    {
        get { return this.lastname; }
    }

    public string State
    {
        get { return this.state; }
    }

    public string Zip
    {
        get { return this.zip; }
    }
}

Order order = GetOrder();
TemplateManager mngr = TemplateManager.FromFile("order-confirmation.st");
mngr.SetValue("order", order);
System.IO.StringWriter writer = new System.IO.StringWriter();
mngr.Process(writer);

string emailBody = writer.ToString();

order-confirmation.st

XML
<ad:showitem>
#item.sku# - #item.name#<br>
<ad:if test="#equals(item.qty, 1)#">
Price: #format(item.price, "C")#<br>
<ad:else>
You bought #item.qty# items for #format(item.price, "C")# 
                        (total: #format(item.total, "C")#)
</ad:if>
</ad:showitem>

#order.firstname# #order.lastname#<br>
#order.address1#<br>
<ad:if test="#isnotempty(order.address2)#">#order.address2#<br></ad:if>
#order.city#, #order.zip# #order.state#
<br>
<table>
<ad:foreach collection="#order.orderitems#" var="orderitem" index="i">
<tr>
    <td>#i#.</td>
    <td bgcolor="#iif(isodd(i), "##DEDEDE", "white")#">
    <ad:showitem item="#orderitem#" />
    </td>
</tr>
</ad:foreach>
</table>
Shipping: #format(order.shipping, "C")#<br>
Taxes: #format(order.tax, "C")#<br>
Order Total: #format(order.total, "C")#<br>

Description of order-confirmation.st:

First, showitem template is defined which shows a single line item of the order. item is passed as attribute to showitem. Then address is shown. Note how if is used to conditionally display second line of address with ending <br> tag. Then each line item of order is looped through using ad:foreach tag. iif function is used to color every other line with #DEDEDE color.

Example #2 for constructing complex SQL queries:

C#
string[] cols = new string[]{"id", "name", "email"};
TemplateManager mngr = TemplateManager.FromFile(file);
mngr.SetValue("colums", cols);
mngr.SetValue("tablename", "customer");
string query = mngr.Process();

And the template file is:

SQL
select #toList(columns, ",")# from #tablename#

Example 1 project has two sample templates that are used to process the same data. First, it outputs it as a C# class to the screen, then it uses HTML template to create HTML file.

If you have any questions, you can use AderTemplates forums.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Chief Technology Officer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionAder Template is not working for a simple variable replace Pin
liglio14-Oct-14 8:20
liglio14-Oct-14 8:20 
AnswerRe: Ader Template is not working for a simple variable replace Pin
Werdna14-Oct-14 13:48
Werdna14-Oct-14 13:48 
Questionproblem with adder template Pin
Tridip Bhattacharjee5-Dec-12 1:18
professionalTridip Bhattacharjee5-Dec-12 1:18 
AnswerRe: problem with adder template Pin
Werdna5-Dec-12 5:02
Werdna5-Dec-12 5:02 
GeneralRe: problem with adder template Pin
Tridip Bhattacharjee5-Dec-12 19:30
professionalTridip Bhattacharjee5-Dec-12 19:30 
GeneralRe: problem with adder template Pin
Werdna7-Dec-12 3:36
Werdna7-Dec-12 3:36 
QuestionDoes it work with RTF Pin
Balck.01-Feb-11 20:14
Balck.01-Feb-11 20:14 
GeneralMy 5 Pin
robocodeboy16-Dec-10 22:29
robocodeboy16-Dec-10 22:29 
GeneralMy vote of 5 Pin
robocodeboy16-Dec-10 21:59
robocodeboy16-Dec-10 21:59 
Generalmake it support external template files [modified] Pin
Huisheng Chen20-Sep-07 23:46
Huisheng Chen20-Sep-07 23:46 
Generalmath expression evaluation Pin
Huisheng Chen11-Aug-05 22:23
Huisheng Chen11-Aug-05 22:23 
GeneralRe: math expression evaluation Pin
Werdna12-Aug-05 5:20
Werdna12-Aug-05 5:20 
Questiontags executed/evaluated all at once? Pin
Huisheng Chen6-Aug-05 6:07
Huisheng Chen6-Aug-05 6:07 
I have something like this:

<ad:foreach var="table" collections=#Tables#>

#table.Init()#

SOME_BIZ_HERE

#table.Close()#

</ad:foreach .....>

when I debug, table.Close() is executed at once, before SOME_BIZE runs.

maybe it is NOT executed like normal languages, but evaluated through-out.

Regards,
unruledboy@hotmail.com
AnswerRe: tags executed/evaluated all at once? Pin
Werdna8-Aug-05 11:57
Werdna8-Aug-05 11:57 
Questionvoid -> [null] ? Pin
Huisheng Chen5-Aug-05 21:33
Huisheng Chen5-Aug-05 21:33 
AnswerRe: void -&gt; [null] ? Pin
Werdna6-Aug-05 5:55
Werdna6-Aug-05 5:55 
Questionruntime var? Pin
Huisheng Chen5-Aug-05 18:06
Huisheng Chen5-Aug-05 18:06 
AnswerRe: runtime var? Pin
Werdna6-Aug-05 5:53
Werdna6-Aug-05 5:53 
GeneralRe: runtime var? Pin
Huisheng Chen6-Aug-05 5:58
Huisheng Chen6-Aug-05 5:58 
Generalor() expression failed Pin
Huisheng Chen1-Aug-05 21:24
Huisheng Chen1-Aug-05 21:24 
GeneralRe: or() expression failed Pin
Werdna2-Aug-05 3:52
Werdna2-Aug-05 3:52 
GeneralRe: or() expression failed Pin
Huisheng Chen2-Aug-05 5:05
Huisheng Chen2-Aug-05 5:05 
GeneralGPL Licence Pin
Schnulle31-Jul-05 20:21
Schnulle31-Jul-05 20:21 
GeneralRe: GPL Licence Pin
Huisheng Chen31-Jul-05 20:50
Huisheng Chen31-Jul-05 20:50 
GeneralRe: GPL Licence Pin
Werdna1-Aug-05 3:36
Werdna1-Aug-05 3:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.