Transforms implement the TemplateTransformModel interface.
You can find some useful TemplateTransformModel implementations in the freemarker.template.utility package.
Implementing your own transform
TemplateTransformModel interface contains one method: Writer getWriter(Writer out, Map args). This is called at the transform start-tag. Between the transform start-tag and end-tag the returned Writer will be used for printing to the output.
The returned Writer object does the actual transformation. It should write the transformed output to the Writer that was passed as the out parameter. When a flush directive is reached, the flush() method of the returned Writer will be called; it should write the contents of internal buffers into the out (if it uses any internal buffers) and after that it should call out.flush(), so the output will be send to the client. When the transform end-tag is reached, close() will be called. Don't call out.close().
Example: This is a possible implementation of a TemplateTransformModel that converts the text to uppercase:
 |  |  |
 |
import java.io.*;
import java.util.*;
import freemarker.template.TemplateTransformModel;
class UpperCaseTransform implements TemplateTransformModel {
public Writer getWriter(Writer out, Map args) {
return new UpperCaseWriter(out);
}
private class UpperCaseWriter extends Writer {
private Writer out;
UpperCaseWriter (Writer out) {
this.out = out;
}
public void write(char[] cbuf, int off, int len)
throws IOException {
out.write(new String(cbuf, off, len).toUpperCase());
}
public void flush() throws IOException {
out.flush();
}
public void close() {
}
}
} |
|  |
 |  |  |
If you put an instance of the above class into the data model:
 |  |  |
 | root.put("upperCase", new UpperCaseTransform()); |
|  |
 |  |  |
then this template:
 |  |  |
 |
blah1
<@upperCase>
blah2
blah3
</@upperCase>
blah4 |
|  |
 |  |  |
will output this:
Typically it is better practice to put commonly used transforms into the Configuration as shared variables.
It's important to realize, that if the transform is stateful, then the state must be stored in the Writer instance, not in the TemplateTransformModel instance itself. TemplateTransformModel instance should be stateless, basically just a factory of Writer objects. Think of nested blocks of the same transform, or shared variables accessed by multiple threads concurrently.
The returned Writer may implement freemarker.template.TransformControl interface. The methods on this interfaces are callbacks that will be called by the template engine and that give the writer a chance to better control the evaluation of the nested content (the template fragment between the <@myTransform> and </@myTransform>). The writer can instruct the engine to skip or to repeat the evaluation of the nested content, and gets notified about exceptions that are thrown during the nested content evaluation. For more information please read the API documentation.
If you need to access the runtime FTL environment (read/write variables, get the current locale, etc.), you can get it with Environment.getCurrentEnvironment().