Sunday, July 08, 2007

Refactoring your Templates

StringTemplate is a template engine written by Terence Parr, the author of the popular ANTLR parser generator. It is based upon the idea of Enforcing a strict Model-View separation in Template Engines. Its features allows you to restructure you templates to be more maintenance friendly: so therefore I chose the title "Refactoring your Templates".

What is a template?

Templates are textual files with "holes" in it that that can be filled with data. Besides these holes templates also need facilities for: looping through items, conditional formatting, template fragments and string formatting.

How does JSP handle this?

JSP uses scriptlets inside the template: small fragments of code. These code fragments give full access to the Java language. The danger of using java code for control flow is that templates are not fully separated from underlying data computations. For instance: programmers may choose to quickly embed a JDBC query inside a template. They also lack facilities for reuse: object-oriented features like inheritance and components are not available.

How does StringTemplate do better?

  • Templates contain no code fragments.
  • Templates can be named and may accept attributes.
  • Templates can be grouped in a single file.
  • Template groups can use inheritance to reduce template duplication.

Templates do not embed code fragments, that allows you to use the same template on different platforms. Naming templates allows you to call templates inside other templates and those calls can receive arguments. Grouping templates into a single file is convenient when you break up templates into smaller parts. Inheritance of templates allows you to override named templates in a group. This feature is nice if you need similar but slightly different results. For example when your program generates SQL and you want to support different SQL database dialects.

How do I use StringTemplate?

This article is a quick introduction to StringTemplate, for more information please see the documentation. The StringTemplate essentials in six bullets:

  • Holes inside the template are marked using dollars, for instance: $username$.
  • Every substitution argument must be calculated beforehand and assigned, using the setAttribute("username", "Foo Bar").
  • Properties of attributes syntax: $person.email$.
  • Conditional substitution syntax is: $if(title)$<h1>$title$</h1>$endif$.
  • Results of template substitution is available using the toString() method.

Requiring arguments to be assigned beforehand makes sure that your code has no order-of-computation dependencies, that make your code difficult to change.

Example template "page.st"

 <html>
<head>
<title>$title$</title>
<meta name="keywords" content="$keyword; separator=","$"/>
</head>
<body>

<h1>Hello $username$!</h1>

<ul>$person:{
<li><a href="mailto:$attr.email$">$attr.name$</a></li>}$
</ul>

</body>
</html>

Example Java code

 import org.antlr.stringtemplate.*;

class Person {
       public String name;
       public String email;

       Person(String name, String email) {
             this.name = name;
             this.email = email;
       }
}

public class Main {

       /**
        * Simple StringTemplate example.
        * @param args
        * @throws IOException
        */

       public static void main(String[] args) throws IOException {
             StringTemplateGroup group = new StringTemplateGroup("website", ".");
             StringTemplate page = group.getInstanceOf("page1");
             page.setAttribute("title", "StringTemplate Rocks!");
             page.setAttribute("username", "Foo Bar");
             page.setAttribute("keyword", "foo");
             page.setAttribute("keyword", "bar");
             page.setAttribute("person",
                    new Person("Webmaster", "webmaster@domain.com"));
             page.setAttribute("person",
                    new Person("Manager", "manager@domain.com"));
             page.setAttribute("item", "bar");
             System.out.println(page.toString());
       }
}

Example result

 <html>
<head>
<title>StringTemplate Rocks!</title>
<meta name="keywords" content="foo,bar"/>
</head>
<body>

<h1>Hello Foo Bar!</h1>

<ul>
<li><a href="mailto:webmaster@domain.com">Webmaster</a></li>
<li><a href="mailto:manager@domain.com">Manager</a></li>
</ul>

</body>
</html>

Templates can be applied to attributes

The example code assigns more items to the person attribute, that are formatted into a list of links. The syntax used is:

 $person:{<ul><a href="mailto:$attr.email$">$attr.name$</a></ul>}$ 

The text between the curly brackets is a template to format a person. There is no need for special looping syntax, because the template is applied to each person.

Extract templates into separate files and call them by name

The person template can be extracted into a separate file and called by its name.

Example template "email_link.st"

 <li><a href="mailto:$attr.email$">$attr.name$</a></li> 

Change inside template "page.st"

 [...]
<ul>
$person:email_link()$
</ul>
[...]

Templates with named arguments

The default argument name is "attr", however it is also possible to use named arguments:

Example template "label_input.st"

 <p>
  <label for="$id$">$label$</label><br/>
  <input name="$id$" id="$id$" type="text" value="$value$"/>
</p>

Example call

 <form>
$label_input(id="test",label="Message",value="Hello World")$
</form>

Example results

 <form>
<p>
  <label for="test">Message</label><br/>
  <input name="test" id="test" type="text" value="Hello World"/>
</p>
</form>

Advanced features

Examples and documentation are in the excellent StringTemplate documentation. More  advanced features of StringTemplate are:

  • Template group files
  • Template inheritance

Group files use the *.stg extension and embed a number of named templates. When you have lots of small templates, this might be more convenient.

Templates are ordered in named groups. It is possible to use an existing template group as a basis and override/extend some templates.

Conclusion

StringTempate helps ANTLR to add a new language backend without adjusting a single line of code. This is a showcase of the power of StringTemplate and how it helps to define complex templates in a clean way.

StringTemplate available for different platforms: Java, C# and Python. It is an interesting project if you need clean templates, certainly worth a look!

Links:

No comments: