This Java template engine is based on the following concepts:
Data model
The FreeMarker data model has a tree structure and is created from a predefined root called ".data_model" (click here for more details).
Each item of data is a tree node and is identified by a name (names are unique for child nodes of a given node, and a name is case sensitive).
If a node is terminal (leaf node), the data it contains is simple type.
The simple (or scalar) types in FreeMarker are as follows:
- String (alphanumeric string),
- Number (whole or decimal number),
- Boolean (true or false boolean value),
- Date (a date: year, month, day),
- Time (a time: hour, minute, second),
- Date-time (a timestamp: date + time + millisecond).
If a node is non-terminal, the data it contains is complex type.
The complex (or composite) types in FreeMarker are as follows:
- Hash (hash table or associative array),
- Sequence (list or array of values).
The ".data_model" root is Hash type. It is used to access first-level node values by their name.
Each node is defined by a path and this path is used to access the node value: we start with the root node and separate each node name with a dot ".".
In a FreeMarker template, the data model is accessed via the ".data_model" variable and data is accessed via the "sub-variables" defined by their "dot" notation in the data model:
Data model: |
|
Access to "sub-variables": |
|
Expression |
Value |
.data_model.person .data_model.person.name .data_model.person.age .data_model.person.isMarried .data_model.birthdate .data_model.hobbies .data_model.hobbies[0] .data_model.hobbies[1] .data_model.hobbies[2] |
Hash object |
As FreeMarker is implemented in Java language, here is a table of correspondences between Java types and the associated FreeMarker types:
Simple types | |
Java Type |
FreeMarker type |
String |
String |
Short, Integer, Long, Double, BigDecimal |
Number |
Boolean |
Boolean |
java.sql.Date |
Date |
java.sql.Time |
Time |
java.sql.Timestamp |
Date-time |
Complex types | |
Map<String, Object> |
Hash |
List< >, <>[] |
Sequence |
|
↑ Top of page
Template
A template is a text document which can be seen as a program written in FTL (FreeMarker Template Language).
It is a mixture of the following sections:
- Text: these sections made up of text will be displayed in the result document as they stand (including formatting characters such as carriage returns, tabulation, etc.).
- Interpolation: these sections will be replaced by a calculated value in the output. The interpolations are delimited by "${" and "}",
- FTL tags: FTL tags are quite similar to XML tags but are considered as instructions for FreeMarker and will not be displayed in the output.
- Comments: the comments are similar to XML comments but are delimited by <#-- and -->. The comments will be ignored by FreeMarker and not written in the output.
A template is identified by its name.
When it is executed for the first time, FreeMarker transforms the FTL program (text document) into Java code which is kept in the memory (in a cache), improving execution performance (one single read and "compilation" of the program for all executions).
In the APE context, a FreeMarker template is a text file with the extension ".ftlx". This file must be encoded in UTF-8. It will contain the following sections:
- Text: these sections will contain the static parts of the result document and will be written in XSL-FO language,
- Interpolation: these sections will display the data model data in certain XSL-FO tags (the dynamic parts) of the result document,
- FTL tags: these can make the display of data conditional, browse data sequences and manage their display, etc. in the result document,
- Comments: as a template is a program, comments need to be inserted to facilitate its maintenance.
Template example:
Interpolation | Text | FLT tags | |||||
Comment | Line break |
↑ Top of page
Template loader
Template lookup is delegated to one or more loaders (template loaders).
Once it is found, the loader sends the template content (the text document).
A template loader is defined by its storage type (lookup in a file system, in the Java "classpath" and associated .JAR files, in the RAM memory, in a database, etc.) and by a search root (a directory, Java package name, etc.).
FreeMarker identifies a template according to:
- its name: in the APE context, it is a template file name (.ftlx extension included);
- a namespace: a template group space. Namespaces can be compared with folders: they behave in an identical way to a folder, there can be elements of the same name in different namespaces. A namespace can also contain other namespaces;
- from a locale (in IETF BCP 47 format): by default, FreeMarker looks to see if there is a template associated with a locale. If it does not exist, the template without a locale is chosen. Furthermore, this locale will become the default locale used when processing the template: data (Number or Date/Time/Date-time type) will be displayed or external strings will be looked up according to this locale.
The meaning of the namespace depends on the template loader used:
- If the loader has a "file system" storage type, the namespace must be seen as a directory defined by a sub-path relative to the search root.
- If the loader has a "classpath" storage type, the namespace must be seen as a Java package defined by a sub-package relative to the search root.
A namespace may be empty.
FreeMarker takes the search root and namespace resolution as a search location.
Example 1: "File system" template loader
Template loader configuration (freemarker.properties) |
template_loader=com.hardis.adelia.mergedocengine.freemarker.conf.FileTemplateLoader(java.io.File ("c:/myTemplates") |
Namespace |
"" |
Deduced search location |
"c:/myTemplates" |
Example 2: "File system" template loader
Template loader configuration (freemarker.properties) |
template_loader=com.hardis.adelia.mergedocengine.freemarker.conf.FileTemplateLoader(java.io.File ("c:/myTemplates") |
Namespace |
"bills" |
Deduced search location |
"c:/myTemplates/bills" |
Example 3: "File system" template loader
Template loader configuration (freemarker.properties) |
template_loader=com.hardis.adelia.mergedocengine.freemarker.conf.FileTemplateLoader(java.io.File ("c:/myTemplates") |
Namespace |
"bills/providers" |
Deduced search location |
"c:/myTemplates/bills/providers" |
Example 4: "classpath" template loader
Template loader configuration (freemarker.properties) |
template_loader=com.hardis.adelia.mergedocengine.freemarker.conf.ClassTemplateLoader(com.hardis.adelia.mergedocengine.ProcessTemplate.CLASS_FOR_OBJECT_BUILDER_EXPRESSION, "/myTemplates") |
Namespace |
"" |
Deduced search location |
"/myTemplates" package from the content of the Java CLASSPATH system variable |
Example 5: "classpath" template loader
Template loader configuration (freemarker.properties) |
template_loader=com.hardis.adelia.mergedocengine.freemarker.conf.ClassTemplateLoader(com.hardis.adelia.mergedocengine.ProcessTemplate.CLASS_FOR_OBJECT_BUILDER_EXPRESSION, "/myTemplates") |
Namespace |
"bills" |
Deduced search location |
"/myTemplates/bills" package from the content of the Java CLASSPATH system variable |
Example 6: "classpath" template loader
Template loader configuration (freemarker.properties) |
template_loader=com.hardis.adelia.mergedocengine.freemarker.conf.ClassTemplateLoader(com.hardis.adelia.mergedocengine.ProcessTemplate.CLASS_FOR_OBJECT_BUILDER_EXPRESSION, "/myTemplates") |
Namespace |
"bills/providers" |
Deduced search location |
"/myTemplates/bills/providers" package from the content of the Java CLASSPATH system variable |
In the APE context, a loader is preconfigured, which enables runtime templates (Hardis FreeMarker libraries) to be used to execute user templates.
The user must also
configure his/her own loader(s) according to his/her template storage type (in a directory, in a .JAR file, etc.).
↑ Top of page
Locale
This is used to particularize the template to be looked up according to the information making up the locale (the language code, country or region code and variant). In order to associate a "foo" template with a given locale, the template file needs to be named "foo_language code _country or region code _variant.ftlx".
The file lookup order and strategy is as follows (same as the Java .properties file lookup):
- "foo_language code_ country or region code _variant.ftlx" file lookup,
- "foo_language code_ country or region code.ftlx" file lookup,
- "foo_language code.ftlx" file lookup,
- "foo.ftlx" file lookup.
Example 1:
Template loader configuration (freemarker.properties) |
template_loader=com.hardis.adelia.mergedocengine.freemarker.conf.FileTemplateLoader(java.io.File ("c:/myTemplates") |
Namespace |
"bills" |
Template name |
"foo_bar.ftlx" |
Locale |
"en-US" |
Template files looked up |
"c:/myTemplates/bills/foo_bar_en_US.ftlx" |
Example 2:
Template loader configuration (freemarker.properties) |
template_loader=com.hardis.adelia.mergedocengine.freemarker.conf.ClassTemplateLoader(com.hardis.adelia.mergedocengine.ProcessTemplate.CLASS_FOR_OBJECT_BUILDER_EXPRESSION, "/myTemplates") |
Namespace |
"bills" |
Template name |
"foo.ftlx" |
Locale |
"sr-Latn-RS" |
Template files looked up |
"/myTemplates/bills/foo_sr_Latn_RS.ftlx" |
↑ Top of page
Furthermore, as described in the previous paragraph, the locale provided to the loader to look up a template will become the default locale used when processing it: data (Number or Date/Time/Date-time type) will be displayed or external strings will be looked up according to this locale.
FTL language
The FTL language is used to manipulate interpolations and call directives.
Interpolation
The format of an interpolation is ${expression} where expression can be any type of expression (for example ${100 + x}).
An interpolation is used to insert the value of the expression converted into text in the result document. Interpolations can only be used in two places: in the text sections (for example, <fo:block> Hello $ {name}! </fo:block>), and in string literal expressions (for example, <#include "/sub_templates/${section3}.ftlx">).
The result of an expression must be FreeMarker String, Number, Date, Time or Date-time char. type.
By default, only these types are automatically converted into an alphanumeric string. Values of other types (Hash or Sequence) must be converted "manually" (i.e. by browsing the values contained in these types to return a text result).
Click here for more information.
Directive
A directive is an FTL language instruction. It is described in the form of a pair of tags:
- An opening tag <#directiveName parameters>,
- A closing tag </#directiveName>.
Everything between the opening and closing tags of a macro is the macro's nested content.
When a directive does not have a closing tag (so no nested content), it is only written with its opening tag. Click here for more information.
There are two different types of directive:
- Predefined directives, i.e. those of the FTL language,
- User directives, i.e. those written by the user.
Use "@" instead of "#" to call the directives used by the user.
For example: <@mydirective parameters> ... </ @ mydirective>.
Another difference is that if the directive has no nested content, you must use the notation <@mydirective parameters />.
There are two different types of user directive:
- Macros: these processing units are defined using the "macro" directive,
- Functions: these processing units are defined using the "function" directive.
A macro is template fragment associated with a variable. You can use this variable in your template as a directive defined by the user, which makes repetitive tasks easier.
For example, here is an example of creating a macro variable which displays "Hello Joe!":
<#macro greet>
<fo:inline font-size="x-large">Hello Joe!</fo:inline>
</#macro>
The "macro" directive itself does not display anything; it simply creates the macro variable, so there will be a variable named "greet".
The elements between <#macro greet> and </#macro> (called macro definition body) will only be executed when you use the variable as a directive.
You use directives defined by the user by writing @ instead of # in the FTL tag syntax.
Use the variable name as the directive name. The end tag for the directives defined by the user is mandatory.
Here is an example of a "greet" macro call:
<@greet></@greet>
But as this directive has no nested content in the call for it, it is preferable to write:
<@greet/>
Which will produce as an output in the result document:
<fo:inline font-size="x-large">Hello Joe!</fo:inline>
It is also possible to define parameters for the macro. They must be defined after the macro name in the "macro" directive:
<#macro greet person>
<fo:inline font-size="x-large">Hello ${person}!</fo:inline>
</#macro>
You can then use this macro as follows:
<@greet person="Fred"/> and <@greet person="Batman"/>
Which will produce as an output in the result document:
<fo:inline font-size="x-large">Hello Fred!</fo:inline> and <fo:inline font-size="x-large">Hello Batman!</fo:inline>
Finally, it is possible to extract and process a macro's nested content, i.e. the text between the macro call start and end tags:
<@greet>This is the nested content of the </@greet> macro call
The <#nested> directive executes the template fragment between the start and end tags of a user directive (click here for more details).
So, if we define a macro as follows:
<#macro nestedContentInMacro>
This text '<#nested>' was into macro call!
</#macro>
You can then use this macro as follows:
<@nestedContentInMacro>Help, I'm a nested text </@nestedContentInMacro>
Which will produce as an output in the result document:
This text 'Help, I'm a nested text ' was into macro call!
A function is a particular macro case: this processing unit has no nested content and is used to return a value in the output. A function may be called either in an interpolation or in another directive.
For example: creation of a function which calculates the average of two numbers
<#function avg x y>
<#return (x + y) / 2>
</#function>
This function may be called as follows:
${avg(10, 20)}
Or like this:
<#local avgNumber = avg(10, 2)>
An .ftlx template which only contains macros and functions defined by the user can be written.
This template may be seen as a module or library: it is used as a logical grouping of processing units and does not produce a result document.
A module template module may be included in a document template using the <#import> directive.
This takes the following as a parameter:
- the access path to the module template, relative to the location of the document template which includes it,
- a namespace used as a prefix when calling a macro or module function in the document template.
Click here for more information.