Click here to Skip to main content
Click here to Skip to main content

Simple Template Engine

, 19 Aug 2008
Rate this:
Please Sign up or sign in to vote.
Generate source code and database scripts for any language or platform.
Prize winner in Competition "Best LAMP article of Aug/Sep 2008"

Introduction

Code generation is a fundamental aspect of flexible, rapid application development. There are many open source and commercial solutions available, such as Microsoft T4 or CodeSmith Tools, but often times, these tools are targeted at specific technologies or platforms. With a little legwork, you can leverage template processing libraries in modern scripting languages such as Ruby or Python to create powerful, open-ended template engines. You can use these template engines with any language or platform to generate source code, database scripts, configuration files, or any other text-based file.

Background

This particular solution uses the open source Ruby language to process templates, given a simple, flexible XML schema representing your domain model. Ruby comes preconfigured on OS X systems, is easily installed through package managers on most popular Linux distributions, and may be installed on Windows platforms using the Ruby One-Click installer found at ruby-lang.org.

Using the code

This example will use the following trivial domain schema describing a library catalog:

<Library catalog="MyCatalog">

    <enumeration name="Category">
        <value>FICTION</value>
        <value>NON_FICTION</value>
        <value>REFERENCE</value>
        <value>PERIODICAL</value>
    </enumeration>

    <enumeration name="Format">
        <value>BOOK</value>
        <value>MAGAZINE</value>
        <value>COMPACT_DISC</value>
        <value>VHS</value>
        <value>DVD</value>
    </enumeration>

    <object name="Media">
        <property name="ID" type="string" />
        <property name="Title" type="string" />
        <property name="Category" type="Category" />
        <property name="Format" type="Format" />
        <property name="Author" type="string" />
        <property name="ReleaseDate" type="DateTime" />
    </object>

    <object name="Author">
        <property name="ID" type="string"/>
        <property name="First" type="string"/>
        <property name="Last" type="string"/>
    </object>

</Library> 

The following template file Enumerations.erb could be used to generate C# enumerations from the library domain specification:

namespace <%= catalog %>
{

    <%- enumerations.each do |enumeration| -%>
    public enum <%= enumeration.name %>
    {
        <%- enumeration.values.each do |value| -%>
        <%= value %><%= ',' if value != enumeration.values.last %>
        <%- end -%>
    }

    <%- end -%>
}

Running the command ruby generator.rb library.xml Enumerations.erb produces the following output:

namespace MyCatalog
{

    public enum Category
    {
        FICTION,
        NON_FICTION,
        REFERENCE,
        PERIODICAL
    }

    public enum Format
    {
        BOOK,
        MAGAZINE,
        COMPACT_DISC,
        VHS,
        DVD
    }

}

The following template file Objects.erb can be used to generate simple C# domain objects from the library domain specification:

using System;

namespace <%= catalog %>
{

    <%- objects.each do |object| -%>
    public partial class <%= object.name %>
    {
        <%- object.properties.each do |property| -%>
        <%= property.type %> <%= property.name %> { get; set; }
        <%- end -%>
    
        public object Clone()
        {
            <%= object.name %> copy = new <%= object.name %>();
            <%- object.properties.each do |property| -%>
            copy.<%= property.name %> = <%= property.name %>;
            <%- end -%>
            return copy;
        }
    }

    <%- end -%>
}

Running the command ruby generator.rb library.xml Objects.erb produces the following output:

using System;

namespace MyCatalog
{

    public partial class Media
    {
        string ID { get; set; }
        string Title { get; set; }
        Category Category { get; set; }
        Format Format { get; set; }
        string Author { get; set; }
        DateTime ReleaseDate { get; set; }
        
        public object Clone()
        {
            Media copy = new Media();
            copy.ID = ID;
            copy.Title = Title;
            copy.Category = Category;
            copy.Format = Format;
            copy.Author = Author;
            copy.ReleaseDate = ReleaseDate;
            return copy;
        }
    }

    public partial class Author
    {
        string ID { get; set; }
        string First { get; set; }
        string Last { get; set; }
        
        public object Clone()
        {
            Author copy = new Author();
            copy.ID = ID;
            copy.First = First;
            copy.Last = Last;
            return copy;
        }
    }

}

If you need to add or modify domain objects, it is simply a matter of updating your XML schema. With the proper templates, you will no longer need to maintain your persistence layer or any other boilerplate code, and any bug fixes or modifications you make to your templates will be immediately available across all of your domain objects. Ultimately, you will spend less time maintaining your plumbing code, and more time doing real development.

Having your domain specification under revision control opens up some more possibilities. You will have a database backup and versioning scheme that corresponds with your repository revision (and possibly build numbers). You could reconstruct your database tables from any point in your repository. You could compare two versions of your schema and generate database rollback and change scripts. Setting up a database for a new developer will be a trivial task.

Points of interest

The Ruby script driving this template engine is very short. I have trimmed out some functionality for the sake of demonstration, but you will have access to the entire Ruby language within the engine as well as your templates, allowing you to extend the engine any way you want.

I've included another (trivial and poorly designed) domain specification to demonstrate the usage of domain specific XML tags. You can use any tag name you want, and specify any attribute you want. One requirement is that the name attribute must be present in any complex element. XML Text fields must also not be used for anything other than representing simple lists (as demonstrated by the following ingredient elements).

<kingdom domain="Animal"/>

    <animal name="Duck" species="Bird">
        <recipe name="Bacon Wrapped Duck" difficulty="Medium">
            <ingredient>10 duck breasts</ingredient>
            <ingredient>3 lbs peppered bacon</ingredient>
            <ingredient>2 jars banana pepper rings</ingredient>
            <ingredient>Italian dressing</ingredient>
        </recipe>
        <recipe name="Plain Duck" difficulty="Easy"/>
        <feature name="Feet" value="Webbed" color="Yellow"/>
        <feature name="Tasty" value="true"/>
    </animal>
    
    ...

</kingdom>

The following template snippet would allow you to iterate through your recipes:

<%- animals.each do |animal| -%>
    <%- animal.recipes.each do |recipe| -%>

        ...

    <%- end -%>
<%- end -%>

If there is enough interest, I could throw together a more practical solution demonstrating schema validation, nullable data types, multiple languages, inheritance, persistence, and build integration. Feel free to e-mail me at baker.alex@gmail.com with any questions or comments.

Helpful links

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

baker.alex
Software Developer
United States United States
No Biography provided

Comments and Discussions

 
GeneralUndocumented functionality. PinmemberAndy Hass20-Aug-08 17:18 

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

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

| Advertise | Privacy | Mobile
Web01 | 2.8.140709.1 | Last Updated 19 Aug 2008
Article Copyright 2008 by baker.alex
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid