Toxic – A General Purpose Template Engine

Licensed under the MIT License

Table of Contents

Description

Toxic is a general purpose template engine, i.e. it produces text output from different template input formats, e.g. for text-, XML- or SQL-templates. New template formats can easily be integrated an even be mixed. One can use text-template format for attributes and text content in XML-templates.

Dynamic aspects of template generation are handled by the hosting programming language, currently only Java. This is a fundamental design decision for Toxic, leading to

  • clean separation of logic and presentation and
  • avoiding a proprietary template language to be learned.

A more detailed discussion of that topic can be found here.

Examples

To give a quick impression of what Toxic can do for you now come some – hopefully – helpful examples. We have text templates e.g. for code generation, XML templates most likely used for xHTML and a little tool for making SQL with JDBC easier.

Nested Code Templates

The following is a template for a Java class with properties. Comment lines like // >>> ph-name <<< represent a placeholder named ph-name where the generator code can place some dynamic content – something that is computed by the generator. Pairs of comment lines starting with // >>> tmpl-name >>> and ending at // <<< tmpl-name <<< are named sub-templates. Sub-templates are not part of their containing templates but can be placed there for convenience. This way the whole template file can resemble a real Java code file.

E.g. the sub-template property contains line 8–10 and 27–28, where line 10 is a placeholder named setter. One can use the sub-templates pojo or bean to generate the content for setter.

In this example, the lines of the template are not taken literally from the template but are parsed by the TextTemplateParser with the '`' character (backtick) to mark placeholders (see Text Templates). I.e. line 9 contains three placeholders type, Name and name which will be replaced by the code generator. Note: Name and name are used to reflect Java naming conventions.

 1:  // >>> copyright <<<
 2:  // >>> package <<<
 3:  
 4:  // >>> import <<<
 5:  
 6:  public class `classname` {
 7:  // >>> property >>>
 8:     
 9:     public `type` get`Name`() { return `name`; }
10:     // >>> setter <<<
11:     // >>> pojo >>>
12:     
13:     public void set`Name`( `type` value )
14:     {
15:        this.`name` = value;
16:     }
17:     // <<< pojo <<<
18:     // >>> bean >>>
19:  
20:     public void set`Name`( `type` value )
21:     {
22:        final `type` old = get`Name`();
23:        this.`name` = value;
24:        pcs.firePropertyChange( "`name`", old, this.`name` );
25:     }
26:     // <<< bean <<<
27:  
28:     private `type` `name``init`;
29:  // <<< property <<<
30:  // >>> bean >>>
31:     
32:     private final PropertyChangeSupport pcs = new PropertyChangeSupport( this ); 
33:  // <<< bean <<<
34:  // >>> classbody <<<
35:  }

An now, here is the code to generate a Java class from that template. We will start with the plain POJO class:

 1:  Template tClass = bups.get( tp, UTF8, "class" );
 2:  Bount bClass = new Bount( tClass );
 3:  bClass.bind( "copyright", "// Copyright © 2012 Marcus Perlick" );
 4:  bClass.bind( "package", "package toxic.example;" );
 5:  bClass.bind( "import", "import java.io.File;" );
 6:  bClass.bind( "classname", "SomeNewClass" );
 7:  
 8:  Template tProp = bups.get( tp, UTF8, "class", "property" );
 9:  Template tSetter = bups.get( tp, UTF8, "class", "property", "pojo" );
10:  Bount bProp = new Bount( tProp );
11:  bProp.bind( "setter", new Bount( tSetter ) );
12:  tProp = bProp.fix();
13:  bProp = new Bount( tProp );
14:  bProp.bind( "name", "foo" );
15:  bProp.bind( "Name", "Foo" );
16:  bProp.bind( "type", "String" );
17:  bProp.bind( "init", " = \"\"" );
18:  
19:  bClass.bind( "classbody", bProp );
20:  bClass.write( System.out );

In line 1 and 2 we use a TemplateSet to get the class template from the file, which is to top-level template content. Templates are designed to be reused with different content for the placeholders. So we create a Bount (bound template) object that is used to bind content to the templates placeholders. In line 3–6 we bind different strings as content to all placeholders but classbody.

The interesting this is, that a template that has all its placeholders bound to some content can not only be written as output but also is content itself. So we will use the property sub-template to generate a property into the class body:

In line 8 and 9 we load the template property and its sub-template pojo to generate a property with a simple POJO setter. In line 10 we create another bound template bProp where we bind the setter placeholder to a bound object for the POJO setter template. Note that the POJO setter has unbound placeholders and that we don't keep its Bount object. Instead we call the fix() method of the bound property template which creates us a new flattened template that has all the placeholders that were not yet bound. I.e. after line 12 tProp holds a template like this:

public `type` get`Name`() { return `name`; }

public void set`Name`( `type` value )
{
   this.`name` = value;
}

private `type` `name``init`;

In line 13–17 we bind some reasonable strings to the still unbound placeholders and the output is:

// Copyright © 2012 Marcus Perlick
package toxic.example;

import java.io.File;

public class SomeNewClass {
   
   public String getFoo() { return foo; }
   
   public void setFoo( String value )
   {
      this.foo = value;
   }

   private String foo = "";
}

To use just the bean setter from our original template we have to replace some lines in our generator code:

 1:  Template tProp = bups.get( tp, UTF8, "class", "property" );
 2:  Template tSetter = bups.get( tp, UTF8, "class", "property", "bean" );
 3:  Bount bProp = new Bount( tProp );
 4:  bProp.bind( "setter", new Bount( tSetter ) );
 5:  tProp = bProp.fix();
 6:  bProp = new Bount( tProp );
 7:  bProp.bind( "name", "foo" );
 8:  bProp.bind( "Name", "Foo" );
 9:  bProp.bind( "type", "String" );
10:  bProp.bind( "init", " = \"\"" );
11:  Bount bBean = new Bount( bups.get( tp, UTF8, "class", "bean" ) );
12:  
13:  bClass.bind( "classbody", concat( bProp, bBean ) );
14:  bClass.write( System.out );
15:  System.out.flush();

But then we would get the result:

// Copyright © 2012 Marcus Perlick
package toxic.example;

import java.io.File;

public class SomeNewClass {
   
   public String getFoo() { return foo; }

   public void setFoo( String value )
   {
      final String old = getFoo();
      this.foo = value;
      pcs.firePropertyChange( "foo", old, this.foo );
   }

   private String foo = "";   
   private final PropertyChangeSupport pcs = new PropertyChangeSupport( this ); 
}

XML Templates

XML templates are inspired by web frameworks like Apache Wicket or Scala's Lift where templates are normal XML documents peppered with some attributes where the template engine can be hooked in:

 1:  <?xml version="1.0" ?>
 2:  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 3:            "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 4:  <html xmlns="http://www.w3.org/1999/xhtml"
 5:        xmlns:toxic="http://fractalqb.de/toxic/xml-template">
 6:  <head>
 7:  <title>Beschreibung der Seite</title>
 8:  </head>
 9:  <body>
10:  <table>
11:  <tr><th>Name</th><th>#</th><th>Price</th><th>Total</th></tr>
12:  <tr toxic:replace="body" toxic:template="row">
13:    <td toxic:fill="name">product name</td>
14:    <td toxic:fill="count">number of products</td>
15:    <td toxic:fill="ppu">price per unit</td>
16:    <td toxic:fill="total">total price</td>
17:  </tr>
18:  </table>
19:  </body>
20:  </html>

Loading this template into a browser looks like this. What we want to do with this template is to take the page including the table and its heading from the template and then to add an arbitrary number of rows. To generate the rows we use the one sample row that is also part of the template.

Like in the previous example we have nested templates:

  • The main template is the document itself containing lines 1–11, 18–20 and a little of line 12.
  • The nested template called row containing lines 12–17.

