Skip to main content

StringTemplate Part 2: Collections and Template Groups

Posted by aberrant on June 2, 2010 at 7:46 PM PDT



This Article deals with StringTemplate. If you've never heard of StringTemplate or a “template engine” you might want to read either Part 1 or the official StringTemplate documentation.

Template Group Files:

In the first article we used a template file (.st file) to hold our template definition. In those examples we had defined a single template that spanned the entire file. When things get more complicated it's convenient to be able to define multiple smaller templates in one file. StringTemplate calls this file a “String Template Group” and recommends a filename ending in “.stg”.

Example String Template Group:

This template group defines one template that takes two parameters, input1 and input2. Parameters passed to the template can then be used in the content area.

Example template with content:

exampleTemplate(input1, input2) ::= <<
    input1 = $input1$
    input2 = $input2$
>>

StringTemplateGroups are a convenient way to group small interrelated templates. These smaller templates can be nested as shown below.

group group-demo;

outerTemplate(input) ::= <<
    In the outer template.
    input = $input$ <-- I can see the value of the 'input' parameter and use it.
    $innerTemplate(input)$
>>

innerTemplate(nestedInput) ::= <<
    The Paramater 'nestedInput' was passed to this template.
    nestedInput = $nestedInput$ <-- I can see the value of the 'nestedInput' parameter and use it.
>>

Here is the code to use this StringTemplateGroup:

StringTemplateGroup group = new StringTemplateGroup(
    new FileReader("templates/group-example.stg"), DefaultTemplateLexer.class);

StringTemplate template = group.getInstanceOf("outerTemplate");
template.setAttribute("input", "Hello World");

System.out.println(template.toString());

The output form this example will look much like this:

In the outer template.
  
input = Hello World; <-- I can see the value of the 'input' parameter and use it.
  
The Paramater 'nestedInput' was passed to this template. I can use it here also.

nestedInput = Hello World <-- I can see the value of the 'nestedInput' parameter and use it.

Collections (Multi-Valued Attributes):

In the first article I showed some basic template tasks. Each example mapped a placeholder to a single piece of data. Very often complicated structures require dealing with collections of data that do not match one to one to placeholders in a template. In Java if we had to process the items in a list we might use a loop.

List<String> xmen = Arrays.asList("Jean Gray", "Cyclops",
    "Angel", "Iceman", "Beast");

System.out.println("Original X-Men");
for(String xman : xmen){
    System.out.println(xman);
}

Output:

Original X-Men
Jean Gray
Cyclops
Angel
Iceman
Beast

A valiant first attempt at using StringTemplate might result in code that looks like this:

StringTemplate template = new StringTemplate(
    "Example 3\nOriginal X-MEN: $xmen$ ");
  
template.setAttribute("xmen", xmen);

System.out.println(template.toString());

As you can see we took the list of strings and just pushed it into the template. Unfortunately this won't give us the same output as above.

Output:

Example 3

Original X-MEN: Jean GrayCyclopsAngelIcemanBeast

Whats happening here is that we gave a “multi-valued attribute” (collection) to StringTemplate but did not tell it how to handle that data. One way to accomplish this is to use a separator.

Example 4:

Original X-MEN members:
$xmen; separator="\n"$

Output:

Example 4:

Original X-MEN members:
Jean Gray
Cyclops
Angel
Iceman
Beast

As you can see we specified a separator of “\n” which means newline. There is no restriction on the size of a separator. The following:

$xmen; separator=" \"\$hi, this is a really long separator\$\"\n\n"$

would yield the following output.

Original X-MEN members:

Jean Gray "$hi, this is a really long separator$"

Cyclops "$hi, this is a really long separator$"

Angel "$hi, this is a really long separator$"

Iceman "$hi, this is a really long separator$"

Beast

