These classes are in the main jar file. Suppose that you want to generate an HTML page containing data produced by your program, or write out an object in serialised form using XML. One way to do this is with successive print statements, but this tends to mix up the dynamic information you are really interested in with a fairly static skeleton. This hurts you both when you are concentrating on the code to write out the dynamic information, and when you find out that you need to change the static skeleton, to make the HTML page look nicer, or when you want to move to a different XML DTD or Schema.
Template generation puts the static framework into a template. This consists of chunks of static text interspersed with escape sequences which cause the substitution of dynamic information from outside the template, or some form of control flow. Heavy-duty versions of this include Java Server Pages, Active Server Pages, and PHP. These are fully-fledged programming languages, which become the main entry point of the program. What is presented here is different: I describe classes that can be used from within a program to generate chunks of text, without demanding that they become the main framework of the program.
Typically, you do the following:
A number of examples of this are in the test code for these classes, TemplateTest.java - which is again with the main jar file. Since it uses templates loaded in via Class.getResourceAsStream, example templates are also here, as TemplateTest.in1.. TemplateTest.in4.
You could do template generation without introducing a StringChunk object, using java.lang.String or java.lang.StringBuffer instead. The problem with this is that you end up doing a lot of string copying, especially if you end up using strings generated from one Template as input to another, which would otherwise be a very good way to generated highly variable nested XML or HTML. Worst case cost, even from StringBuffer, can be quadratic in the number of characters finally generated. Consider the following:
for (int i = 0; i < n; i++)
{
sb = new StringBuffer("x");
sb.append(old.toString());
old = sb;
}
Obviously, you would never write this, but something like this could happen
in the worst case during template generation.
StringChunk objects can be created from Strings, e.g. as StringChunk sc = new StringChunk.StringCarrier("String text"). They can also be appended as e.g. appendResult = stringChunk.append(stringChunk2). Appending StringChunks takes constant time, no matter the length of the StringChunks involved. It does not modify the StringChunks involved, but creates a new StringChunk to form the result, so there should be no problems with concurrency, or with appending one StringChunk to many others.
Once you (or the template generation code) have finished your sequence of appends and are happy with the result, you can call StringChunk.toString() or StringChunk.printAll(java.io.Writer) to produce a String, or write out a result. This takes time proportional to the length of the string produced or result written plus the number of chunks appended together.
The limited template generation provided here is based on a very simple data structure, which maps from a key to a sequence of values. As templates are expanded, they use the values in each sequence in order, until the sequence is exhausted. This makes it possible to write templates that produce lists of the values associated with a particular key. A ConsumableMap uses its ListIterators to move along the sequence of values associated with a particular key. To support more complex control structures in the templates, it is also possible to undo the consumption of values in a sequence. You can call mark() before attempting to extract the values associated with a number of keys. After extracting these values you can either call accept() to accept the consumption of these values, or reject() to restore the state of the map at the time mark() was called. Sequences of mark() and accept() or reject() nest. mark() and accept() take constant time. reject() takes time proportional to the number of values extracted since its nested mark (it is implemented by calling previous() to restore the state of each ListIterator from which a value was extracted using next()).
The escape character for these templates is '%'. It can be used as follows:
The following control sequences are defined:
You can define your own control sequences by inserting them as values in a Map from Strings to objects implementing the ControlTemplate interface and passing that Map as the second parameter to the two-parameter Template Constructor. When your ControlTemplate object is referenced in the process of template generation, the single method defined in the interface, tryGenerate() is called, passing your object the ConsumableMap, a TemplateRecord object constructed from the template text inside your object's control brackets, and a StringChunk object which is the entire text generated so far. If the keys you insert in the map collide with the existing control template keys described above (such as "fail" and "ignore") an IllegalArgumentException will be thrown during the construction of the Template.
%< Produce input element with default value where possible inside a table line: note loss of white space after final % in this line>%% <tr><td><label for="%:release(%$name%%)">%$label%</label></td> <td>%:try(%$mandatoryMarker%%)</td> <td><input type="%$type%" name="%$name%" %:try(value="%$value%"%) %:try(checked="%$checked%" %)/></td></tr>%