The attribute toxic:replace in line 12 introduces a placeholder named body into the global template that replaces the complete element of the toxic:replace attribute. I.e. the example row is not part of the global template but instead of it we can fill in some content.

The other attribute toxic:template in line 12 makes a template of the <tr> element that we can use to create table rows to fill the body placeholder in the global template1.

In line 13–16 we see another kind of placeholder definition, the toxic:fill attribute. In contrast to toxic:replace this one does not discard its complete element but only the child nodes of the element. Also all non-toxic attributes of the elements are preserved. This way we can replace the text content of the <td/> elements with some other content. Replacing attribute content is described in the documentation of XML templates.

To fill in some lines into this table template we use this code:

 1:  public static void main( String[] args ) throws Exception
 2:  {
 3:      TemplateSet tset = mkTSet(); // creates a template set
 4:      XmlTemplateParser xtp = new XmlTemplateParser();
 5:      Template tPage = tset.get( xtp, Charset.forName( "UTF-8" ), "templates", "xml", "table1" );
 6:      Template tRow = tset.get( xtp, Charset.forName( "UTF-8" ), "templates", "xml", "table1", "row" );
 7:      Bount bPage = new Bount( tPage );
 8:      BeanContent<BillPos> bRow = new BeanContent<>( tRow, BillPos.class,
 9:                                                     "article" ,"name",
10:                                                     "number", "count",
11:                                                     "pricePerUnit", "ppu",
12:                                                     "totalAmount", "total" );
13:      bRow.escape( XmlEscAsciiBased.FACTORY, "name" );
14:      ListContent<BillPos> bTable = new ListContent<>( bRow );
15:      bPage.bind( "body", bTable );
16:      List<BillPos> model = new ArrayList<>();
17:      for ( int i = 0; i < 10; i++ )
18:          model.add( new BillPos() );
19:      bTable.link( model );               
20:      bPage.write( System.out );
21:      System.out.flush();
22:  }

Here we use two templates tPage for the HTML document and tRow for a single row in the table. To demonstrate linking between Java objects and templates we use a Java Bean BillPos that has all the data we need to create a single line of the table. In line 8-12 we use a BeanContent object to bind the bean properties article, number, pricePerUnit and totalAmount to the placeholders name, count, ppu and total. To make sure that the strings that go into the name placeholder are escaped for XML output, we add an escape object to that placeholder. One could also escape the other placeholders but we are sure that the numbers from the bean don't need to. Because we want to generate several table rows we use a List<BillPos> as our model (line 16-18). Such a model can be used as content through a ListContent<> object as shown in line 14, 15 and 19. The xHTML that is produced by this example looks like this.

SQL Templates & JDBC

A more exotic application for a template engine might be to remove SQL strings from your Java code when working with plain JDBC. Let's say you start with a simple DAO class like this:

public class PersonDao
{    
    private DataSource dataSource;

    public Person getPersonById( int id ) throws SQLException
    {
        try ( Connection db = dataSource.getConnection() ) {
            PreparedStatement stmt = db.prepareStatement(
                     "select called, surname "+
                     "from test.person "+
                     "where id = ?" );
            stmt.setInt( 1, id );
            ResultSet ress = stmt.executeQuery();
            if ( ress.next() ) {
                return new Person( ress.getString( 2 ),
                                   ress.getString( 1 ) );
            }
            else {
                return null;
            }
        }
    }
}

Besides the question if it wouldn't be better to use some sophisticated ORM like JPA or JDO or even MyBatis there may be simple applications that only have to read some records from a database and nothing else for which the full featured frameworks would be overkill. If you don't believe that something like that could exist… you are not forces to read on.

However, the problem with code like the example above is that it is not "easy" to maintain. I.e. if one changes the SQL string the position parameters in the stmt.setXXX() and ress.getXXX() calls have to be adopted. Especially for more complex statements this is a tedious and error prone task.