I have embedded both quotes and dollar signs to emphasize that you really can use anything in a separator. (If you really want to but I don't recommend it.)

Applying Templates to multi-valued attributes:

Separators have their place but I find that applying a template to a multi-valued attribute significantly more flexible and powerful. I also feel it's one of the key differentiators between StringTemplate and other template engines. First lets look at some good (bad?) old fashioned Java string building.

// Manual formatting and spacing
StringBuilder builder = new StringBuilder();
builder.append("Example 2 \n");
builder.append("<html><body>\n");
builder.append("<h1>Original X-Men</h1>\n");
builder.append(" <ul>\n");
for(String xman : xmen){
    builder.append(" <li>"+ xman + "</li>\n");
}
builder.append(" </ul>\n");
builder.append("</body></html>\n");

Here is some standard java code for building up a block of HTML. Note there are two different levels of formatting here. One is the HTML, which has requirements like the opening and closing of tags. The other is the actual formatting of the output text itself. This is done to keep the generated block readable after it's generated. Whenever text is appended both formatting concerns need to be taken into account. Unlike many of the examples from Part 1 this has some flow control going on. Specifically, we have a loop. When we translate this logic to a template, or more specifically two templates, we can eliminate the details of the loop.

group list-demo;

htmListExample(xmen) ::= <<
Example 5:
<html>
    <body>
        <h1>Original X-Men</h1>
        <ul>
            $xmen:listItem()$
        </ul>
    </body>
</html>

>>

listItem() ::= <<

<li>$it$</li>
>>

Here we have the contents of our template file “list-template-group.stg”. We have defined two templates “htmlListExample” and “listItem”. From our previous examples we know that xmen is a multi-valued attribute. The : syntax is used to apply the listItem template to every item in the list. This generates the following output:

Output:

Example 5:
<html>
    <body>
      <h1>Original X-Men</h1>
      <ul>
  
        <li>Jean Gray</li>
        <li>Cyclops</li>
        <li>Angel</li>
        <li>Iceman</li>
        <li>Beast</li>
      </ul>
    </body>
</html>

As you can see there is no loop, no loop counter and we didn't even define a variable in our second template. StringTemplate makes the $it$ variable available if you have not explicitly named one. Now to be honest this output is not the exact same output as the above block of Java code. There is an extra new line at the beginning of the list items. Most of the time I find this to be “good enough” for the “readability” formatting, but there is a way to fix it. StringTemplate provides functions for slicing a list up into manageable parts. Here we are going to use first() and rest().

/**
* HTML example for applying a template to a list using first() and rest()
*/
firstRestExample(xmen) ::= <<

Example 6:
<html>
  <body>
    <h1>Original X-Men</h1>
    <ul>
        $first(xmen):firstListItem()$$rest(xmen):listItem()$
    </ul>
  </body>
</html>
>>

firstListItem() ::= "<li>$it$</li>"

The first() function returns only the first element of the multi-valued attribute. We then apply the firstListItem() template to only the first item. Everything except the first element (returned by rest()) has the listItem() template applied to it. This generates the exact same output as the block of old fashioned Java code.

Example 6:
<html>
  <body>
    <h1>Original X-Men</h1>
    <ul>
        <li>Jean Gray</li>
        <li>Cyclops</li>
        <li>Angel</li>
        <li>Iceman</li>
        <li>Beast</li>
    </ul>
  </body>
</html>

In this example the use of first and rest was used to solve a trivial whitespace issue. In more complicated templates the functions first(), rest(), etc. may solve more important problems. Finally you are not restricted to applying just one template to an item in a list.

/**
* HTML example for applying multiple templates to a list.
*/
applyMultipleExample(xmen) ::= <<

Example 7:
<html>
  <body>
    <h1>Original X-Men</h1>
    <ul>
        $xmen:bold():listItem()$
    </ul>
  </body>
</html>
>>

/**
* wraps the text in a <span> with a style of bold
*/
bold() ::= <<
<span style="font-weight: bold;">$it$</span>
>>

For each value of the multi-valued attribute the templates are applied from left to right. In this case bold is applied first then list item.

Output:

Example 7:
<html>
  <body>
    <h1>Original X-Men</h1>
    <ul>

        <li><span style="font-weight: bold;">Jean Gray</span></li>
        <li><span style="font-weight: bold;">Cyclops</span></li>
        <li><span style="font-weight: bold;">Angel</span></li>
        <li><span style="font-weight: bold;">Iceman</span></li>
        <li><span style="font-weight: bold;">Beast</span></li>
    </ul>
  </body>

</html>

In this article we have explored string template group files, defining multiple templates withing a single file and applying templates to multi-values attributes (collections). If you want to learn more I recommend the official StringTemplate documentation. It has more detailed explanations and examples.

 Source Code: StringTemplateDemos2.zip

 

Related Topics >>