Introduction

Skip to end of metadata
Go to start of metadata

Most programs that emit source code or other text output are unstructured blobs of generation logic interspersed with print statements. The primary reason is the lack of suitable tools and formalisms. The proper formalism is that of an output grammar because you are not generating random characters--you are generating sentences in an output language. This is analogous to using a grammar to describe the structure of input sentences. Rather than building a parser by hand, most programmers will use a parser generator. Similarly, we need some form of unparser generator to generate text. The most convenient manifestation of the output grammar is a template engine such as StringTemplate.

A template engine is simply a code generator that emits text using templates, which are really just "documents with holes" in them where you can stick values called attributes. An attribute is either a program object such as a string or VarSymbol object, a template instance, or sequence of attributes including other sequences. Template engines are domain-specific languages for generating structured text. StringTemplate breaks up your template into chunks of text and attribute expressions, which are by default enclosed in angle brackets <attribute-expression> (but you can use whatever single character start and stop delimiters you want). StringTemplate ignores everything outside of attribute expressions, treating it as just text to spit out. To evaluate a template and generate text, we "render" it with a method call:

Java

ST.render()

C#

ST.Render()

Python

ST.render()

For example, the following template has two chunks, a literal and a reference to attribute name:

Using templates in code is very easy. Here is the requisite example that prints "Hello, World":

 

Java
C#
Python

 

 

MVC Pattern

In the parlance of the model-view-controller (MVC) pattern, templates represent the view and the code fragment represents both model (the name string) and controller (that pulls from the model and injects attributes into the view).

StringTemplate is not a "system" or "engine" or "server"; It is designed to be embedded inside other applications and is distributed as a small library with no external dependencies except ANTLR (used for parsing the StringTemplate template language).

Groups of templates

The primary classes of interest are ST, STGroupDir, and STGroupFile. You can directly create a template in code, you can load templates from a directory, and you can load a file containing a collection templates (a template group file). Group files behave like zips or jars of template directories.

For example, let's assume we have two templates decl and init in directory /tmp:

/tmp/decl.st
/tmp/init.st

We can access those templates by creating a STGroupDir object. We then ask for an instance with getInstanceOf() and inject attributes with add():

 

Java
C#
Python

 

This example demonstrates some key syntax and features. Template definitions look very similar to function definitions except that the bodies are strings. Template decl takes three arguments by reference as only two of them directly. Instead of expanding value immediately, invokes/includes template init instead with an argument of value. Alternatively, Template init could take no arguments. It would still see attribute value though because of dynamic scoping. That essentially means that a template can reference the attributes of any invoking template.

Note, that to get the spacing correct, there is no space between expression <name> and <init()>. If we do not inject a declaration initialization (attribute value), we don't want to space between the name and the ';'. Template init emits " = <v>" only if the controller code injects a value, which we do here (0). In this case, we have injected two strings and one integer, but we can send in any object we want; more on that below.

Sometimes it's more convenient to collect templates together into a single unit called the group file. For example, we can collected the template .st files into a single .stg group file:

/tmp/test.stg

To pull templates from this file instead of a directory, all we have to do is change our constructor to use STGroupFile:

 

Java
C#
Python

 

 

Accessing properties of model objects

Template expressions can access the properties of objects injected from the model. For example, consider the following User object.

Java
C#
Python

 

We can inject instances of User just like predefined objects like strings and can refer to properties using the '.' dot property access operator. StringTemplate interprets o.p by looking for property p within object o. The lookup rules differ slightly between language ports, but in general they follow the old JavaBeans naming convention. StringTemplate looks for methods getP(), isP(), hasP() first. If it fails to find one of those methods, it looks for a field called p. In the following example, we access properties id and name. Also note that the template uses $...$ delimiters, which makes more sense since we are generating HTML.

 

Java
C#
Python

 

Property reference u.id evaluates to the field of the injected User object whereas u.name evaluates to the "getter" for the field name.

StringTemplate renders all injected attributes and any reference properties to text using the string conversion method natural for the implementation language; e.g., toString() in Java, ToString() in C#, and str{_}{}_() in Python. In this case, a reference to $u$ would yield "999:parrt".

Injecting data aggregate attributes

Being able to pass in objects and access their fields is very convenient but often we don't have a handy object to inject. Creating one-off data aggregates is a pain, you have to define a new class just to associate two pieces of data. StringTemplate makes it easy to group data during add() calls. You may pass in an aggregrate attribute name to add() with the data to aggregate. The syntax of the attribute name describes the properties. For example "a.{p1,p2,p3}" describes an attribute called a that has three properties p1, p2, p3. Here's an example:

 

Java
C#
Python

 

Applying templates to attributes

Let's look more closely at how StringTemplate renders attributes. It does not distinguish between single and multi-valued attributes. For example, if we add attribute name with value "parrt" to template "<name>", it renders to "parrt". If we call add() twice, adding values "parrt" and "tombu" to name, it renders to "parrttombu". In other words, multi-valued attributes render to the concatenation of the string values of the elements. To insert a separator, we can use the separator option: "<name; separator=\", \">". Without changing the attributes we inject, the output for that template is "parrt, tombu". To alter the output emitted for each element, we need to iterate across them.

StringTemplate has no "foreach" statement. Instead, we apply templates to attributes. For example, to surround each name with square brackets, we can define a bracket template and apply it to the names:

Injecting our list of names as attribute name into template test yields "[parrt][tombu]". Combining with the separator operator yields "[parrt], [tombu]":

StringTemplate is dynamically typed in the sense that it doesn't care about the types of the elements except when we access properties. For example, we could pass in a list of User objects, User(999,"parrt") and User(1000,"tombu"), and the templates would work without alteration. StringTemplate would use the "to string" evaluation function appropriate for the implementation language to evaluate <x>. The output we'd get is "[999:parrt], [1000:tombu]". StringTemplate sets the first parameter of the template it's applying to the iterated value (x in this case).

Sometimes creating a separate template definition is too much effort for a one-off template or a really small one. In those cases, we can use anonymous templates (or subtemplates). Anonymous templates are templates without a name enclosed in curly braces. They can have arguments, though, just like a regular template. For example, we can redo the above example as follows.

Anonymous template {x | [<x>]} is the in-lined version of bracket(). Argument names are separated from the template with the '|' pipe operator.

StringTemplate will iterate across any object that it can reasonably interpret as a collection of elements such as arrays, lists, dictionaries and, in statically typed ports, objects satisfying iterable or enumeration interfaces.

General use of StringTemplate for formatting

The ST.format method is great for general use in general code.

Java
C#
Python

 

Yields:

Labels:
  1. May 27, 2011

    Greetings,

    Your example from Groups of Templates section does not work in v4.0.2

    st stay always null .

  2. May 28, 2011

    Works for me. did you put decl.st in there?

  3. Aug 30, 2011

    I suppose Bartel forgot adding the '.st' suffix.

    I encountered a same mistake.

    "For example, let's assume we have two templates decl and init in directory /tmp:"

    IMHO, this sentence is a bit misleading, I created two file names 'decl' and 'init' according to it.

    After a few try, I finally noticed the right file name  'decl.st' at the header line of the code snippet.

    Best Regards

  4. Jan 02, 2012

    In the section Injecting data aggregate attributes, the term newline is undefined in the Java example. Shouldn't newline be replaced with "\n"?