Object rendering

Note: You should also look at The Internationalization and Localization of Web Applications.

The atomic element of a template is a simple object that is rendered to text by its ToString() method. For example, an integer object is converted to text as a sequence of characters representing the numeric value written out. What if you wanted commas to separate the 1000's places like 1,000,000? What if you wanted commas and sometimes periods depending on the locale?.

Prior to 2.2, there was no means of altering the rendering of objects to text. The controller had to pull data from the model and wrap it on an object whose ToString() method rendered it appropriately.

As of StringTemplate 2.2, you may register various attribute renderers associated with object class types. Normally a single renderer will be used for a group of templates so that Date objects are always displayed using the appropriate Locale, for example. There are, however, situations where you might want a template to override the group renderers. You may register renderers with either templates or groups and groups inherit the renderers from super groups (if any).

There is a new abstraction that defines how an object is rendered to string:

Java

class AttributeRenderer

C#

interface IAttributeRenderer

Python

class AttributeRenderer

Here is a renderer that renders date objects tersely.

Java

public class DateRenderer implements AttributeRenderer {
	public String toString(Object o) {
		SimpleDateFormat f = new SimpleDateFormat("yyyy.MM.dd");
		return f.format(((Calendar)o).getTime());
	}
}
...
StringTemplate st =new StringTemplate(
		"date: <created>",
		AngleBracketTemplateLexer.class);
st.setAttribute("created", new GregorianCalendar(2005, 07-1, 05));
st.registerRenderer(GregorianCalendar.class, new DateRenderer());
String expecting = "date: 2005.07.05";
String result = st.toString();

C#

public class DateRenderer : IAttributeRenderer
{
	public string ToString(object o) {
		DateTime dt = (DateTime) o;
		return dt.ToString("yyyy.MM.dd");
	}
}
...
...
StringTemplate st =new StringTemplate("date: <created>",typeof(AngleBracketTemplateLexer));
st.SetAttribute("created", new DateTime(2005, 07, 05, New GregorianCalendar()));
st.registerRenderer(typeof(DateTime), new DateRenderer());
string expecting = "date: 2005.07.05";
string result = st.ToString();

Python

import stringtemplate3
from datetime import date

class DateRenderer(stringtemplate3.AttributeRenderer):
    def toString(self, o, format=None):
        return o.strftime("%Y.%m.%d")
...
...
st = stringtemplate3.StringTemplate("date: <created>", lexer="angle-bracket")
st["created"] = date(year=2005, month=7, day=5)
st.registerRenderer(date, DateRenderer())
expecting = "date: 2005.07.05"
result = str(st)

In the sample code above, date objects are represented as objects of type:

Java

Calender

C#

DateTime

Python

date

All attributes of the date types above in template st are rendered using the DateRenderer object.

Note: In light of the new format option the following paragraph should be revised.

You will notice that there is no way for the template to say which renderer to use. Allowing such a mechanism would effectively imply an ability to call random code from the template. In StringTemplate's scheme, only the model or controller can set the renderer. The template must still reference a simple attribute such as <created>. If you need the same kind of attribute displayed differently within the same template or group, you must pass in two different attribute types. This would be rare, but if you need it, you can easily still wrap an object in a renderer before sending it to the template as an attribute. For example, if you have a web site that allows editing of some descriptions, you will probably need both an escaped and unescaped version of the description. Send in the unescaped description as one attribute and send it in again wrapped in an HTML escape renderer as a different attribute.

As far as I can tell, this functionality is mostly useful in the web page generation realm rather than code generation; perhaps an opportunity will present it self though.

Format Option

There are cases where the template is the only reasonable place to determine what formatting needs to be applied to an attribute. For example, when generating HTML different characters need to be escaped in an attribute value than in element content. Only the template knows where it is going to put an attribute. Another, perhaps less likely, example would be a template that is rendering Java code that has SQL statements in Java strings. Attributes within the SQL statements will need different escaping.

The format option allows the template to decide what formatting to use where but leaves the details of how the formatting is done completely in the hands of the controller.

To make use of the format option you must create a renderer that implements interface AttributeRenderer and provides an implementation for the toString method that takes a formatName String.

Java

public class BasicFormatRenderer implements AttributeRenderer {
    public String toString(Object o) {
        return o.toString();
    }
    public String toString(Object o, String formatName) {
        if (formatName.equals("toUpper")) {
            return o.toString().toUpperCase();
        } else if (formatName.equals("toLower")) {
            return o.toString().toLowerCase();
        } else {
            throw new IllegalArgumentException("Unsupported format name");
        }
    }
}

Python

class BasicFormatRenderer(stringtemplate3.AttributeRenderer):
    def toString(self, o, formatName=None):
        if formatName is None:
            # no formatting specified
            return str(o)

        if formatName == "toUpper":
            return str(o).upper()
        elif formatName == "toLower":
            return str(o).lower()
        else:
            raise ValueError("Unsupported format name")

The renderer is registered with a group as previously shown. The renderer can do anything it likes to format the string. The toUpper and toLower cases are examples of what can be done. It is not required that an exception is thrown if the formatName is not supported you could also simply return the result of o.toString().

From a template you can now use any of the named formats supported by the registered renderers. For example:

$name;format="toUpper"$

The expression after the equal sign must resolve to a string that matches one of the strings that the renderer recognizes. There is no default value for the format option.

The format option can be combined with any of the other options. Format will apply to the value of the null option but not to the separator.

For example

$list : { [$it$] };format="toUpper",separator=" and ",null="woops"$

results in

[X] and [Y] and [WOOPS] and [Z]

when list contains "x", "y", null, "y" and toUpper is a supported format option of the available renderer for type String that returns the upper case input string. Note that the value of null was upper cased but the separator " and " was not.

If you really want the separator to be formatted then you must do this

${$list : { [$it$] };separator=" and ",null="woops"$};format="toUpper"$