Tutorial
Introduction
SimpleTemplate generates text using templates you provide. A template
is plain text interspersed with template directives. While the rest of
the text is emitted as it is into the output, SimpleTemplate processes
the template directives and replaces the directives with data from the
model.
TemplateReader, Template and Scope
For reading a template, you use a TemplateReader. You can create a
TemplateReader by either using the constructor or using one of the
static helper functions.
TemplateReader reader = new TemplateReader(url, startToken, endToken);
TemplateReader reader = TemplateReader.fromFile(fileName);
TemplateReader reader = TemplateReader.fromFile(fileName, startToken, endToken);
TemplateReader reader = TemplateReader.fromString(s);
TemplateReader reader = TemplateReader.fromString(s, startToken, endToken);
TemplateReader reader = TemplateReader.fromResource(resource);
TemplateReader reader = TemplateReader.fromResource(resource, startToken, endToken);
Example:
TemplateReader reader = TemplateReader.fromString(“Hello $greeting$”);
In the first form, url refers to a java.lang.URL. All other methods
invoke this constructor. By default, SimpleTemplate uses “$” as
directive separator. You can override this by using one of the methods
that accepts startToken and endToken parameters.
The ‘include’ SimpleTemplate directive uses the given resource or file
as the base for finding included templates. In case of a TemplateReader
that is constructed using one of fromString variants, the working
directory is used as the base instead.
Once you have a TemplateReader, use readTemplate() method to parse and extract a Template from the input.
Template template.readTemplate();
Once you have a template, you need to pass the java model and get the
results. You use a Scope object to pass the model details and apply
method to get the results. You can put any number of objects in a scope
using different names. Using the same name twice, replaces the previous
object with a later one.
Scope scope = new Scope();
scope.put(“greeting”, “World”) ;
String result = template.apply(scope);
The apply method processes the template and returns the result as a String. The result will contain “Hello World”.
Template Syntax
SimpleTemplate input is just plain text interspersed with template
directives. Any number of model variables can be passed to the template
through the scope object.
Contexts and Data Types
SimpleTemplate operates in two
contexts. When you start reading a file it is in Text context. In Text
context all text input is emitted into the output. SimpleTemplate moves
into Template context when the stream reaches one of the template
directives (i.e one of $identifier, $if,
etc.). In the Template context, SimpleTemplate reads text as tokens and
can identify integers, booleans (true/false) and strings. Each of these
data types, themselves, is a template. You can switch into a block
context using ${}$ block. See Block Statements.
Attribute Access
The simplest directive in a template is referencing to a model
attribute. You can refer to an attribute reference such as name and
email from scope:
Name is $name$ and email is $email$
The $name$ and $email$ in the template will be replaced with the return
values from the toString method of the objects put in the scope as
“name” and “email”.
You can also access nested attributes.
Address address = new Address();
address.setName(“John Doe”);
address.setEmail(“john.doe@example.com”);
scope.put(“address”, address);
With scope setup with a composite object like address:
Name is $address.name$ and email is $address.email$
gives a result “Name is John Doe and email is john.doe@example.com”.
SimpleTemplate just emits an empty string for the attributes that are
null. When an attribute needs to be resolved, SimpleTemplate looks for
methods that start with 'get', 'is' and '' it that order. For example, address.email returns the value of if it exists. If it does not exist
When an attribute (nested or otherwise) refers to a
java.util.Collection, SimpleTemplate handles it differently.
SimpleTemplate emits a concatenation of toString values of each item
separated by st_list_separator. Also each list item is prefixed with
st_list_prefix and suffixed with st_list_suffix.
With scope setup as:
String[] flowers = new String[] { “Rose”, “Jasmine”, “Lily” } ;
scope.put(“list”, flowers);
The following script:
$set st_list_separator to “--”
$set st_list_prefix to “<flower:”
$set st_list_suffix to “/>”
$list$
gives a result
“<flower:Rose/>--<flower:Jasmine/>--<flower:Lily/>”.
The st_list_prefix and st_list_suffix defaults to empty strings and
st_list_separator defaults to “,”.
Indexed Access
You can access items from a Collection, Array or a Map using indexed access. Setting up scope as follows,
String[] flowers = new String[] { “Rose”, “Jasmine”, “Lily” } ;
scope.put(“flowers”, flowers);
the following template
$flowers[0]$
gives a result "Rose". Similarly, with a map set into scope:
Map<String, String> capitals = new HashMap<String, String();
capitals.put("India", "New Delhi");
capitals.put("United States", "Washington");
capitals.put("Canada", "Ottawa");
scope.put(“capitals”, capitals);
the following template
$capitals["United States"]$
gives a result "Washington".
You can even use an expression as an index. For accessing items from a
Collection, the expression should be resolved into an integer. For
example, with scope setup as
Map<String, String> capitals = new HashMap<String, String();
capitals.put("India", "New Delhi");
capitals.put("United States", "Washington");
capitals.put("Canada", "Ottawa");
scope.put(“capitals”, capitals);
scope.put("mycountry", "India");
the following template
$capitals[mycountry]$
gives a result "New Delhi". You get the same result even you use a template instruction as an index. So,
$capitals[$mycountry$]$
also gives a result "New Delhi".
Looping
You can loop through a Map or a Collection with SimpleTemplate looping directive.
List of flowers available:
$flowers {
$index1$: $it$
}$
$capitals {
Capital of $key$ is $value$
}$
Withing the scope of the looping over a Collection or Array, SimpleTemplate sets index0 to the current item index (starting with 0), index1 to the current item index (starting with 1) and it to the item itself. For Maps, key is set to key of the current entry and value is set to the value.
You can also loop through a Map using the values or keySet (though it is nothing to do with SimpleTemplate).
Known Capitals: $capitals.keySet { $it$ }
Introducing Aliases
Sometimes, you need to access attributes that are deeply nested in the
model. You can introduce aliases into the current scope and reduce the
amount of typing you need to do as well as make the template code look
good. SimpleTemplate adds the aliases it, index0, index1, key and value in the looping context. You can introduce your own aliases using set and with directives to SimpleTemplate.
Suppose we have a AddressBook object which has a list of AddressBookEntrys. Each AddressBookEntry further has a list of PhoneNumbers each with a type and a number. For iterating through all the phone numbers in an AddressBook we might write:
$addressBook.addresses {
Name: $it.firstName$ $it.lastName$
$it.phoneNumbers {
$it.type$ phone: $it.number$
}$
}$
The same thing can also be written (using with):
$addressBook.addresses {
$with it {
Name: $firstName$ $lastName$
$phoneNumbers {
$with it {
$type$ phone: $number$
}$
}$
}$
}$
In this academic example the amount of the template text for the example with with is more. However, in real world where you need to work with deeply nested java objects, the with directive saves time and makes the template code read cleaner.
We can also use set directive to create an alias. The same code above written using set :
$addressBook.addresses {
$set address to it
Name: $address.firstName$ $address.lastName$
$address.phoneNumbers {
$with it {
$type$ phone: $number$
}$
}$
}$
Conditionals
There will be times in when you want to conditionally emit text. For those cases SimpleTemplate supports if-else and if constructs. Most expressions can be used as conditions to test in a if statement. An object that evaluates to null is false. A Collection, Map, Array and String returns false if it is empty. All other cases the expression evaluates to be true.
Using conditionals you can check whether a Collection is empty before you process it:
This address book belongs to: $addressBook.belongsTo$
$ifelse $addressBook.addresses {
$addressBook.addresses {
$set address to it
Name: $address.firstName$ $address.lastName$
$address.phoneNumbers {
$with it {
$type$ phone: $number$
}$
}$
}$
}
{
"No address book does not have any entries"
}$
Block Statements
SimpleTemplate supports two types block statements. You have already
seen one of them - the looping constructs and conditional examples use {}$ blocks. The other type of block statement starts with ${ and ends with }$. The difference between these two kinds of block statements is the way they are evaluated. The {}$ block is evaluated as text and it escapes into the template mode when it sees any one of the template directives. The ${ block is evaluated in template syntax. When SimpleTemplate reads a template it starts evaluating in text mode.
For examples,
${ "Hello World" }$
evaluates to Hello World where as,
${ {"Hello World"}$ }$
evaluates to "Hello World" (Note the double quotes around the text). You can use {}$ blocks only in a template context because in Text context the { will be considered as text.
Defining and Invoking Methods
When you are defining templates, there are times the same template code
is repeated over and over again. You can use methods to reduce the
repetition and make the template code clearer.
For example,
$printAddressDetails (address) {
<address>
<firstname>$address.firstName$</firstname>
<lastname>$address.lastName$</lastname>
<email>$address.email$</email>
</address>
}$
Defines a method that takes one parameter - address. You can invoke this method by:
$:printAddressDetails(address)$
You can also invoke this method by,
$address:printAddressDetails()$
When you invoke the method using the first form the evaluated value of the expression before : is passed to the method as the first parameter.
Your methods can take any number of parameters. SimpleTemplate matches
the number of parameters and throws an exception if they do not match.
You can also chain multiple methods - each one taking the return value
of the previous one. So,
$italics(s) ${ "<i>" $s$ "</i>" }$
$bold(s) ${ "<b>" $s$ "</b>" }$
${ $set hello to "Hello" $hello:bold():italics()$ }$
Gives an out put of "<i><b>Hello</b></i>".
Reusing templates using include
The ultimate reuse is however, to use a complete template or methods in
another template. SimpleTemplate provides a inclusion mechanism to
achieve this.
File: address.st
$printAddressDetails (address) {
<address>
<firstname>$address.firstName$</firstname>
<lastname>$address.lastName$</lastname>
<email>$address.email$</email>
</address>
}$
File: template.st
$include "address.st"
$addressBook.addresses {
$it:printAddressDetails()$
}$
With both the files in the same directory, processing template.st includes the definition of printAddressDetails method and the result will contain the address details.
Next Steps
This concludes this tutorial. The downloads includes the examples that
are discussed in this tutorial in the examples directory along with a
build file. You can find help and support on SimpleTemplate google groups.
Just one more thing...
When developing SimpleTemplate I started with a class heirarchy that
contained a TemplateElement, CompositeTemplateElement,
SimpleTemplateElement etc. In the end, clubbing all of them together
made better sense. So everything in SimpleTemplate is a TemplateElement
- either it is a block statement or an if statement. And any of them
can be used any where. Play around - it is fun to find things that work
and that do not work (because the Parser imposes restrictions).