A Super-Simple Template System

In the last post, I explored Text Templates (T4), and determined that using T4 text templates for my code generation needs would add complexity and not yield sufficient ROI (although I did determine that a doc gen example using T4 is interesting in its own right). However, my exploration into T4 text templates yielded one important point, which is that delimiting blocks of code using <# and #> is a good approach. This post details my super-simple template system, which will be more than adequate for building this first version of a doc gen system.

This post is the ninth in a series of blog posts on generating Open XML documents. Here is the complete list: Generating Open XML WordprocessingML Documents Blog Post Series

For what it’s worth, I did a fair amount of reading of the C#, VB, and XML specs and determined to my own satisfaction that those combinations are fine. I could go on for about two pages, detailing exactly where the hash mark is allowed in all three languages, and why <# and #> are safe, but I’ll spare you the ordeal. In any case, these are the combinations that the T4 architects and program managers decided on, and I’m certain that an extraordinary amount of time was spent designing the T4 syntax.

I am going to make one more simplification, which is that in my super-simple template system, the <# must be the first two non-whitespace characters on a line, and that #> must be the last two non-whitespace characters. You will see that this makes the LINQ projection that processes the template very simple. Ultimately, this template system would be best implemented by defining a grammar and writing or using a real parser, but my main objective is to build a small example that enables us to explore document generation, so a shortcut is in order here.

To allow for further enhancements in the future, I’m going to specify that the contents will be a small XML document. While this makes the syntax a bit more verbose, we gain such advantages as XML schema validation, extensibility, and a familiar syntax. Following is an example of a template using this system. It contains two insertion blocks, one with the name of Using, and the other with the name of GeneratorMain:

<# <Insert Name="Using"/> #>

namespace GenDocs
{
    class Generator
    {
        static void Main(string[] args)
        {
            <# <Insert Name="GeneratorMain"/> #>
        }
    }
}

The Using insertion block might be replaced with this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

The GeneratorMain insertion block could be replaced with this:

Console.WriteLine("Hello world");

Processing this template would then result in the following C# program:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GenDocs
{
    class Generator
    {
        static void Main(string[] args)
        {
Console.WriteLine("Hello world");
        }
    }
}

The LINQ projection that processes this template, in its entirety, is:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;

class Program
{
    // Simulated method that returns the text of a tagged content control.
    static string GetTextFromContentControl(string tag)
    {
        if (tag == "Using")
            return
@"using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;";
        if (tag == "GeneratorMain")
            return @"Console.WriteLine(""Hello world"");";
        return "error";
    }

    static void Main(string[] args)
    {
        string[] templateCode = File.ReadAllLines("template.txt");
        var filledTemplate = templateCode
            .Select(l =>
            {
                string trimmed = l.Trim();
                if (trimmed.StartsWith("<#") && trimmed.EndsWith("#>"))
                {
                    XElement insert = XElement.Parse(trimmed.Substring(2, trimmed.Length - 4));
                    string tag = insert.Attribute("Name").Value;
                    return GetTextFromContentControl(tag);
                }
                else
                    return l;
            })
            .ToArray();
        File.WriteAllLines("GeneratedDocGenProgram.cs", filledTemplate);

        // Print out the template for demonstration purposes.
        Console.WriteLine(File.ReadAllText("GeneratedDocGenProgram.cs"));
    }
}

To run this example, create a new C# console application. Save the template as a file named template.txt in the bin directory, and then run it. The example produces the following output:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GenDocs
{
    class Generator
    {
        static void Main(string[] args)
        {
Console.WriteLine("Hello world");
        }
    }
}

!!!

5 Comments »

  1. phil nolan said,

    March 1, 2011 @ 8:08 pm

    Great series of posts. Really enjoying them so do please keep up the good work 🙂

  2. Eric White said,

    March 15, 2011 @ 4:24 am

    It’s been a lot of fun. Glad you like them! 🙂

  3. Red Johnson said,

    May 1, 2012 @ 6:49 pm

    Hello,
    I did not kinda get the last part of the instruction Eric.Can you help me?

  4. Rog McShane said,

    May 2, 2012 @ 1:06 am

    Hi Eric!

    Is there anyway somebody can do this for me online?

  5. Richard said,

    July 3, 2012 @ 8:47 pm

    I’m just learning about the T4 template system in Visual Studio but I’m gradually getting the hang of it. I’ll check out the rest of your series too – thanks!

RSS feed for comments on this post · TrackBack URI

Leave a Comment