the properties file

It contains the messages used to render the template. It facilitates internationalization.

properties file static_message=hi! dynamic_message=hello {0} dynamic_message_with_choices=The disk {0} contains {1,choices,0#no file|1#one file|1<{0,number,integer} files}.

the model

It is a simple Map of key-value pairs where:

Each tag (see syntax below) is replaced in the template by a computed value (using one or more keys).

If a key is not present in the template, an exception is thrown. An optional flag permits to render the page even if the key is not present. In this case, the tag is replaced by nothing...

java code model = new HashMap(); model.put("firstname", "john"); model.put("which_message", "dynamic_message"); model.put("disk_name", "myDisk"); final ArrayList mylist = new ArrayList(); mylist.add(new HashMap() {{ put("total", 0); }}); mylist.add(new HashMap() {{ put("total", 1); put("disk_name", "yourDisk"); }}); mylist.add(new HashMap() {{ put("total", 1234); }}); mylist.add(new HashMap() {{ }}); model.put("mylist", mylist);

the filters

The filters allow to render a value by applying specific code.

You can apply one or more filters on a key (see syntax below).

java code filters = new HashMap<String, Filter>(); // interface Filter : String to String filters.put("upper", new Filter() { public String apply(String value) { return value.toUpperCase(); } }); filters.put("lower", new Filter() { public String apply(String value) { return value.toLowerCase(); } }); filters.put("quote", new Filter() { public String apply(String value) { return "'" + value + "'"; } }); // interface ObjectFilter : InType to OutType filters.put("size", new ObjectFilter<Collection,Integer>() { public Integer apply(Collection value) { return value.size(); } }); filters.put("gt0", new ObjectFilter<Integer,Boolean>() { public Boolean apply(Integer value) { return value.intValue() > 0; } }); filters.put("add", new ObjectFilter<Integer[],Integer>() { public Integer apply(Integer values[]) { int sum = 0; for (value : values) { sum += value.intValue(); } return sum; } });

The input type of the next filter must be the output type of the previous filter except if the input type is String.

In this case, the .toString() method is called to pass data to the next filter.

Finally, if the output type is an array, the input type must also be an array.

the templates

a template without section

withoutsection.tpl This is a file without section. ~'dynamic_message[firstname]~ Bye ~firstname:upper~

java code template = new Template("withoutsection.tpl", filters, messages, Locale.ENGLISH); OutputStreamWriter out = new OutputStreamWriter(System.out); template.printFile(out, model); out.flush();

a template with one or more sections

withsection.tpl <!-- #section section1 --> This is a file with one or more sections. ~'dynamic_message[firstname]~ <!-- #section section2 --> Bye ~firstname:upper~

java code template = new Template("withsection.tpl", filters, messages, Locale.ENGLISH); OutputStreamWriter out = new OutputStreamWriter(System.out); template.printSection(out, "section1", model1); // model1 = model.clone() template.printSection(out, "section2", model2); // model2 = model.clone() out.flush();

tag syntax with examples

syntax by example model (with python syntax) result description
~~
{ }
            
~ Protects the '~' character.
~firstname~
{ 'firstname': 'john' }
            
john Replace the tag firstname by the value associated to the key firstname contained in the model.
~firstname:upper:quote~
{ 'firstname': 'john' }
            
'JOHN' Replace the tag and applyies the filters.
~lastname?~
{ }
            
  Allow to render the page even if the key lastname is not present in the model.
Without the '?' character, an exception is thrown.
~'static_message[]~
{ }
            
hi! Displays the static_message of the properties file.
~'dynamic_message[firstname]~
{ 'firstname': 'john' }     
            
hello john Displays the dynamic_message of the properties file using firstname value to render {0}.
~'dynamic_message[lastname?]~
{ }     
            
  Displays nothing because lastname is not in the model. Without the '?' character, an exception is thrown.
~'dynamic_message[firstname:upper]:quote~
{ 'firstname': 'john' }     
            
'hello JOHN' Same as above. Puts firstname in uppercase and quotes the result.
~which_message[firstname]~
{ 'firstname': 'john',
  'which_message': 'dynamic_message' }     
            
hello john Displays the which_message (i.e. dynamic_message) using firstname value to render {0}.
~message?[firstname]~
{ 'firstname': 'john' }     
            
Displays nothing because message is not in the model. Without the '?' character, an exception is thrown.
<ul>
~mylist#list~
<li>~'dynamic_message_with_choices[disk_name,total?]~</li>
~#list~
</ul>
{ 'disk_name': 'myDisk',
  'mylist': [ { 'total': 0 }, 
              { 'total': 1, 
                'disk_name': 'yourDisk' }, 
              { 'total': 1234 },
              { 'disk_name': 'yourDisk' }
            ] }     

<ul>
<li>The disk myDisk contains no file.</li>
<li>The disk yourDisk contains one file.</li>
<li>The disk myDisk contains 1,234 files.</li>
</ul>

Displays dynamic_message_with_choices for each model contained in the mylist value.
The disk_name and total are taken in the model of the list. They override the values at the same level than mylist.
The '?' character on the key total allows to render the page even total is not present in the model.
~boolean#true~~'dynamic_message[firstname]~~#true~
{ 'boolean': true, 'firstname': 'John' }
hello John Render the part between the start and the end tag if the boolean is True.
~boolean#false~~'dynamic_message[firstname]~~#false~
{ 'boolean': false, 'firstname': 'John' }
hello John Render the part between the start and the end tag if the boolean is False.
There are ~(list1:size,list2:size):add~ elements
{ 'list1: [ { 'name': 'John' }, { 'name': 'Jane' } ],
  'list2: [ { 'name': 'Paul' } ] }
There are 3 elements Call the add filter with an array composed by 2 integers, the size of the lists.
>>> Read the unit tests of the library for more examples... <<<

the tests

You can simply check that the requested sections are called with the expected models.

java code package temmental; import static temmental.TemplateUtils.createList; import static temmental.TemplateUtils.createModel; import java.io.IOException; import java.io.StringWriter; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import junit.framework.TestCase; public class TestTemplateTest extends TestCase { protected HashMap<String, ObjectFilter> filters; private HashMap<String, Object> model; protected Properties properties; protected StringWriter out; protected Template template; protected TemplateRecorder recorder; @Override protected void setUp() throws Exception { super.setUp(); template = new Template("test/test.tpl", filters, properties, Locale.ENGLISH); TemplateRecorder.setRecording(true); out = new StringWriter(); } @Override protected void tearDown() throws Exception { super.tearDown(); TemplateRecorder.setRecording(false); } public void testPrintFile() throws IOException, TemplateException { model = new HashMap<String, Object>(); model.put("k1", "v1"); model.put("k2", "v2"); template.printFile(out, model); HashMap<String, Object> expectedModel = new HashMap<String, Object>(); expectedModel.put("k1", "v1"); expectedModel.put("k2", "v2"); TemplateRecord record = TemplateRecorder.getTemplateRecordFor("test/test.tpl"); Map<String, ? extends Object> model = record.getModelForFile(); assertEquals(expectedModel, model); } public void testPrintSection() throws IOException, TemplateException { List<Map<String, Object>> list = createList( createModel("index", 0, "fruit", "orange"), createModel("index", 1, "fruit", "apple"), createModel("index", 2)); model = new HashMap<String, Object>(); model.put("fruits", list); model.put("firstname", "John"); model.put("lastname", "Doe"); template.printSection(out, "test", model); model = new HashMap<String, Object>(); model.put("fruits", list); model.put("firstname", "Jane"); model.put("lastname", "Doe"); template.printSection(out, "test", model); TemplateRecord record = TemplateRecorder.getTemplateRecordFor("test/test.tpl"); List<Map<String, ? extends Object>> models = record.getModelsForSection("test"); HashMap<String, Object> expectedModel = new HashMap<String, Object>(); expectedModel.put("firstname", "John"); expectedModel.put("lastname", "Doe"); expectedModel.put("fruits", list); assertEquals(expectedModel, models.get(0)); expectedModel = new HashMap<String, Object>(); expectedModel.put("firstname", "Jane"); expectedModel.put("lastname", "Doe"); expectedModel.put("fruits", list); assertEquals(expectedModel, models.get(1)); } }