
Introduction
FreshMarker is a simple, embedded Java 21 Template-Engine, which is inspired by FreeMarker. Most of the basic concepts are the same.
FreshMarker's area of application is the processing of simple templates with reduced programming features.
Despite reduced programming features, FreshMarker is open to new data types and built-ins.
In particular, the lack of modern data types like java.time.LocalDate
in FreeMarker was the reason for the implementation of FreshMarker.
Adding FreshMarker
Add FreshMarker as a dependency from Maven Central to your project.
<dependency>
<groupId>de.schegge</groupId>
<artifactId>freshmarker</artifactId>
<version>1.7.5</version>
</dependency>
implementation 'de.schegge:freshmarker:1.7.5'
implementation("de.schegge:freshmarker:1.7.5")
ivy"de.schegge:freshmarker:1.7.5"
Usage
Using the template engine is very easy. First the configuration has to be generated and then a template instance has to be created with its help. Finally, the template is processed with the data from the model. The return value of the process method is the result of template processing.
Since version 1.5.0, the Template
is no longer created directly via the Configuration
, but via the DefaultTemplateBuilder
.
Configuration configuration = new Configuration();
TemplateBuilder templateBuilder = configuration.builder();
Template template = templateBuilder.getTemplate("test", "Hello ${example}!");
System.out.println(template.process(Map.of("example", "World")));
Configuration configuration = new Configuration();
Template template = configuration..getTemplate("test", "Hello ${example}!");
System.out.println(template.process(Map.of("example", "World")));
There are a few design decisions to consider when using the template engine.
The configuration is complex to generate and should therefore not be generated for each template.
There are many ways in your own application to provide the configuration only once. A simple Spring Boot variant is shown in the example.
@Bean
public Configuration freshmarker() {
return new Configuration();
}
The Template instance is not thread safe, so it must not be shared between different threads. The result is unpredictable.
Also, the model must be static, the values should not change during processing. This can lead to surprising results.
Templates
A template engine generates output by creating a representation of a model using instructions in a template.
This complicated sentence can be better explained with a small example.
Hello ${example}!
The example template contains three parts.
A first literal text fragment Hello
, an interpolation fragment ${name}
and a second literal text fragment !
.
Literal text means the fragment is not interpreted in any particular way
[1].
Interpolations
The interpolation is a fragment, which is filled with data from the model.
The name example
in the curly brackets is the name by which a value in the model can be identified.
Map<String, Object> model = Map.of("example", "World");
The example model contains a value World
with the name example
.
When the example template is processed, the template engine produces the output Hello World!
based on the example model.
Directives
In addition, there is the possibility of hiding fragments in the template or repeating them several times. There are special constructs for this, called directives.
<#if example=='World'>
Hello ${example}!
<#else>
Hallo ${example}!
</#if>
Directives look like HTML tags with a # before the tag name.
In this example, the English Hello
is only written if the value is World
.
In all other cases the German Hallo
is written.
Built-Ins
Another important template concept is the built-in.
The result of an interpolation is changed by the built-ins.
The interpolation from the last example produces the output World
from the value World
.
If the interpolation is extended to ${example?uppercase}
then the interpolation produces WORLD
.
In this case the built-in upper_case
has been added.
Depending on the type of value, a wide variety of built-ins can be used.
[2].
User Directive
In addition to the standard directives, FreshMarker also supports User Directives.
<@log level='info' message='test'/>
User directives can be defined via Macro directives or by implementing the UserDirective
Interface.
public interface UserDirective {
void execute(ProcessContext context, Map<String, TemplateObject> args, Fragment body);
}
It can be registered to the Configuration
by the registeruserDirective
method.
doubles
configuration = new Configuration();
configuration.registerFunction("doubles",
(context, args, body) -> { body.process(context); body.process(context); }
);
Functions
Additional functionality can be added to the template-engine interpolations by Functions.
public interface TemplateFunction {
TemplateObject execute(ProcessContext context, List<TemplateObject> args);
}
A Function is a Java class implementing the functional interface TemplateFunction
.
It can be registered to the Configuration
by the registerFunction
method.
abs
and avg
configuration = new Configuration();
configuration.registerFunction("abs", (context, args) ->
args.get(0).evaluateToObject(context).asNumber().map(TemplateNumber::abs).orElseThrow());
configuration.registerFunction("avg", (context, args) ->
args.stream().map(o -> o.evaluate(context, TemplateNumber.class)).reduce(TemplateNumber::add).orElseThrow().divide(new TemplateNumber(args.size())));
Each registered Function can be called via a function call with its name as the function name.
<#-- result 15 -->
${avg(10, 20)}
<#-- result 25 -->
${avg(10, 20, 30, 40)}
<#!-- result 10 -->
${abs(-10)}
Output Format and Escaping
Every Template is associated with an Output Format. The Output Format defines the escaping rule for dynamically generated output. Without escaping, there is a security risk known as template injection. With template injection, dangerous fragments are injected into the output.
<!-- title = "<script>alert("hallo")</script>"; -->
<h1>${title}</h1>
The example shows an HTML fragment in which the text for a heading is to be inserted. Without escaping, the script fragment is inserted and a more or less dangerous Javascript is executed instead of displaying a title.
FreshMarker's escape mechanism replaces the necessary meta characters of an output format to neutralize them.
The following table lists the supported output formats and their meta character replacements.
the last two lines indicate how comments can be inserted into the output using the @log directive. The ␣
character is used to indicate whitespaces in prefixes and suffixes.
Name | Meta Characters | Replacements | Comment Prefix | Comment Suffix |
---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
no escaping |
|
|
|
|
no escaping |
no comments |
||
|
no escaping |
|
|
|
|
no escaping |
|
|
|
|
no escaping |
|
|
|
|
no escaping |
no comments |
Template Loading
The Configuration
(DefaultTemplateBuilder
since 1.5.0) class offers four methods for loading templates.
Method | Description |
---|---|
|
Load template from path with default charset |
|
Load template from path with the given charset |
|
Read template from the given reader |
|
Read template from the given string |
|
Read template from the given reader |
|
Read template from the given string |
Regardless of the method selected for loading the template, the imports are loaded by the configured TemplateLoader
.
By default, the DefaultFileSystemTemplateLoader
is configured.
This loads all imports from the file system.
The FileSystemTemplateLoader
can also be used with other file system implementations.
TemplateLoader
with the ZipFileSystem
FileSystem fs = FileSystems.newFileSystem(Paths.get("imports.zip"));
configuration.setTemplateLoader(new FileSystemTemplateLoader(fs));
In this example, the imports are loaded from the zip archive imports.zip
.
Custom template loaders can also be used instead of the DefaultFileSystemTemplateLoader
.
They have to implement the TemplateLoader
interface.
public interface TemplateLoader {
String getImport(Path path, String filename, Charset charset) throws IOException;
default String getImport(Path path, String filename) throws IOException {
return getImport(path, filename, Charset.defaultCharset());
}
}
The filename
parameter is the name of the import file and path
is the parent directory of the template.
The parent directory can be used to load import files located relative to the template.
For templates loaded by String or Reader the path is the current directory. |
Template Caching
FreshMarker does not know any caching mechanism, but you can store and reuse templates with any technology (Maps, Singletons, Caches) you like.
Partial Reduction
This feature is in an experimental stage and should be used with care. |
When processing a template, all necessary data must be available in the model. Otherwise, an exception is thrown if data is missing during processing. If the template contains values that remain the same for a large number of evaluations, the template can be preprocessed.
TemplateBuilder templateBuilder = configuration.builder();
Template template = templateBuilder.getTemplate("test", """"
<#list employee as e>${e.firstname}.${e.lastname}@${company.domain}</#list>
""");
Template reducedTemplate = template.reduce("company", new Company("schegge.de"));
The reduce method receives some of the necessary data and partially evaluates the template. As a result, it delivers a new template in which part of the structure has been simplified.
A reduction cannot be carried out if loop variables, the default or exist operator are used. Variables and parameter should not hide model variables, this could be produced unpredicted results. |
Model
The template engine needs data to fill the interpolations. Because it is an embedded Java engine, normal Java classes are used. A map is always used as the model for calling the engine.
By default, the template engine supports String
, Number like Integer
, Long
, Double
and Float
, Boolean
and Date Types as base types.
In addition, the engine also knows List
, Map
, Sequences, Records and Beans.
The top level model is a Map<String, Object>
, which contains the top level values by their names.
Map<String, Object> model = Map.of("father", father, "mother", mother);
The following tables show all model types supported by FreshMarker. Because FreshMarker can be extended, there are more types available [3].
Without a package specification, the class names refer to the classes of the same name from the JDK.
Type | Java Type(s) | FreshMarker primitive type |
---|---|---|
String |
|
yes |
String |
|
yes |
Number |
|
yes |
Number |
|
yes |
Number |
|
yes |
Number |
|
yes |
Boolean |
|
yes |
DateTime |
|
yes |
Date |
|
yes |
Time |
|
yes |
Year |
|
yes |
YearMonth |
|
yes |
MonthDay |
|
yes |
Duration |
|
yes |
File |
|
yes |
Path |
|
yes |
Period |
|
yes |
Sequence |
Lists |
no |
Range |
- |
no |
Slice |
- |
no |
Map |
Maps |
no |
Bean |
Beans |
no |
Record |
Top-Level Records |
no |
Enum |
Enums |
yes |
Null |
- |
yes |
Locale |
|
yes |
Version |
- |
yes |
All primitive types, except Null, have a default representation via their toString
method.
This must be considered with the enums, because the toString
method can be overridden.
Ranges and Slices
Ranges
Ranges are data structures that represent an interval with a lower limit and an optional upper limit.
Ranges without an upper limit are called right-unlimited ranges, ranges with an upper limit are called right-limited ranges. If the size of the interval is specified instead of the upper limit, then this range is called a length-limited range.
Right-unlimited Range
In principle, a right-unlimited range has no upper limit and can therefore only be used in a few places.
The main area of application is slicing, where a part is cut out of a list, String
or range.
3
A right unlimited range has the form x..
where x
can be any numerical expression.
1.. // lower bound 1
-10.. // lower bound -10
a.. // lower bound in the variable a (must be a numeric value)
Right-limited Range
A right-limited range has a lower and an upper limit. It also represents a sequence of numbers and can be used in this way in a list directive. A right-limited range has the form x..y
or x..<y
where x
and y
can be any numeric expression. The second form describes a range with an exclusive upper limit. This form is called a right-limited exclusive range.
1..10 // lower bound 1 and upper bound 10
-10..<0 // lower bound -10 and upper bound -1
a..b // lower bound in variable a and upper bound in variable b
10..0 // lower bound 1 and upper bound 10
a..<b // lower bound in variable a and an exclusive upper bound in variable b
As can be seen in the examples, the lower limit may be greater than the upper limit. This means that the representation as a sequence cannot be monotonically increasing, but can be monotonically decreasing. In the case of a monotonically increasing sequence, we speak of a legal range, otherwise of an inverse range.
Length-limited Range (since 1.6.4)
A length-limited range is a right-limited range with a different syntax a..*c
.
Where a
again corresponds to the lower limit of the range and c
indicates the size of the range.
If the size of the range is positive, then the upper limit is greater than the lower limit. This results in a legal right-limited range. If the size of the range is negative, then the upper limit is smaller than the lower limit. This results in an inverted right-limited range.
1..*10 // lower bound 1 and upper bound 10
5..*5 // lower bound 5 and upper bound 9
4..*-5 // lower bound 4 and upper bound 0
a..*b // lower bound in variable a and an range size in variable c
Slices
Slices
define slice operators on other data types.
These operators can be applied to Range
, List
, and String
values.
The slices are described by range expressions in square brackets.
The range specifies the lower and upper limits of the Slice
.
With inverted ranges, the slice operation produces an inverted result.
List Slices
TBD
List
slices${['a','b','c','d','e','f','g'][1..4]?join(';')} // => b:c:d:e
${['a','b','c','d','e','f','g'][1..]?join(';')} // => b;c;d;e;f;g
${['a','b','c','d','e','f','g'][4..1]?join(';')} // => e;d;c;b
Range Slices
TBD
Range
slices${(1..10)[1..4]?join(';')} // => 2;3;4;5
${(1..10)[1..]?join(';')} // => 2;3;4;5;6;7;8;9;10
${(1..10)[4..1]?join(';')} // => 5;4;3;2
String Slices
TBD
String
slices${'abraxas'[1..4]} // => brax
${'abraxas'[1..]} // => braxas
${'abraxas'[4..1]} // => xarb
Optional Values
Optional values of type java.util.Optional
are also supported in the model.
Empty optionals are interpreted as null
and all other values are interpreted as instances of the generic type.
An optional can therefore be checked with the existence operator and the default operator can be used to determine a replacement value for empty optionals.
Java | FreshMarker |
---|---|
|
${optional??} |
|
${optional == null} |
|
${optional!42} |
Lazy Values
Normally the values are written directly into the model.
Sometimes the cost of creating optional values is quite high.
In that case it would be a waste to calculate the values if they are not used.
However, since FreshMarker needs the values in the model, a compromise is made.
Instead of calculating the values immediately and placing them in the map, the calculation is inserted into the model as a TemplateObjectSupplier
.
Their actual values are lazy evaluated only when accessed during processing.
Map<String, Object> model = Map.of(
"familyTree", TemplateObjectSupplier.of(() -> fetchTreeFromDataBase(name)),
"name", "Kayser");
template.process(model);
In the example above, the familyTree
value is lazy evaluated, when the variable is accessed during template processing.
Forbidden Types
Although many Java types are supported directly or as a Java Bean, some classes cannot be used. For security reasons, all classes from the java
, javax
, sun
and com.sun
packages are ignored.
if you desperately need one of these classes, you can declare it as an exception.
configuration.getSecurity().addAllowedClass(Runtime.class);
configuration.getSecurity().addAllowedPackages(java.lang.annotation.Annotation.class);
configuration.getSecurity().addAllowedPackages("java.io");
Of course, it only makes sense to allow classes that can also be interpreted by FreshMarker. Alternatively, a class can also be integrated via Custom String Types or a Plug-in. |
In the example above, the class java.lang.Runtime
and all classes in the packages java.lang.annotation
and java.io
and all of their subpackages are allowed.
Interpolations
Interpolations produce output for the values in the model.
When processing an interpolation, there are two mandatory constraints that must be met.
An interpolation must evaluate to a FreshMarker primitive type.
For example, if the result is a list, an exception is thrown.
If the result of the evaluation is the special value NULL
, then an exception is also thrown.
An interpolation must always return a result.
Configuration configuration = new Configuration();
TemplateBuilder templateBuilder = configuration.builder();
Template template = templateBuilder.getTemplate("test", "Hello ${example}!");
System.out.println(template.process(Map.of()));
The example above produces an ProcessException, because the interpolation ${example}
is evaluated to NULL
.
To deal with missing values, FreshMarker provides two operations.
The default operator !
and the exists operator ??
.
The default operator replaces a NULL
value with the specified parameter.
If the parameter is missing, the value is replaced with an empty text.
Configuration configuration = new Configuration();
TemplateBuilder templateBuilder = configuration.builder();
Template template = templateBuilder.getTemplate("test", "Hello ${example!'World'}!");
System.out.println(template.process(Map.of()));
The example above produces the output Hello World!
, because the interpolation ${example!'World'}
evaluates to World
if no value for example
is given.
The exists operator evaluates to true
if the value exists, and it evaluates to false
if the value is missing.
Configuration configuration = new Configuration();
TemplateBuilder templateBuilder = configuration.builder();
Template template = templateBuilder.getTemplate("test", "Hello ${example???then(example, 'World')}!");
System.out.println(template.process(Map.of()));
The exists example is a bit more complicated because the operator is commonly used in directives.
Since there are enough examples for the exists operator in the directives section, this unusual variant is presented here.
The interpolation consists of the variable example
, the exists operator ??
and the boolean build-in ?then(example, 'World')
.
The exists operator is called on the missing value example
and the result is false
.
The boolean built-in returns the first parameter on true
and the second parameter on false
.
In this example the second parameter 'World'
is returned and the result of the whole interpolation is World
.
Built-Ins
Built-Ins are interpolation operations which are called on the current value of the interpolation.
upper_case
built-inTemplateBuilder builder = new Configuration().builder();
Template template = builder.getTemplate("test", "HELLO ${example?upper_case}!");
System.out.println(template.process(Map.of("example", "world")));
In the above example the built-in upper_case
is called on the current value world
of the interpolation.
The built-in converts a text to its uppercase form.
Since the capitalization can only be changed for text, the execution of this built-in on other types such as numbers or dates is forbidden. In general, all built-ins are assigned to a type and an exception is thrown if a wrong type is used.
if a built-in seems to apply to multiple types, it’s because each type has a built-in with the same name.
It is possible to use more than one built-in in an interpolation. The built-ins are called from left to right on the current value of the interpolation. This means a built-in is called on the result of a previous built-in or the initial value.
TemplateBuilder builder = new Configuration().builder();
Template template = builder.getTemplate("test", "HELLO ${example?upper_case?lower_case}!");
System.out.println(template.process(Map.of("example", "WorlD")));
In the example above the upper_case
built-in is called on the current value WorlD
.
The second built-in lower_case
is called on the current value WORLD
, which is the result of upper_case
.
the result of the interpolation is the result world
of the second built-in.
Null Aware Built-In Handling (since 1.7.4)
Normally, the processing of built-ins ends with an exception if no suitable built-in is found.
In the case of null
values and empty optionals, this behavior can now be changed using the flags BuiltinHandlingFeature.IGNORE_OPTIONAL_EMPTY
and BuiltinHandlingFeature.IGNORE_NULL
flags.
In both cases, the actual built-in is ignored and the input value is returned.
Configuration configuration = new Configuration();
TemplateBuilder builder = configuration.builder().with(BuiltinHandlingFeature.IGNORE_NULL);
Template template = builder.getTemplate("test", "Hello ${example?upper_case?lower_case!'World'}!");
assertEquals("Hello World", template.process(Map.of()));
The example does not generate an exception if example
contains the value null
,
because both build-ins pass on the value instead of throwing an exception.
A default operator must be added at the end so that the interpolation as a whole does not throw an exception.
Instead of setting both features, an experimental alternative built-in operator can also be used (
|
String Built-Ins
upper_case
Built-In
This built-in converts a text in its uppercase form.
Input | Output |
---|---|
${'The quick brown Fox jumps over the lazy Dog'?upper_case} |
THE QUICK BROWN FOX JUMPS OVER THE LAYZ DOG |
lower_case
Built-In
This built-in converts a text in its lowercase form.
Input | Output |
---|---|
${'The quick brown Fox jumps over the lazy Dog'?lower_case} |
the quick brown fox jumps over the lazy dog |
capitalize
Built-In
This built-in converts the first letter of each word to uppercase.
A word with a first lowercase letter means \b(\p{javaLowerCase})(\p{IsAlphabetic}*)\b
Input | Output |
---|---|
${'The quick brown Fox jumps over the lazy Dog'?capitalize} |
The Quick Brown Fox Jumps Over The Lazy Dog |
uncapitalize
Built-In
This built-in converts the first letter of each word to lowercase.
A word with a first uppercase letter means \b(\p{javaUpperCase})(\p{IsAlphabetic}*)\b
Input | Output |
---|---|
${'The quick BROWN Fox jumps over the lazy Dog'?uncapitalize} |
the quick bROWN fox jumps over the lazy dog |
camelCase
Built-In
This built-in converts a text from snake_case, screaming_snake_case or kebab-case to camelCase. The result may look strange is the input is in some other format.
Input | Output |
---|---|
|
|
|
|
snake_case
Built-In
This built-in converts a text from camelCase to snake_case. The result may look strange is the input is in some other format.
Input | Output |
---|---|
|
|
screaming_snake_case
Built-In
This built-in converts a text from camelCase to screaming_snake_case. The result may look strange is the input is in some other format.
Input | Output |
---|---|
|
|
kebab_case
Built-In
This built-in converts a text from camelCase to kebab-case. The result may look strange is the input is in some other format.
Input | Output |
---|---|
|
|
slugify
Built-In (since 1.4.6)
This built-in converts a text to a slug.
Input | Output |
---|---|
|
|
|
|
|
|
trim
Built-In
This built-in removes leading and trailing whitespaces from the text.
The examples use ␣
as a replacement for the whitespace.
Input | Output |
---|---|
|
|
trim_to_null
Built-In (since 1.7.1)
This built-in removes leading and trailing whitespaces from the text.
The examples use ␣
as a replacement for the whitespace.
If the result is an empty string, null
is returned.
If the text is null
, then null
is returned instead of throwing an exception.[4]
Input | Output | Type |
---|---|---|
|
|
|
|
|
|
|
|
|
empty_to_null
Built-In (since 1.7.1)
This built-in returns null
if the text is empty.
If the text is null
, then null
is returned instead of throwing an exception.[4]
Input | Output | Type |
---|---|---|
|
|
|
|
|
|
|
|
|
blank_to_null
Built-In (since 1.7.1)
This built-in returns null
if the text is blank.
The examples use ␣
as a replacement for the whitespace.
If the text is null
, then null
is returned instead of throwing an exception.[4]
Input | Output | Type |
---|---|---|
|
|
|
|
|
|
|
|
|
contains
Built-In
This built-in return true
if the text contains the specified parameter, otherwise false
.
Interpolation | Output |
---|---|
|
|
|
|
startsWith
Built-In
This built-in return true
if the text starts with the specified parameter, otherwise false
.
Interpolation | Output |
---|---|
|
|
|
|
endsWith
Built-In
This built-in return true
if the text ends with the specified parameter, otherwise false
.
Interpolation | Output |
---|---|
|
|
|
|
toBoolean
Built-In
This built-in return true
if the text is true
and false
if it is false
.
Interpolation | Output |
---|---|
|
|
|
|
|
This is an error. |
length
Built-In
This built-in return the length of the text.
Interpolation | Output |
---|---|
|
|
|
|
esc
, escape
Built-In
This built-in convert the text into markup with the specified output format. It cannot be processed further because there are no built-ins for markup.
Interpolation | Output |
---|---|
|
|
|
|
noEsc
, no_esc
, no_escape
Built-In
This built-in convert the text into markup without an output format. This can be useful if the current value should not escape with the current output format. It cannot be processed further because there are no built-ins for markup.
Interpolation | Output |
---|---|
|
|
locale
Built-In
This built-in converts a string value into a locale value.
Interpolation | Output | Type |
---|---|---|
|
|
|
|
|
|
|
|
|
version
Built-In
This built-in converts a string value into a version value.
Interpolation | Output | Type |
---|---|---|
|
|
|
|
|
|
i18n
Built-In (experimental, since 1.6.7)
This built-in returns a string from a resource bundle based on the key in the value.
the resource bundle name must be added to the template via the Template#addResourceBundle method or used as the parameter of the built-in.
|
Interpolation | Output | Resource Bundle |
---|---|---|
|
|
|
|
|
|
|
|
Boolean Built-Ins
c
(computer language) Built-In
This built-in converts a boolean to the string literals true
or false
.
h
(human language) Built-In (since 1.4.6)
This built-in converts a boolean to the localized string literals true
or false
.
Localizations is only supported for default values in
freshmarker.properties boolean.true=true boolean.false=false number.1=one number.2=two number.3=three number.4=four number.5=five number.6=six number.7=seven number.8=eight number.9=nine date.-2=the day before yesterday date.-1=yesterday date.0=today date.1=tomorrow date.2=the day after tomorrow period.day=day period.days=days period.month=month period.months=months period.year=year period.years=years |
Interpolation | Output |
---|---|
|
|
|
|
then
Built-In
This built-in returns its first parameter if the value is true
or the second parameter if the value is false
.
Interpolation | Output |
---|---|
|
|
|
|
string
Built-In
This built-in returns its first string parameter if the value is true
or the second string parameter if the value is false
.
The string
Built-In is replaceable by the then
Built-In.
Interpolation | Output |
---|---|
|
|
|
|
Number Built-Ins
c
(computer language) Built-In
This built-in converts a number to the string using the toString()
method of the corresponding base types BigDecimal
, BigInteger
, Double
, Float
, Long
, AtomicLong
, Integer
, AtomiInteger
, Byte
or Short
.
Without this builtin, the number is formatted with the default number formatter for the current locale. |
h
(human language) Built-In (since 1.4.6)
This built-in formats the numbers between 1 and 9 as localized strings 'one' to 'nine'. Other numbers are returned as numbers and formatted by default.
Localizations is only supported for default values in
freshmarker.properties boolean.true=true boolean.false=false number.1=one number.2=two number.3=three number.4=four number.5=five number.6=six number.7=seven number.8=eight number.9=nine date.-2=the day before yesterday date.-1=yesterday date.0=today date.1=tomorrow date.2=the day after tomorrow period.day=day period.days=days period.month=month period.months=months period.year=year period.years=years |
Interpolation | Locale | Output |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
format
Built-In
This built-in format the number by the given format parameter and the current locale. See java.util.Formatter
for details.
Interpolation | Locale | Output |
---|---|---|
|
|
|
|
|
|
abs
Built-In
This built-in return the absolute value of the number.
Interpolation | Output |
---|---|
|
|
|
|
|
|
sign
Built-In
This built-in return the sign of the number as an Integer.
Interpolation | Output |
---|---|
|
|
|
|
|
|
min
Built-In (since 1.6.0)
This built-in returns the smaller value of the variable and the specified parameter.
Interpolation | Output |
---|---|
|
|
|
|
max
Built-In (since 1.6.0)
This built-in returns the bigger value of the variable and the specified parameter.
Interpolation | Output |
---|---|
|
|
|
|
Cast Built-Ins (since 1.0.0)
These built-ins cast the number to a TemplateNumber
instance in the specified type range.
Interpolation | Output | Type |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
roman
Built-In (since 1.3.5)
This built-in converts numbers between 1 and 3999 to roman numerals.
Interpolation | Output |
---|---|
|
|
|
|
|
|
utf_roman
Built-In (since 1.3.5)
This built-in converts numbers between 1 and 3999 to roman numerals. This built-in
uses the Unicode Characters \u2160
, \u2164
, \u2169
and \u216C
to \u216F
.
Interpolation | Output |
---|---|
|
|
|
|
|
|
clock_roman
Built-In (since 1.3.5)
This built-in converts numbers between 1 and 12 to roman numerals. This built-in
uses the Unicode Characters from \u2160
to \u216B
.
Interpolation | Output |
---|---|
|
|
|
|
|
|
Date-Time Built-Ins
The Built-Ins in the examples operate on the java.time.Instant
, java.time.ZonedDateTime
, java.time.LocalDateTime
respectively java.util.Date
value 1968-08-24 12:34:56
.
date
Built-In
This built-in returns the date part of the value.
Interpolation | Output | Type |
---|---|---|
|
|
date |
time
Built-In
This built-in returns the time part of the value.
Interpolation | Output | Type |
---|---|---|
|
|
time |
c
(computer language) Built-In
This built-in returns a timestamp string of the value.
Interpolation | Output | Type |
---|---|---|
|
|
string |
string
Built-In
This built-in returns a formatted string of the value. [5]
Interpolation | Output | Type |
---|---|---|
|
|
string |
at_zone
Built-In
This built-in returns a temporal at the given time zone of the value. [5]
Interpolation | Output | Type |
---|---|---|
|
|
date time (TemplateZonedDateTime) |
zone
Built-In
This built-in returns a string for the current time zone of the value.
It is only available for the java.time.TemplateZonedDateTime
type.
Interpolation | Output | Type |
---|---|---|
|
|
string |
year
Built-In (since 1.7.0)
This built-in returns the year of the value.[6]
Interpolation | Output | Type |
---|---|---|
|
|
year |
month
Built-In (since 1.7.0)
This built-in returns the month of the value.[6]
Interpolation | Output | Type |
---|---|---|
|
|
month |
day
Built-In (since 1.7.0)
This built-in returns the day of month of the value as a number.[6]
Interpolation | Output | Type |
---|---|---|
|
|
number |
Date Built-Ins
The Built-Ins in the examples operate on the java.time.LocalDate
respectively java.sql.Date
value 1968-08-24
.
date
Built-In
This built-in returns the date part of the value.
This built-in seems pointless at first because the result corresponds to the original value. However, it is often not certain what type the variable actually has. With a datetime value, the time would otherwise also be used without ?date .
|
Interpolation | Output | Type |
---|---|---|
|
|
date |
c
(computer language) Built-In
This built-in returns a date string of the value.
Interpolation | Output | Type |
---|---|---|
|
|
string |
h
(human language) Built-In (since 1.4.6)
This built-in returns a date string of the value.
If the date is a maximum of two days before or after the reference date, a localized variant of the
the day before yesterday
, yesterday
, today
, tomorrow
or the day after tomorrow
is used.
The reference date is the parameter of the built-in.
If the parameter is missing, LocalDate.now()
is used.
[5]
Localizations is only supported for default values in
freshmarker.properties boolean.true=true boolean.false=false number.1=one number.2=two number.3=three number.4=four number.5=five number.6=six number.7=seven number.8=eight number.9=nine date.-2=the day before yesterday date.-1=yesterday date.0=today date.1=tomorrow date.2=the day after tomorrow period.day=day period.days=days period.month=month period.months=months period.year=year period.years=years |
Interpolation | Output | Type |
---|---|---|
|
|
string |
|
|
date |
|
|
string |
string
Built-In
This built-in returns a formatted string of the value. [5]
Interpolation | Output | Type |
---|---|---|
|
|
string |
since
Built-In (since 1.6.1)
This built-in returns a duration between the value and the parameter.
If the parameter is missing, java.time.LocalDate.now()
is used.
[5]
Interpolation | Output | Type |
---|---|---|
|
|
duration |
|
|
duration |
until
Built-In (since 1.6.1)
This built-in returns a duration between the value and the parameter.
If the parameter is missing, java.time.LocalDate.now()
is used.
[5]
Interpolation | Output | Type |
---|---|---|
|
|
duration |
|
|
duration |
year
Built-In (since 1.7.0)
This built-in returns the year of the value.[5]
Interpolation | Output | Type |
---|---|---|
|
|
year |
month
Built-In (since 1.7.0)
This built-in returns the month of the value.[5]
Interpolation | Output | Type |
---|---|---|
|
|
month |
day
Built-In (since 1.7.0)
This built-in returns the day of month of the value as a number.[5]
Interpolation | Output | Type |
---|---|---|
|
|
number |
Time Built-Ins
The Built-Ins in the examples operate on the java.time.LocalTime
respectively java.sql.Time
value 12:34:56
.
time
Built-In
This built-in returns the time part of the value.
This built-in seems pointless at first because the result corresponds to the original value. However, it is often not certain what type the variable actually has. With a datetime value, the date would otherwise also be used without ?time .
|
Interpolation | Output | Type |
---|---|---|
|
|
time |
c
(computer language) Built-In
This built-in returns a time string of the value.
Interpolation | Output | Type |
---|---|---|
|
|
string |
string
Built-In
This built-in returns a formatted string of the value. [5]
Interpolation | Output | Type |
---|---|---|
|
|
string |
Year Built-Ins
The Built-Ins in the examples operate on the value 2025
.
c
(computer language) Built-In (since 1.7.0)
This built-in returns a year string of the value.
Interpolation | Output | Type |
---|---|---|
|
|
string |
year
Built-In (since 1.7.0)
This built-in returns the year of the value.
Interpolation | Output | Type |
---|---|---|
|
|
year |
is_leap
Built-In (since 1.7.0)
This built-in returns true if the year is a leap year.
Interpolation | Output | Type |
---|---|---|
|
|
boolean |
YearMonth Built-Ins
The Built-Ins in the examples operate on the value 2025-08
.
c
(computer language) Built-In (since 1.7.0)
This built-in returns a year string of the value.
Interpolation | Output | Type |
---|---|---|
|
|
string |
year
Built-In (since 1.7.0)
This built-in returns the year of the value.
Interpolation | Output | Type |
---|---|---|
|
|
year |
month
Built-In (since 1.7.0)
This built-in returns the month of the value.
Interpolation | Output | Type |
---|---|---|
|
|
month |
is_leap
Built-In (since 1.7.0)
This built-in returns true if the year is a leap year.
Interpolation | Output | Type |
---|---|---|
|
|
boolean |
MonthDay Built-Ins
The Built-Ins in the examples operate on the value 08-24
.
c
(computer language) Built-In (since 1.7.0)
This built-in returns a year string of the value.
Interpolation | Output | Type |
---|---|---|
|
|
string |
month
Built-In (since 1.7.0)
This built-in returns the month of the value.
Interpolation | Output | Type |
---|---|---|
|
|
month |
day
Built-In (since 1.7.0)
This built-in returns the day of month of the value as a number
Interpolation | Output | Type |
---|---|---|
|
|
number |
Sequence Built-Ins
The Built-Ins in the examples operate on a sequence with the values 2
, 3
, 4
, 5
and 6
.
first
Built-In
This built-in returns the first element of a sequence.
Template | Output |
---|---|
|
|
last
Built-In
This built-in returns the last element of a sequence.
Template | Output |
---|---|
|
|
reverse
Built-In
This built-in returns a reversed sequence of a sequence.
Template | Output |
---|---|
|
|
size
Built-In
This built-in returns the size of a sequence as a number.
Template | Output |
---|---|
|
|
join
Built-In (since 1.5.1)
This built-in returns the elements of a sequence as a string. The default limiter is ", " but can be changed with the first optional parameter of the built-in. The second optional parameter is an alternative parameter for the last element in the sequence.
Template | Output |
---|---|
|
|
|
|
|
|
Range Built-Ins
lower
Built-In (since 1.6.4)
This built-in returns the lower bound of a range.
Template | Output |
---|---|
|
|
|
|
upper
Built-In (since 1.6.4)
This built-in returns the upper bound of a limited range.
Template | Output |
---|---|
|
|
reverse
Built-In (since 1.6.4)
This built-in returns a reversed limited range.
Template | Output |
---|---|
|
|
size
Built-In (since 1.6.4)
This built-in returns the size of a limited range as a number.
Template | Output |
---|---|
|
|
join
Built-In (since 1.6.4)
This built-in returns the elements of a limited range as a string. The default limiter is ", " but can be changed with the first optional parameter of the built-in. The second optional parameter is an alternative parameter for the last element in the sequence.
Template | Output |
---|---|
|
|
|
|
|
|
Looper Built-Ins
index
Built-In
This built-in returns the index (starting with zero) of the current loop value as a number value.
Template | Output |
---|---|
|
|
counter
Built-In
This built-in returns the counter (starting with one) of the current loop value as a number value.
Template | Output |
---|---|
|
|
roman
Built-In (since 1.3.5)
This built-in returns the counter (starting with one) of the current loop value as a roman number value.
Template | Output |
---|---|
|
|
utf_roman
Built-In (since 1.3.5)
This built-in returns the counter (starting with one) of the current loop value as a roman number value.
This built-in uses the Unicode Characters \u2160
, \u2164
, \u2169
and \u216C
to \u216F
.
Template | Output |
---|---|
|
|
clock_roman
Built-In (since 1.3.5)
This built-in returns the counter (starting with one up to twelve) of the current loop value as a roman number value.
This built-in uses the Unicode Characters from \u2160
to \u216B
.
Template |
Output |
|
|
is_first
Built-In
This built-in returns the boolean value true
, if the current loop value is the first one and false
otherwise.
Template | Output |
---|---|
|
|
This built-in useful, if the first item in a sequence should look different.
Template | Output |
---|---|
|
|
is_last
Built-In
This built-in returns the boolean value true
, if the current loop value is the last one and false
otherwise.
Template | Output |
---|---|
|
|
This built-in useful, if the last item in a sequence should look different.
Template | Output |
---|---|
|
|
item_parity
Built-In
This built-in returns the string value even
, if the current loop counter is even and odd
otherwise.
It is useful for CSS class names.
Template | Output |
---|---|
|
|
item_parity_cap
Built-In
This built-in returns the string value Even
, if the current loop counter is even and Odd
otherwise.
It is useful for CSS class names.
Template | Output |
---|---|
|
|
item_cycle
Built-In
This built-in cycles throw the parameters for the current loop counter. It is useful for CSS class names.
Template | Output |
---|---|
|
|
has_next
Built-In
This built-in returns false
, if the current loop value is the last one and true
otherwise.
Template | Output |
---|---|
|
|
This built-in useful, if the last item in a sequence should look different.
Template | Output |
---|---|
|
|
Enum Built-Ins
The Built-Ins in the examples operate on the enum value java.nio.file.StandardCopyOption.ATOMIC_MOVE
.
ordinal
Built-In
This built-in returns the ordinal of the value.
Interpolation | Output | Type |
---|---|---|
|
|
number |
c
(computer language) Built-In
This built-in returns the name of the value.
Interpolation | Output | Type |
---|---|---|
|
|
number |
File and Path Built-Ins
Since FreshMarker 1.3.2 the File and Paths BuiltIns are included via an additional artefact.
|
if these built-ins are used carelessly, third parties may receive information about files on the server. |
The Built-Ins in the examples operate on the java.io.File
respectively java.nio.file.Path
value /src/main/asciidoc/manual.adoc
.
exists
Built-In
This built-in returns true, if the file/path in the value exists.
Interpolation | Output | Type |
---|---|---|
|
|
boolean |
is_directory
Built-In
This built-in returns true, if the file/path in the value is a directory.
Interpolation | Output | Type |
---|---|---|
|
|
boolean |
is_file
Built-In
This built-in returns true, if the file/path in the value is a file.
Interpolation | Output | Type |
---|---|---|
|
|
boolean |
can_execute
Built-In
This built-in returns true, if the file/path in the value can be executed.
Interpolation | Output | Type |
---|---|---|
|
|
boolean |
can_read
Built-In
This built-in returns true, if the file/path in the value can be read.
Interpolation | Output | Type |
---|---|---|
|
|
boolean |
can_write
Built-In
This built-in returns true, if the file/path in the value can be written.
Interpolation | Output | Type |
---|---|---|
|
|
boolean |
size
Built-In
This built-in returns the size of the file/path in the value.
Interpolation | Output | Type |
---|---|---|
|
|
number |
name
Built-In
This built-in returns the name of the file/path in the value.
Interpolation | Output | Type |
---|---|---|
|
|
string |
parent
Built-In
This built-in returns the parent file/path of the value.
Interpolation | Output | Type |
---|---|---|
|
|
file/path |
Locale Built-Ins
The Built-Ins in the examples operate on the locale value Locale.GERMANY
.
lang
, language
Built-In
This built-in returns the language part of the locale value.
Interpolation | Output | Type |
---|---|---|
|
|
|
|
|
|
language_name
Built-In (since 1.7.5)
This built-in returns the language name of the locale value.
Interpolation | Output | Type |
---|---|---|
|
|
|
country
Built-In
This built-in returns the country part of the locale value.
Interpolation | Output | Type |
---|---|---|
|
|
|
country_name
Built-In (since 1.7.5)
This built-in returns the country name of the locale value.
Interpolation | Output | Type |
---|---|---|
|
|
|
Version Built-Ins
The Built-Ins in the examples operate on the version value 1.0.2
.
major
Built-In
This built-in returns the major part of the version value.
Interpolation | Output | Type |
---|---|---|
|
|
|
minor
Built-In
This built-in returns the minor part of the version value.
Interpolation | Output | Type |
---|---|---|
|
|
|
patch
Built-In
This built-in returns the patch part of the version value.
Interpolation | Output | Type |
---|---|---|
|
|
|
is_before
Built-In
This built-in return true
if the version is before the specified parameter, otherwise false
. The type of the parameter is string or version.
Interpolation | Output | Type |
---|---|---|
|
|
|
|
|
|
is_equal
Built-In
This built-in return true
if the version is equal the specified parameter, otherwise false
. The type of the parameter is string or version.
Interpolation | Output | Type |
---|---|---|
|
|
|
|
|
|
is_after
Built-In
This built-in return true
if the version is after the specified parameter, otherwise false
. The type of the parameter is string or version.
Interpolation | Output | Type |
---|---|---|
|
|
|
|
|
|
Random Built-Ins
The Random built-ins are included via an additional artefact.
|
The examples uses the .random
built-in variable containing a SecureRandom
value and variable abraxas
containing a Random
value.
boolean
Built-In
This built-in returns a random boolean value.
Interpolation | Output | Type |
---|---|---|
|
|
boolean |
|
|
boolean |
int
Built-In
This built-in returns a random int value. If a parameter is specified, the built-in returns a number between 0 and the parameter.
Interpolation | Output | Type |
---|---|---|
|
|
int |
|
|
int |
double
Built-In (since 1.2.0)
This built-in returns a random double value. If a parameter is specified, the built-in returns a number between 0 and the parameter.
Interpolation | Output | Type |
---|---|---|
|
|
double |
|
|
double |
item
Built-In
This built-in returns a random item from a sequence.
Interpolation | Output |
---|---|
|
|
uuid
Built-In
This built-in returns a random UUID as string.
Interpolation | Output | Type |
---|---|---|
|
|
|
sentence
Built-In (since 1.2.0)
This built-in returns a random sentence as string.
Interpolation | Output | Type |
---|---|---|
|
|
|
paragraph
Built-In (since 1.2.0)
This built-in returns a random paragraph as string.
Interpolation | Output | Type |
---|---|---|
|
|
|
Directives
If/ElseIf/Else Directive
This directive generates conditional output. The expression in the if
and elseif
statement
is evaluated to a boolean value. If the value is true
the corresponding block is evaluated.
If the expression cannot evaluate to a boolean value an exception is thrown.
if
part<#if text??>
${text}
</#if>
The elseif
and else
part are optional and can be dismissed.
<#if number >= 100>
greater equal hundred
<#elseif number > 10>
greater ten
<#else>
less ten
</#if>
Switch/Case/Default Directive
This directive generates conditional output.
Each case
statement handles a possible outcome of the evaluation.
With the switch
directive, the expressions of the case
statements are compared one after the other with the expression in the switch
statement. The first case
statement that matches is used as the output.
If none of the specified case
statements match, then the optional default
statement is used.
The FreshMarker Switch Directive does not support fall-through, so break statements are unnecessary and therefor not supported.
|
<#switch color>
<#case 'RED'>#FF0000
<#case 16711680>#FF0000
<#case 'GREEN'>#00FF00
<#case 65280>#00FF00
<#case 'BLUE'>#0000FF
<#case 255>#0000FF
<#default>${color}
</#switch>
<#switch color>
<#case foreground>color: ${color}
<#case background>background-color: ${color}
</#switch>
The case
statement expressions do not have to be constants. This means that more complex expressions can also be used. If this is not desired, it can be switched off using the feature SwitchDirectiveFeature.ALLOW_ONLY_CONSTANT_CASES
.[7]
In addition, the individual case
statement expressions do not have to have the same type. If this is desired, it can be activated via the SwitchDirectiveFeature.ALLOW_ONLY_EQUAL_TYPE_CASES
feature. This feature is only taken into account if the feature SwitchDirectiveFeature.ALLOW_ONLY_CONSTANT_CASES
is also activated.[7]
Switch/On/Default Directive (since 1.7.1)
This directive generates conditional output.
Each on
statement handles a possible outcome of the evaluation.
With the on
directive, the expressions of the on
statements are compared one after the other with the expression in the sitch
statement. The first on
statement that matches is used as the output.
If none of the specified on
statements match, then the optional default
statement is used.
The difference between on
and case
statement is that an on
statement can contain several comparison expressions.
<#switch color>
<#on 'RED', 'Crimson', 'DarkRed'>#FF0000
<#on 'GREEN'>#00FF00
<#on 'BLUE', 'Navy'>#0000FF
<#default>${color}
</#switch>
The on
statement expressions do not have to be constants. This means that more complex expressions can also be used. If this is not desired, it can be switched off using the feature SwitchDirectiveFeature.ALLOW_ONLY_CONSTANT_ONS
.
In addition, the individual on
statement expressions do not have to have the same type. If this is desired, it can be activated via the SwitchDirectiveFeature.ALLOW_ONLY_EQUAL_TYPE_ONS
feature. This feature is only taken into account if the feature SwitchDirectiveFeature.ALLOW_ONLY_CONSTANT_ONS
is also activated.
List Directive
The List Directives processes its body for each element of a sequence. The sequence can be either a list, a hash or a range. A List Directive defines the sequence and the name of the item variable in the block. In addition, an optional loop variable containing meta information of the current loop iteration can also be specified.
Although FreeMarker and FreshMaker are similar, the list directive syntax differs. FreeMarker does not know a separate loop variable. In addition, FreshMaker does not support an Else element in the list. This can be replaced by a separate If Directive. FreeMarker's Items Directive is also not supported. there are plenty of ways to get the same result. |
List Directive with a list
A list is a sequence of values of any model type.
<#list sequence as s>
${s} Marx
</#list>
In the above example, each element from the list is simply output followed by the word Marx
.
If the list contains Chico
, Harpo
, Groucho
, Gummo
and Zeppo
then the result is a five-line listing of the Marx brothers.
The next example the interpolation ${s}
is preceded by the interpolation ${l?counter}
.
<#list sequence as s with l>
${l?counter}. ${s} Marx
</#list>
Because l
is the name of the loop variable, the first interpolation evaluates to the counter of the current item in the list.
1. Chico Marx 2. Harpo Marx 3. Groucho Marx 4. Gummo Marx 5. Zeppo Marx
List Directive with a range
A range is defined by a lower and a non-mandatory upper numerical limit. Although FreshMarker also masters right-unlimited ranges, List Directives are not allowed to use them.
<#list 1..5 as s>
${s} * ${s} = ${s*s}
</#list>
The current item in the above example is any integer value from 1 to 5 inclusive. The result is a list of square value equations.
1 * 1 = 1 2 * 2 = 4 3 * 3 = 9 4 * 4 = 16 5 * 5 = 25
Because the limits of a range do not have to be constants, a List Directive can also be used to mimic a for loop.
<#list a..b as s>
${s} * ${s} = ${s*s}
</#list>
This example loops through all values between numeric variable a
and numeric variable b
.
For the value 3
in a
and 1
in b
the following result is generated.
3 * 3 = 9 2 * 2 = 4 1 * 1 = 1
List Directive with a hash
A hash is a sequence of key-value pairs. The key is a string and the value is any model type. For a hash the item in the List Directive is a key-value pair.
<#list map as key, value>
${key}->${value}
</#list>
In addition to the Java Map types, Java Beans and Records are also interpreted as a hash. It is not only possible to iterate over the entries of a Map , but also over the attributes of Java Beans and Records.
|
<#list map as key, value with l>
${l?counter}. ${key}->${value}
</#list>
List Directive with a hash and sorted keys
Normally the list iterates over the entries without a preferred order.
With Java Map
types this can be influenced by the use of SequencedMap
implementations. However, this is not possible when using a Java Bean.
The keys of a hash can also be sorted in ascending and descending order by adding sorted asc
after the key for ascending sorting and sorted desc
for descending sorting.
<#list map as key sorted asc, value>
${key}->${value}
</#list>
List Directive with a filter (since 1.6.6)
The list directive runs over all elements of a list. An element can be omitted with an if statement. If the looper variable is also used, this can lead to jumps. There are two solutions to this problem.
The first solution is to include a filtered list in the model and to use this list in the list directive. The second solution is to use a filter expression in the list directive.
The filter expression in the list directive follows directly after the looper part.
<#list 1..10 as s filter s % 2 == 0>
${s} * ${s} = ${s*s}
</#list>
List Directive with an offset (since 1.7.2)
The start offset in the list directive can be increased using an offset expression. The offset expression in the list directive follows directly after the filter part. If the list ist filtered by a filter expression, the offset is applied to the filtered and not to the original list.
<#list 1..10 as s offset 5>
${s} * ${s} = ${s*s}
</#list>
List Directive with a limit (since 1.6.6)
The number of elements in the list directive can be reduced using a limit expression. The limit expression in the list directive follows directly after the offset part. If the list ist filtered by a filter expression, the limit is applied to the filtered and not to the original list.
<#list 1..10 as s limit 4>
${s} * ${s} = ${s*s}
</#list>
Var/Set Directive
The Var and Set Directives are used to define local variables in the template. Unlike the model values, new values can be assigned to variables.
The first use of a variable is with the Var Directive. This defines the variables in the current context. Later, the variable can be assigned new values with the Set Directive. A variable only exists within its context.
<#var test='eins'/>
<#set test='zwei'/>
<#set test='drei'/>
A new context is created within the List Directive, Macro Directive and the conditional If and Switch Directives. Therefore, variables that are created in a Macro Directive can no longer be accessed outside.
<#macro copyright>
<#var test='test'/>
</#macro>
<@copyright/>
${test!'gonzo'}
In this example the variable test
cannot accessed outside of the macro.
The result of the interpolation ${test!'gonzo'}
is therefore gonzo
.
Variables with an identical name cannot created in the same context. Variables in an outer context can be hidden by variables with an identical name.
Output-Format Directive
The output-format directive changes the current output format of the template. The new output format remains in place until it is changed by another output-format directive.
<#outputformat 'HTML'>
${text}
<#outputformat 'plainText'>
${text}
Setting Directive
The Setting Directive changes some standard values in the template. The settings remains in place until it is changed by another settings directive.
<#setting locale="de-DE">
${42.23}
<#setting locale="en-US">
${42.23}
Macro Directive
The macro directive can be used to create a macro, which can then be used as a user directive in the template.
<#macro copyright from>
/*
* Copyright © ${from}-${.now?string('yyyy')} Jens Kaiser
*
* All Rights resevered.
*/
</#macro from>
<@copyright/>
User Directive
The user directive insert the output of Macro directives or UserDirective
implementions in the generated content.
<@log level='info' message='test'/>
Some User Directives are available as standard. These are currently the compress
and oneliner
User Directives.
compress
User Directive
The compress
User Directive reduces whitespaces (
, \n
, \r
) in the output.
Whitespace sequences containing newlines will be replaced by a single newline.
Whitespace sequences not containing newlines will be replaced by a single space.
Whitespace prefixes and suffixes not containing newlines will be removed.
<@compress>
Hello World
</@compress>
Hello World
oneliner
User Directive
TBD
Import Directive (since 1.0.0)
Macros can be loaded by the import
directive.
<#import 'macros.fmt' as m/>
The file name parameter is the name of the import file and will be loaded by default relative to the template file.
<#import 'macros.fmt' as m/>
<@m.copyright/>
Include Directive (experimental, since 1.7.0)
Sub-templates can be loaded by the include
directive. This feature must be activated with templateBuilder.with(IncludeDirectiveFeature.ENABLED)
.
<#include 'include.fmt'/>
The file name parameter is the name of the include file and will be loaded by default relative to the template file.
<#import 'macros.fmt' parse=false/>
If the included content does not need to be processed, this can be turned off using the parse attribute. In this case, the content of the include will be included as simple text.
If all includes do not need to be parsed, automatic parsing can be deactivated centrally using the feature IncludeDirectiveFeature.PARSE_BY_DEFAULT
. In this case, the parse attribute in the directive is no longer required.
Brick Directive (since 1.6.6)
It is clearer if related templates are contained in the same document. The document can be evaluated as a template as a whole or the templates it contains can be evaluated individually.
A part that can be evaluated individually is contained within a brick[8] directive.
<#brick 'salutation'>Hello ${customer.first} ${customer.last},</#brick>
<#brick 'signature'>
Best regards
${employee.first} ${employee.last}
${employee.title}
</#brick>
A single brick directive is evaluated with the Template#processBrick(String, Map)
method. The String
parameter is the name of the directive.
Map<String, Object> model = Map.of("customer", customer, "employee", employee);
Template template = new Configuration().buider().getTemplate("mail", mail);
String salutation = template.processBrick("salutation", model);
String signature = template.processBrick("signature", model);
Built-In Variables
Now Built-In Variable
The .now
built-in variable of Type TemplateZoneDateTime
provides the current timestamp.
Locale Built-In Variable
The .locale
built-in variable of Type TemplateLocale
provides the current locale.
Country Built-In Variable
The .country
built-in variable of Type TemplateString
provides the current locales country code.
Language Built-In Variable
The .laguage
built-in variable of Type TemplateString
provides the current locales language code.
Version Built-In Variable
The .version
built-in variable of Type TemplateVersion
provides the current FreshMarker version.
Random Built-In Variable
The
|
The .random
built-in variable of Type TemplateRandom
can be used to generate random value via several built-ins.
Expressions
Expressions are an important part of template evaluation. They are evaluated within Conditional Directives and Interpolations.
The expressions are based on Java expressions, but have some differences. The majority of the deviations result from the basic implementation decisions for FreshMarker as a template engine.
As far as practicable, calculations should not take place within the template engine but in the external application. Basically, expressions should be used to make decisions in the Conditional Directives and to influence the output of Interpolations.
For this reason, FreshMarker does not have any bit shift operators, for example. Variables are assigned values within the template via the Var and Set Directives, so FreshMarker does not have an assignment operator or compound operators like Java.
Literal Expressions
TBD
Inline Sequences
TBD
Variables
TBD
Method Invocation
TBD
Conjunctions
Operator | Name | Description | Example |
---|---|---|---|
|
Logical And Operator |
evaluates both operands |
|
|
Unicode Logical And Operator |
evaluates both operands |
|
|
Conditional And Operator |
evaluates only the first operand, if it evaluates to false |
|
Disjunctions
Operator | Name | Description | Example |
---|---|---|---|
|
Logical Or Operator |
evaluates both operands |
|
|
Unicode Logical Or Operator |
evaluates both operands |
|
|
Conditional Or Operator |
evaluates only the first operand, if it evaluates to true |
|
|
Logical XOR Operator |
evaluates both operands |
|
|
Unicode Logical XOR Operator |
evaluates both operands |
|
Range Operator
TBD
Equality Operators
This operator works only on FreshMarkers primitive types. There is no Equality defined on Hashes, Sequences or Ranges.
Operator | Name | Example |
---|---|---|
|
Equals Operator |
|
|
Not Equals Operator |
|
|
Unicode Not Equals Operator |
|
Relational Operators
Since FreshMarker 1.6.3 model types can overload these operators. |
Numerical Comparison Operators
Operator | Name | Example |
---|---|---|
|
Less Operator |
|
|
Less Equals Operator |
|
|
Unicode Less Equals Operator |
|
|
Greater Operator |
|
|
Greater Equals Operator |
|
|
Unicode Greater Equals Operator |
|
The greater > and the greater equals >= operator must be surrounded by parenthesis in directive expression to distinguish it from the directive closing symbol > .
|
Version Comparison Operators
These operators are in experimental stage. |
Operator | Name | Example |
---|---|---|
|
Less Operator |
|
|
Less Equals Operator |
|
|
Unicode Less Equals Operator |
|
|
Greater Operator |
|
|
Greater Equals Operator |
|
|
Unicode Greater Equals Operator |
|
Multiplicative Operators
Since FreshMarker 1.6.3 model types can overload these operators. |
Operator | Name | Example |
---|---|---|
|
Multiplication Operator |
|
|
Unicode Multiplication Operator |
|
|
Division Operator |
|
|
Unicode Division Operator |
|
|
Remainder Operator |
|
Additive Operators
Since FreshMarker 1.6.3 model types can overload these operators. In addition to the additive operators for the numeric types and the string concatenation, there are some overloaded operators for date and period. |
Operator | Name | Example | Description |
---|---|---|---|
|
String Concatenation Operator |
|
|
|
Numerical Addition Operator |
|
|
|
Numerical Subtraction Operator |
|
|
|
Temporal Addition Operator |
|
add the period to the given date. This operation is not a commutativity. (experimental) |
|
Temporal Subtraction Operator |
|
subtract the period from the given date. This operation is not a commutativity. (experimental) |
|
Temporal Addition Operator |
|
add the amount of days to the given date. This operation is not a commutativity. (experimental) |
|
Temporal Subtraction Operator |
|
subtract the amount of days from the given date. This operation is not a commutativity. (experimental) |
|
Period Addition Operator |
|
add the period to the given period. (experimental) |
|
Period Subtraction Operator |
|
subtract the period from the given period. (experimental) |
|
Period Addition Operator |
|
add the amount of days to the given period. This operation is not a commutativity. (experimental) |
|
Period Subtraction Operator |
|
subtract the amount of days from the given period. This operation is not a commutativity. (experimental) |
|
Year Addition Operator |
|
add the amount of years to the given year. This operation is not a commutativity. (experimental) |
|
Year Subtraction Operator |
|
subtract the amount of years from the given year. This operation is not a commutativity. (experimental) |
|
List Concatenation Operator |
|
Concatenate the lists to a new list. (experimental) |
Negation
Operator | Description | Example |
---|---|---|
|
Logical Not Operator |
|
|
Unicode Logical Not Operator |
|
Unary Numerical Operators
Operator | Name | Example |
---|---|---|
|
Unary Plus Operator |
|
|
Unary Minus Operator |
|
Default Operator
Returns true
if the operant is not NULL
.
Operator | Description | Example |
---|---|---|
|
Default Operator |
|
|
Default Operator with parameter |
|
Dot Key Operator
TBD
Dynamic Key Operator
Slicing
TBD
Built-In Operator
TBD
Existence Operator
Returns true
if the operant is not NULL
.
Operator | Description | Example |
---|---|---|
|
Existence Operator |
|
|
Unicode Existence Operator |
|
Customization
Customization by Configuration
Register Simple Mapping
The registerSimpleMapping
method assigns a String
mapping to a class. If no mapping is specified, the toString
method of the class is used.
java.util.BitSet
Configuration config = new Configuration();
configuration.registerSimpleMapping(BitSet.class, x -> {
BitSet bitSet = (BitSet)x;
StringBuilder builder = new StringBuilder();
for (int i = 0, n = bitSet.length(); i < n; i++) {
builder.append(bitSet.get(i) ? "●" : "○").append("\u2009");
}
return builder.toString();
});
The example Mapping generates the String ○ ● ○ ● ● ○ ○ ● ●
for the BitSet 010110011
.
Customization by TemplateBuilder
TBD
With Features (since 1.7.0)
The with
and without
methods can be used to modify the behaviour of FreshMarker.
The parameter of the methods is a feature constant to activate or deactivate the corresponding feature.
Include Directive Features
These features are controlled by constants in the IncludeDirectiveFeature
class.
Feature | Description | Initial enabled |
---|---|---|
ENABLED |
Feature that determines whether include directives are evaluated. |
|
LIMIT_INCLUDE_LEVEL |
Feature that determines whether the level of includes is limited. |
|
IGNORE_LIMIT_EXCEEDED_ERROR |
Feature that determines whether the exceeded level of includes is an error. |
|
PARSE_BY_DEFAULT |
Feature that determines whether the content of include directives is parsed by default. |
|
Switch Directive Features
These features are controlled by constants in the SwitchDirectiveFeature
class.
Feature | Description | Initial enabled |
---|---|---|
ALLOW_ONLY_CONSTANT_CASES |
Feature that determines whether case expressions must be constants. |
|
ALLOW_ONLY_CONSTANT_ONS |
Feature that determines whether on expressions must be constants. |
|
ALLOW_ONLY_EQUAL_TYPE_CASES |
Feature that determines whether all case expressions must contain constants from the same type. |
|
ALLOW_ONLY_EQUAL_TYPE_ONS |
Feature that determines whether all on expressions must contain constants from the same type. |
|
Builtin Handling Features (since 1.7.4)
These features are controlled by constants in the BuiltinHandlingFeature
class.
Feature | Description | Initial enabled |
---|---|---|
IGNORE_OPTIONAL_EMPTY |
Feature that ignores errors due empty optionals on built-ins. |
|
IGNORE_NULL |
Feature that ignores errors due null values on built-ins. |
|
With Clock (since 1.6.9)
The withClock
method can be used to create a DefaultTemplateBuilder
that generates templates with a modified clock. This allows templates to generate an output as it would have looked at a different time. This applies in particular to the built-in variable .now
.
Configuration config = new Configuration();
Instant instant = ZonedDateTime.of(1968, 8, 24, 12, 30, 0, 0,ZoneOffset.UTC).toInstant();
TemplateBuilder builder = config.builder().withClock(Clock.fixed(instant, ZoneOffset.UTC));
With Locale
TBD
Configuration config = new Configuration();
TemplateBuilder builder = config.builder().withLocale(Locale.GERMANY);
With Zone-ID
TBD
Configuration config = new Configuration();
TemplateBuilder builder = config.builder().withZoneId(ZoneId.of("Europe/Berlin"));
With Output-Format
TBD
Configuration config = new Configuration();
TemplateBuilder builder = config.builder().withOutputFormat(StandardOutputFormats.ADOC);
Custom Formatter
The default formatting for number and temporal values can be changed with the
Configuration#registerFormatter(String, String)
method.
The first parameter type
specifies the value type number
, zoned-date-time
(for Instant
and ZonedDateTime
), date-time
(for LocalDateTime
), date
(for LocalDate
) and time
(for LocalTime
). The second parameter is a pattern of DecimalFormatter
or DateTimeFormatter
.
Configuration config = new Configuration();
config.registerFormatter("number", "#.00");
TemplateBuilder templateBuilderWithNumber = config.builder();
config.registerFormatter("date", "dd. MM. yyyy");
config.registerFormatter("time", "HH:mm");
TemplateBuilder templateBuilderWithNumberAndTemporal = config.builder();
TBD
Custom String Types
Unknown Java classes are usually treated like beans. However, this is not desired in many cases. If the ToString representation of an object is to be used in the output, its class can be registered via registerSimpleMapping(Class<?> type)
for this purpose.
Alternatively, the method registerSimpleMapping(Function<Object, String> mapping)
can be used if further adjustments are to be made.
toString
methodConfiguration config = new Configuration();
config.registerSimpleMapping(LeitwegId.class);
TemplateBuilder templateBuilder = config.builder();
Template template = templateBuilder.getTemplate("id", "ID: ${id}!");
LeitwegId leitwegId = LeitwegId.parse("04011000-1234512345-06");
String resullt = template.process(Map.of("id", leitwegId));
// ID: 04011000-1234512345-06!
Function
Configuration config = new Configuration();
config.registerSimpleMapping(LeitwegId.class, lid -> "<<" + lid + ">>");
TemplateBuilder templateBuilder = config.builder();
Template template = templateBuilder.getTemplate("id", "${id}!");
LeitwegId leitwegId = LeitwegId.parse("04011000-1234512345-06");
String resullt = template.process(Map.of("id", leitwegId));
// ID: <<04011000-1234512345-06>>!
Custom OutputFormat
TBD
default TemplateString escape(TemplateString value) {
return value;
}
default TemplateString comment(TemplateString value) {
return TemplateString.EMPTY;
}
}
Custom primitive Model Types
Evaluate Value
TBD
Type Mapper
TBD
Operator Overloading
TBD
Custom Plug-Ins
The Plug-in mechanism is the core of FreshMarker's modular extension concept. Plug-ins can be used to add new types, formatters, functions, user directives, built-ins and mappers.
It is possible to create completely new template types or to add further features to existing ones.
In addition to the existing plug-ins for the FreshMarker basic types, you can also develop your own Plug-ins.
Plug-ins can be registered with FreshMarker via the ServiceLoader
mechanism or via the Configuration#registerPlugin
method.
public interface PluginProvider {
default void registerFeature(TemplateFeatures features) {
}
default void registerMapper(Map<Class<?>, Function<Object, TemplateObject>> mapper) {
}
default void registerFormatter(Map<Class<? extends TemplateObject>, Formatter> formatter) {
}
default void registerFormatter(Map<Class<? extends TemplateObject>, Formatter> formatter, FeatureSet featureSet) {
registerFormatter(formatter);
}
default void registerBuildIn(Map<BuiltInKey, BuiltIn> builtIns) {
}
default void registerBuildIn(Map<BuiltInKey, BuiltIn> builtIns, FeatureSet featureSet) {
registerBuildIn(builtIns);
}
default void registerTemplateObjectProvider(List<TemplateObjectProvider> providers) {
}
default void registerUserDirective(Map<String, UserDirective> directives) {
}
default void registerFunction(Map<String, TemplateFunction> functions) {
}
default void registerBuiltInVariableProviders(Map<String, Function<ProcessContext, TemplateObject>> providers) {
}
}
TBD
Custom Features
Every Custom Plug-in can add its own features to the Configuration
default void registerFeature(TemplateFeatures features) {
}
TBD