First we create a sql file that will contain all our SQL statements for the PersonDao class. We call it PersonDao.sql and put it into the same folder as the class, so that the DAO class can easily load it as a resource:

-- >>> select-by-id >>>
select :<called:, :<surname:
from :schema:.person
where id = :>id:
-- <<< select-by-id <<<

To parse the SQL file we use the same combination of template parsers as in the Nested Code Templates example. As delimiters for the placeholders in the TextTemplateParser we now use ':' instead of '`'. Placeholder names starting with '>' are our input parameters to the statement, those starting with '<' are output parameters and the rest is not handled by the SqlTool. Note that we use such a placeholder schema to select the schema that holds the table in the DAO not in the template.

But now let's see how the DAO code changes to make use of such a template. Actually it will be more code because we have to initialize some data about the SQl statement:

1:  public class PersonDao
2:  {
3:      private final String sqlSelectById;
4:      private final int[] inSelectById_id;
5:      private final int outSelectById_called;
6:      private final int outSelectById_surname;
7:      
8:      private DataSource dataSource;
9:  ...

First we need some members where we can keep the data.

sqlSelectById
holds the SQL statement for the selectById() method.
inSelectById_id
holds all the indices where we have to set the id parameter.
outSelectById_called
holds the index to get the content of column called.
outSelectById_surname
holds the index to get the content of column surname.

For brevity we do all the initialization in the constructor:

10:  ...
11:      public PersonDao() throws IOException
12:      {
13:          TemplateParser tp = SqlTool.makeTemplateParser();
14:          InputStream is = getClass().getResourceAsStream( getClass().getSimpleName()+".sql" );
15:          Template sqt = tp.read( "select-by-id", is, null, Charset.defaultCharset(), 0, "select-by-id" );
16:          is.close();
17:          Bount sqb = new Bount( sqt );
18:          sqb.bind( "schema", "testdb" );
19:          sqt = sqb.fix();
20:          SqlTool sqlTool = new SqlTool( sqt );
21:          sqlSelectById = sqlTool.getSql();
22:          inSelectById_id = sqlTool.getInIndices( "id" );
23:          outSelectById_called = sqlTool.getOutIndex( "called" );
24:          outSelectById_surname = sqlTool.getOutIndex( "surname" );
25:      }
26:  ...

In line 13 we create a new TemplateParser to load the template. In line 14 we open the template file as input stream. Line 15 reads the template. Line 16–18 do the dynamic SQL thing so that the table is located in the DB schema testdb.

In line 20–24 we use the SqlTool to get the SQL statement and all the indices of the in- and output parameters.

MemberValue
sqlSelectByIdselect called, surname from testdb.person where id = ?
inSelectById_id[ 1 ]
outSelectById_called1
outSelectById_surname2

With these things at hand we can rewrite the getPersonById() method to something more maintainable:

27:  ...
28:      public Person getPersonById( int id ) throws SQLException
29:      {
30:          try ( Connection db = dataSource.getConnection() ) {
31:              PreparedStatement stmt = db.prepareStatement( sqlSelectById );
32:              SqlTool.bind( stmt, inSelectById_id, id );
33:              ResultSet ress = stmt.executeQuery();
34:              if ( ress.next() ) {
35:                  return new Person( ress.getString( outSelectById_surname ),
36:                                     ress.getString( outSelectById_called ) );
37:              }
38:              else {
39:                  return null;
40:              }
41:          }
42:      }
43:  }

There is nothing very exiting about this except that we

  • don't have the SQL string in the code.
  • don't have to do tedious position counting of column manes or parameter indices.
  • can also use the templates for dynamic SQL.

One thing to mention is line 32 which is nothing else than some convenience function for:

for ( int i : inSelectById_id )
    stmt.setInt( i, id );

Footnotes:

1 When toxic:replace and a toxic template definition are together in one element the replace is not part of the defined template but of the parent template.

Author: Marcus Perlick

Date: May 25, 2014

HTML generated with emacs org-mode & Toxic by [qb]