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>2.0.0-SNAPSHOT</version>
</dependency>
implementation 'de.schegge:freshmarker:2.0.0-SNAPSHOT'
implementation("de.schegge:freshmarker:2.0.0-SNAPSHOT")
ivy"de.schegge:freshmarker:2.0.0-SNAPSHOT"
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 model should 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?upper_case}
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 or by the Extension API.
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 or by the Extension API.
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.
<h1>${title}</h1>
Map<String, Object> model = Map.of("title", "<script>alert("hallo")</script>");
<h1><script>alert("hallo")</script></h1>
<h1><script>alert("hallo")</script></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 |
Custom output formats can be registered to the Configuration
by the registerOutputFormat
method or by the Extension API.
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 employees as e>${e.firstname}.${e.lastname}@${company.domain}</#list>
""");
Map<String, Object> reductionMap = Map.of("company", new Company("schegge.de"));
Template reducedTemplate = template.reduce(reductionMap);
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.
Although the replaced variables do not have to be passed to the Best Practices with Partial Reduction Data
|
Partial Reduction works at the level of directives and interpolations. The reduction is performed differently depending on the type of directive.
The special nature of the Default and Exist Operators
Partial Reduction evaluates all expressions based on the available data. Where the evaluation can be carried out in full, a reduction is possible; where the evaluation runs into an error because data is missing, no reduction can take place.
The default and exist operators have a special quality. Their mode of operation includes the reaction to non-existent data. Therefore, only their reactions to existing data can be taken into account in the reduction. The missing values are only not specified in the reduction, the normal evaluation can provide these values later. Therefore, evaluating NULL values during reduction is considered an error.
Reducing Interpolations
Partial reduction replaces interpolations with text fragments if the expression can be fully evaluated within the interpolation.
TemplateBuilder templateBuilder = configuration.builder();
Template template = templateBuilder.getTemplate("test", """"
${company.domain?upper_case}
""");
Template reducedTemplate = template.reduce("company", new Company("schegge.de"));
In this example, the interpolation is replaced by the text SCHEGGE.DE
.
Reducing Conditionals
The conditional directives If and Switch are reduced by replacing their fragment with the block whose condition is fulfilled. If an error occurs during the evaluation of a condition because data is missing, the conditional directive is not replaced.
TemplateBuilder templateBuilder = configuration.builder();
Template template = templateBuilder.getTemplate("test", """"
<#if company.domain??>
${e.firstname}.${e.lastname}@${company.domain}
<#else>
No email address
</#if>
""");
Template reducedTemplate = template.reduce("company", new Company("schegge.de"));
In this example, the If Directive can be replaced by ${e.firstname}.${e.lastname}@schegge.de
because the expression company.domain??
could be evaluated.
List Unrolling (since 1.10.0)
Partial Reduction on List Directives is executed by unrolling the loops. Unrolling is used by default only if there is a maximum of five entries in the list.
This feature has to be activated with ReductionFeature.UNFOLD_LIST
. See Partial Reduction Features how to change the maximum of five entries for unrolling.
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.
Types
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 |
Character |
|
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 |
Random |
|
yes |
The types File, Path and Random are part of the FreshMarker File respectively FreshMarker Random |
Primitive Types
FreshMarker distinguishes between primitive and non-primitive types.
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.
|
Other types, such as ranges, slices, sequences and hashes are only permitted in expressions.
Literals of primitive types can be used for Null, String, Boolean, Integer and Double in the templates.
Type |
Examples |
Null |
null |
String |
'test', '', "test", "" |
Integer |
1234, 42 |
Double |
12.34, 42.0 |
Sequences
Sequences represent all data structures that implement java.util.List
.
The elements of sequences can be accessed via the hash operator sequence[index]
or the slice operator.
sequence[42]
sequence[index] // index is a variable coatining the number 42
The expression index can be a value between 0
and the length of the sequence. Other values lead to an error.
It is possible to use literals of sequences in templates, whereby the elements are written comma-separated between square brackets.
['a','b','c']
[1,3,5,7,9]
Hashes
Hashes represent data structures in which a data element is accessed via a String
key. This includes all implementations of java.util.Map
that define a String
key.
FreshMarker also supports Java Beans and Records as Hash. In this case, the data elements are accessed via the attribute names.
The elements can be accessed via the dot operator hash.name
or the hash operator hash['name']
.
hash.value
hash['value']
hash[key] // key is a variable coatining the string 'value'
It is possible to use literals of hashes in templates, whereby the key-value pairs are written comma-separated between curly brackets.
{ 'firstname': 'Jens', 'lastname': 'Kaiser' }
{ 'boolean': true, 'integer': 42 }
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.
Ranges differ from other types because they only exist as literals in the templates. Ranges cannot be injected via the model.
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
, Sequence
, String
and Number
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.
Empty Slices
Slices based on empty ranges can also be used for slicing.
However, they only ever generate an empty object of the respective target type.
This means an empty Range
, an empty Sequence
, an empty String
and NULL
as an empty Number
.
List Slices
The slice operator on List
values extracts a sublist from a given list.
The lower and upper bounds are the index in the source list for the first and last entry of the new list.
For slices without an upper bound, the last entry of the source list is the last entry of the new list.
Because the first entry of a list is zero in the Java Universe, a slice with a lower bound below zero is forbidden.
In the case of a right-limited range, the upper bound must exist in the source list. Otherwise, a processing error will occur.
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
To imitate the behavior of a Java sublist with an exclusive upper bound, a right-limited range with an exclusive upper bound can be used. Example
List slice with exclusive upper bound
|
Range Slices
The slice operator on Range
values extracts a range form a given range.
For slices without an upper bound, the upper bound of a limited source range is the upper bound of the new range.
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
The slice operator on String
values extracts a substring from a given string.
The lower and upper bounds are the index in the source string for the first and last character of the new string.
For slices without an upper bound, the last character of the source string is the last character of the new string.
Because the first index of a string is zero in the Java Universe, a slice with a lower bound below zero is forbidden.
In the case of a right-limited slice, the upper bound must exist in the source string. Otherwise, a processing error will occur.
String
slices${'abraxas'[1..4]} // => brax
${'abraxas'[1..]} // => braxas
${'abraxas'[4..1]} // => xarb
To imitate the behavior of a Java substring with an exclusive upper bound, a right-limited range with an exclusive upper bound can be used. Example
String slice with exclusive upper bound
|
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.
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, the nul-safe built-in operator ?!
can be used.
TemplateBuilder builder = new Configuration().builder();
Template template = builder.getTemplate("test", "Hello ${example?!upper_case?!lower_case!'World'}!");
assertEquals("Hello World", template.process(Map.of()));
=== Log Built-In
The special built-in log
can be used to inspect the current value in an expression. The built-in logs the current content of the expression to the logger builtin.logging
and returns the current value.
example?log?upper_case?log?lower_case?log
This expression produces the following log.
20:25:05 [main] DEBUG builtin.logging -- input:1:3 'example' => World
20:25:05 [main] DEBUG builtin.logging -- input:1:3 'example?log?upper_case' => WORLD
20:25:05 [main] DEBUG builtin.logging -- input:1:3 'example?log?upper_case?log?lower_case' => world
=== 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 if 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 if 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 if 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 if 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 |
---|---|
|
|
|
|
|
|
==== strip
Built-In (since 1.11.0)
This built-in removes leading and trailing Unicode whitespaces[4] from the text.
The examples use ␣
as a replacement for the whitespace.
Input | Output |
---|---|
|
|
==== strip_leading
Built-In (since 1.11.0)
This built-in removes leading Unicode whitespaces[4] from the text.
The examples use ␣
as a replacement for the whitespace.
Input | Output |
---|---|
|
|
==== strip_trailing
Built-In (1.11.0)
This built-in removes trailing Unicode whitespaces[4] from the text.
The examples use ␣
as a replacement for the whitespace.
Input | Output |
---|---|
|
|
==== strip_to_null
Built-In (since 1.11.0)
This built-in removes leading and trailing Unicode whitespaces[4] 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.[5]
Input | Output | Type |
---|---|---|
|
|
|
|
|
|
|
|
|
==== 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.[5]
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.[5]
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.[5]
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 |
---|---|
|
|
|
|
==== is_empty
Built-In (since 1.11.0)
This built-in return true
for an empty text and false
otherwise.
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 |
---|---|---|
|
|
|
|
|
|
|
|
==== left_pad
Built-In (since 1.8.1)
This built-in returns a string with the minimum length that was specified as the first parameter. If the original string is already this long or longer, the return value is equal to the original string.
The string is padded on the left with whitespaces or a character from the optional padding parameter. If the padding parameter is longer than one character, then the padding is applied cyclically.
The examples use ␣
as a replacement for the whitespace.
Interpolation | Output |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
==== right_pad
Built-In (since 1.8.1)
This built-in returns a string with the minimum length that was specified as the first parameter. If the original string is already this long or longer, the return value is equal to the original string.
The string is padded on the right with whitespaces or a character from the optional padding parameter. If the padding parameter is longer than one character, then the padding is applied cyclically.
The examples use ␣
as a replacement for the whitespace.
Interpolation | Output |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
==== center_pad
Built-In (since 1.9.0)
This built-in returns a string with the minimum length that was specified as the first parameter. If the original string is already this long or longer, the return value is equal to the original string.
The string is padded on both sides with whitespaces or a character from the optional padding parameter. If the padding parameter is longer than one character, then the padding is applied cyclically.
The examples use ␣
as a replacement for the whitespace.
Interpolation | Output |
---|---|
|
|
|
|
|
|
|
|
|
|
==== mask
Built-In (since 1.9.0)
This built-in returns a masked string with the length of the original string. Every none space character is replaced by characters from the mask pattern.
If the first parameter is a string, then it is the mask pattern. Otherwise, it is the asterisk character. If the mask pattern is longer than one character, then the mask pattern is applied cyclically.
A first number parameter is the number of unmasked characters at the end of the masked string.
Interpolation | Output |
---|---|
|
|
|
|
|
|
|
|
|
|
==== mask_full
Built-In (since 1.9.0)
This built-in returns a masked string with the length of the original string. Every character is replaced by characters from the mask pattern.
If the first parameter is a string, then it is the mask pattern. Otherwise, it is the asterisk character. If the mask pattern is longer than one character, then the mask pattern is applied cyclically.
A first number parameter is the number of unmasked characters at the end of the masked string.
Interpolation | Output |
---|---|
|
|
|
|
|
|
|
|
|
|
=== 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 de
, en
and fr
.
If you need another one or want to change a supported one put a resource file freshmarker_xx.properties
on the classpath.
It should contain all localized values of the original ones where xx
is the selected language.
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 |
---|---|
|
|
|
|
=== 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 de
, en
and fr
.
If you need another one or want to change a supported one put a resource file freshmarker_xx.properties
on the classpath.
It should contain all localized values of the original ones where xx
is the selected language.
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 |
---|---|
|
|
|
|
|
|
==== clamp
Built-In (since 1.10.0)
Clamps the value to fit between min and max. If the value is less than min, then min is returned. If the value is greater than max, then max is returned. Otherwise, the original value is returned.
Interpolation | Output |
---|---|
|
|
|
|
|
|
==== unicode
Built-In (since 1.10.0)
Convert value into the corresponding Unicode character. If the number is not an integer, it is converted appropriately. With an additional parameter, this is added to the value as an offset.
Interpolation | Output |
---|---|
|
|
|
|
|
|
|
|
=== Character Built-Ins (since 1.10.0)
==== c
(computer language) Built-In
This built-in converts a character to a string .
==== is_whitespace
Built-In
This built-in returns true
if the character is a whitespace and false
otherwise (see java.lang.Character#isWitespace
).
==== is_digit
Built-In
This built-in returns true
if the character is a digit and false
otherwise. See java.lang.Character#isDigit
).
==== is_letter
Built-In
This built-in returns true
if the character is a letter and false
otherwise (see java.lang.Character#isLetter
).
==== is_upper_case
Built-In
This built-in returns true
if the character is an uppercase character and false
otherwise (see java.lang.Character#isUpperCase
).
Interpolation | Output | Type |
---|---|---|
|
|
boolean |
|
|
boolean |
==== is_lower_case
Built-In
This built-in returns true
if the character is a lowercase character and false
otherwise (see java.lang.Character#isLowerCase
).
Interpolation | Output | Type |
---|---|---|
|
|
boolean |
|
|
boolean |
==== is_alphabetic
Built-In
This built-in returns true
if the character is an alphabetic character and false
otherwise (see java.lang.Character#isAlphabetic
).
==== is_letter
Built-In
This built-in returns true
if the character is a letter and false
otherwise (see java.lang.Character#isLetter
).
==== is_emoji
Built-In (experimental)
This built-in returns true
if the character is an emoji and false
otherwise (see java.lang.Character#isLowerCase
).
==== upper_case
Built-In
This built-in converts the character to uppercase (see java.lang.Character#toUpperCase
).
Interpolation | Output | Type |
---|---|---|
|
|
character |
|
|
character |
==== lower_case
Built-In
This built-in converts the character to lowercase (see java.lang.Character#toLowerCase
).
Interpolation | Output | Type |
---|---|---|
|
|
character |
|
|
character |
==== unicode_block
Built-In (experimental)
This built-in returns the name of the containing unicode block.
Interpolation | Output | Type |
---|---|---|
|
|
string |
|
|
string |
=== Date-Time Built-Ins
The Built-Ins in the examples operate on the java.time.Instant
, java.time.ZonedDateTime
, java.time.OffsetDateTime
,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. [6]
Interpolation | Output | Type |
---|---|---|
|
|
string |
==== at_zone
Built-In
This built-in returns a temporal at the given time zone of the value. [6]
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.[7]
Interpolation | Output | Type |
---|---|---|
|
|
year |
==== month
Built-In (since 1.7.0)
This built-in returns the month of the value.[7]
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.[7]
Interpolation | Output | Type |
---|---|---|
|
|
number |
==== easter
Built-In (since 1.8.0)
This built-in returns the date of Easter sunday in the year of the value as a date.[6]
Interpolation | Output | Type |
---|---|---|
|
|
date |
==== supports
Built-In (since 1.8.2)
This built-in returns true
for the supported temporal units, false
otherwise. The currently available temporal units YEARS
, MONTHS
, DAYS
, HOURS
, MINUTES
and SECONDS
are all supported.
For Instant values only DAYS , HOURS , MINUTES and SECONDS return true .
|
Interpolation | Output | Type |
---|---|---|
|
|
boolean |
|
|
boolean |
|
|
boolean |
=== 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.
[6]
Localizations is only supported for de
, en
and fr
.
If you need another one or want to change a supported one put a resource file freshmarker_xx.properties
on the classpath.
It should contain all localized values of the original ones where xx
is the selected language.
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. [6]
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.
[6]
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.
[6]
Interpolation | Output | Type |
---|---|---|
|
|
duration |
|
|
duration |
==== 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 |
==== easter
Built-In (since 1.8.0)
This built-in returns the date of Easter sunday in the year of the value as a date.[6]
Interpolation | Output | Type |
---|---|---|
|
|
date |
==== supports
Built-In (since 1.8.2)
This built-in returns true
for the supported temporal units, false
otherwise. From the currently available temporal units only YEARS
, MONTHS
and DAYS
are supported.
Interpolation | Output | Type |
---|---|---|
|
|
boolean |
|
|
boolean |
|
|
boolean |
=== 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. [6]
Interpolation | Output | Type |
---|---|---|
|
|
string |
==== supports
Built-In (since 1.8.2)
This built-in returns true
for the supported temporal units, false
otherwise. From the currently available temporal units only HOURS
, MINUTES
and SECONDS
are supported.
This built-in exists for time values so that all temporal types can be checked for temporal units. |
Interpolation | Output | Type |
---|---|---|
|
|
boolean |
|
|
boolean |
|
|
boolean |
=== 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 |
==== easter
Built-In (since 1.8.0)
This built-in returns the date of Easter sunday in the year of the value as a date.
Interpolation | Output | Type |
---|---|---|
|
|
date |
==== supports
Built-In (since 1.8.2)
This built-in returns true
for the supported temporal units, false
otherwise. From the currently available temporal units only YEARS
is supported.
Interpolation | Output | Type |
---|---|---|
|
|
boolean |
|
|
boolean |
|
|
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 |
==== easter
Built-In (since 1.8.0)
This built-in returns the date of Easter sunday in the year of the value as a date.
Interpolation | Output | Type |
---|---|---|
|
|
date |
==== supports
Built-In (since 1.8.2)
This built-in returns true
for the supported temporal units, false
otherwise. From the currently available temporal units only YEARS
and MONTHS
are supported.
Interpolation | Output | Type |
---|---|---|
|
|
boolean |
|
|
boolean |
|
|
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 |
==== supports
Built-In (since 1.8.2)
This built-in returns true
for the supported temporal unit, false
otherwise. For this type only MONTHS
and DAYS
are supported.
Interpolation | Output | Type |
---|---|---|
|
|
boolean |
|
|
boolean |
|
|
true |
=== 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 |
---|---|
|
|
|
|
|
|
==== size
Built-In
This built-in return the size of the text.
Interpolation | Output |
---|---|
|
|
|
|
==== is_empty
Built-In (since 1.11.0)
This built-in return true
for an empty sequence and false
otherwise.
Interpolation | 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 |
---|---|
|
|
|
|
|
|
==== size
Built-In
This built-in return the size of a limited range.
Interpolation | Output |
---|---|
|
|
|
|
==== is_empty
Built-In (since 1.11.0)
This built-in return true
for an empty range and false
otherwise.
Interpolation | Output |
---|---|
|
|
|
|
==== is_limited
Built-In (since 2.0.0)
This built-in return true
for a limited range and false
otherwise.
Interpolation | Output |
---|---|
|
|
|
|
==== is_unlimited
Built-In (since 2.0.0)
This built-in return true
for an unlimited range and false
otherwise.
Interpolation | 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.
<dependency>
<groupId>de.schegge</groupId>
<artifactId>freshmarker-file</artifactId>
<version>1.6.1</version>
</dependency>
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.
<dependency>
<groupId>de.schegge</groupId>
<artifactId>1.5.1</artifactId>
<version>1.0.1</version>
</dependency>
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 |
---|---|---|
|
|
|
=== Type checking Built-Ins (since 1.8.1)
Normally, the type of a variable does not need to be checked because the data model has been modeled correctly.
Nevertheless, it may occasionally be necessary to check the type of a variable so that it receives special treatment.
This built-ins available on all FreshMarker types (this includes your custom types too). |
==== is_null
Built-In
This built-in returns the true
is the variable is NULL
and false
otherwise.
It only exists to complete the type checks, normally you use the existence operator.
Interpolation | Output | Type |
---|---|---|
|
|
|
|
|
|
==== is_string
Built-In
This built-in returns the true
is the variable contains a string value and false
otherwise.
Interpolation | Output | Type |
---|---|---|
|
|
|
|
|
|
==== is_character
Built-In
This built-in returns the true
is the variable contains a character value and false
otherwise.
Interpolation | Output | Type |
---|---|---|
|
|
|
|
|
|
==== is_boolean
Built-In
This built-in returns the true
is the variable contains a boolean value and false
otherwise.
Interpolation | Output | Type |
---|---|---|
|
|
|
|
|
|
==== is_number
Built-In
This built-in returns the true
is the variable contains a number value and false
otherwise.
Interpolation | Output | Type |
---|---|---|
|
|
|
|
|
|
==== is_sequence
Built-In
This built-in returns the true
is the variable contains a sequence value and false
otherwise.
Interpolation | Output | Type |
---|---|---|
|
|
|
|
|
|
==== is_hash
Built-In
This built-in returns the true
is the variable contains a hash value and false
otherwise.
Interpolation | Output | Type |
---|---|---|
|
|
|
|
|
|
==== is_enum
Built-In
This built-in returns the true
is the variable contains an enum value and false
otherwise.
Interpolation | Output | Type |
---|---|---|
|
|
|
|
|
|
==== is_range
Built-In
This built-in returns the true
is the variable contains a range value and false
otherwise.
Interpolation | Output | Type |
---|---|---|
|
|
|
|
|
|
==== is_temporal
Built-In (since 1.8.2)
This built-in returns the true
is the variable contains a temporal value and false
otherwise.
A temporal value is one of the types
java.util.Date
, java.sql.Time
, java.sql.Date
,
java.time.Instant
,
java.time.ZonedDateTime
,
java.time.LocalDateTime
, java.time.LocalDate
, java.time.LocalTime
,
java.time.Year
,java.time.YearMonth
and java.time.MonthDay
.
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
.[8]
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.[8]
=== 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/Assign Directive
The Var, Set and Assign 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. The variable can be assigned later new values with the Set and Assign Directive. A variable only exists within its context.
<#var test/>
<#set test='zwei'/>
<#set test='drei'/>
The Var Directive allows the assignment of an initial value to the variable.
<#var test='eins'/>
<#set test='zwei'/>
<#set test='drei'/>
Instead of multiple Var Directives a single Var Directive with several variable definitions can be used. The individual definitions can be separated from each other by a space or a comma.
<#var test1='eins' test2='zwei'/>
<#var test3, test4/>
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.
Since version 1.9.0, it is now possible for all directives to generate a context. See [variable-scope-feature] |
<#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}
The following list of settings is supported.
Name |
Example |
Description |
locale |
|
Set the locale with a language tag. |
date_format |
|
Set the format for date values. |
time_format |
|
Set the format for time values. |
datetime_format |
|
Set the format for datetime values. |
offset_datetime_format |
|
Set the format for datetime values. |
zoned_datetime_format |
|
Set the format for datetime values. |
zone_id |
|
Set the zone-id. |
=== 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[9] 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 .random
built-in variable is included via an additional artefact.
<dependency>
<groupId>de.schegge</groupId>
<artifactId>{plugin-random-version}</artifactId>
<version>1.0.1</version>
</dependency>
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
The greater > and the greater equals >= operator must be surrounded by parenthesis in directive expression to distinguish it from the directive closing symbol > .
|
Since FreshMarker 1.6.3 model types can overload these operators.
Therefore, they are not only available for numeric types, but also for Version
, String
, Duration
, Period
and the temporal types.
Custom types can also overload all relational 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 |
|
==== String Comparison Operators (since 2.0.0)
Two String
values can be compared lexicographic with each other.
Operator | Name | Example |
---|---|---|
|
Less Operator |
|
|
Less Equals Operator |
|
|
Unicode Less Equals Operator |
|
|
Greater Operator |
|
|
Greater Equals Operator |
|
|
Unicode Greater Equals Operator |
|
==== Temporal Comparison Operators (since 2.0.0)
Two temporal values of the same type can be compared with each other.[6]
An exception to this is the type MonthDay
, as the year would be required as a context for correct comparison.
Operator | Name | Example |
---|---|---|
|
Less Operator |
|
|
Less Equals Operator |
|
|
Unicode Less Equals Operator |
|
|
Greater Operator |
|
|
Greater Equals Operator |
|
|
Unicode Greater Equals Operator |
|
==== Duration and Period Comparison Operators (since 2.0.0)
Duration
and Period
can be compared with each other values of the same type.
Operator | Name | Example |
---|---|---|
|
Less Operator |
|
|
Less Equals Operator |
|
|
Unicode Less Equals Operator |
|
|
Greater Operator |
|
|
Greater Equals Operator |
|
|
Unicode Greater Equals Operator |
|
==== Version Comparison Operators
Two versions are compared with each other according to the Semantic Versioning schema.
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
The Built-In operator calls type-specific functions, so-called Built-Is, on an expression. If the specified operator does not exist for the specific type, processing is terminated with an error.
The built-in operator is available in the default ?
and a null-safe variant ?!
.
With the null-safe variant, the processing of NULL
and Optional
values is less restrictive. Instead of an error, the built-ins return their input value here.
This processing can also be configured for the default variant using a Feature.
Operator | Description | Example |
---|---|---|
|
Built-In Operator |
|
|
Null-aware Built-In Operator |
|
==== 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
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.
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!
Alternatively, the method registerSimpleMapping(Function<Object, String> mapping)
can be used if further adjustments are to be made.
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();
});
010110011
○ ● ○ ● ● ○ ○ ● ●
=== 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
), offset-date-time
(for OffsetDateTime
), 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
=== 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. |
|
TemplateBuilder builder = new Configuration().builder()
.with(IncludeDirectiveFeature.LIMIT_INCLUDE_LEVEL, 2);
===== Switch Directive Features
Constants in the SwitchDirectiveFeature
class control these features.
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. |
|
OPTIMIZE_CONSTANT_SWITCH |
Feature that determines whether the switch optimized with a map (since 1.11.0). |
|
ERROR_ON_DUPLICATE_CASE_EXPRESSION |
Feature that determines whether a duplicate case expression results in an error (since 1.11.0) on optimized constant switch directives. |
|
===== 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 to empty optionals on built-ins. |
|
IGNORE_NULL |
Feature that ignores errors due to |
|
===== Partial Reduction Features (since 1.10.0)
These features are controlled by constants in the ReductionFeature
class.
Feature | Description | Initial enabled |
---|---|---|
UNFOLD_LIST |
Feature that allows template reduction by unrolling list directives. Feature that determines whether list directives are unrolled. Only list directives that do not exceed a configurable number of elements are unrolled. The default value is 5. |
|
MERGE_CONSTANT_FRAGMENTS |
Feature that determines whether constant fragments are merged. |
|
TemplateBuilder builder = new Configuration().builder()
.with(ReductionFeature.UNFOLD_LIST, 7);
==== 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 Date-Time Format
TBD
Configuration config = new Configuration();
TemplateBuilder builder = config.builder().withDateTimeFormat("short");
==== With Date Format
TBD
Configuration config = new Configuration();
TemplateBuilder builder = config.builder().withDateFormat("dd. MMMM yyyy");
==== With Time Format
TBD
Configuration config = new Configuration();
TemplateBuilder builder = config.builder().withTimeFormat("medium");
==== With Output-Format
TBD
Configuration config = new Configuration();
TemplateBuilder builder = config.builder().withOutputFormat(StandardOutputFormats.ADOC);
== Extension API (since 1.8.0)
=== Overview
The Extension
mechanism replaces the previous PlugInProvider
mechanism. Although the previous plug-in mechanism worked well, it shows some weaknesses when extending FreshMarker.
=== Registering Extensions
Extensions
can be registered automatically via the Java ServiceLoader
mechanism or programmatically.
==== Automatic Extension Registration
Extensions
can be registered automatically via the
Java ServiceLoader
mechanism. This allows third-party Extensions
to be automatically recognized and registered, depending on what is available in the classpath.
Specifically, a custom Extension
can be registered by specifying its fully qualified class name in a file named org.freshmarker.api.extension.Extension
in the /META-INF/services
folder in the associated JAR file.
==== Programmatic Extension Registration
Developers can register Extension
programmatically by calling the Configuration#register(Extension)
method.
When an Extension
is registered automatically, it can only be configured via template features. In contrast, when an Extension
is registered programmatically, it can be modified via constructor arguments or setter methods.
==== Extension Order
The Extension
are registered in a deterministic order. The default Extension
are registered first, followed by the automatic Extension
and then the programmatic Extension
.
=== Feature Handling
TemplateFeatureProvider
defines the API for Extensions
that wish to add new Freshmarker template features.
Template features are used to modify the behavior of FreshMarker or third-party Extensions
.
Each template feature is a constant in an enum implementing the TemplateFeature
interface. A template feature is enabled by default, this can be changed by overriding the isEnabledByDefault
method.
public enum ExampleFeature implements TemplateFeature {
FIRST, SECOND
}
public enum ExampleFeature implements TemplateFeature {
FIRST, SECOND;
boolean isEnabledByDefault() {
return this == FIRST;
}
}
The enum ExampleFeature
provides two template features. They can be evaluated via a provided FeatureSet
instance.
private boolean firstEnabled;
private boolean secondDisabled;
public init(FeatureSet featureSet) {
firstEnabled = featureSet.isEnabled(ExampleFeature.FIRST);
secondDisabled = featureSet.isDisabled(ExampleFeature.SECOND);
}
Before a template feature can be used, it must first be registered with FreshMarker. There are two ways to do this.
The TemplateFeature
can be added to the constructor of the Configuration
class or a custom TemplateFeatureProvider
can be registered.
public class ExampleFeatureProvider implements TemplateFeatureProvider {
@Override
public Set<TemplateFeature> provideFeatures() {
return EnumSet.allOf(ExampleFeature.class);
}
}
After registration, the features are available for all Extension
.
In order for an Extension
to react to a feature, it must implement the Extension#init(FeatureSet)
method.
The TemplateFeatureProviders are already evaluated during registration, while the other extensions are first evaluated for the template builder and then for the template. All available features are then available for evaluation.
|
=== Additional Built-Ins
BuiltInProvider
defines the API for Extensions
that wish to add new built-ins.
Each built-in must implement the BuiltIn
interface.
public class ExampleBuiltIn implements BuiltIn {
public TemplateObject apply(TemplateObject value, List<TemplateObject> parameters, ProcessContext context) {
new TemplateString("EXAMPLE: " + string);
}
}
(value, parameters, context) -> new TemplateString("EXAMPLE: " + value);
Since each BuiltIn
is assigned to a model type and has a name, the BuiltInProvider
returns the built-ins as Register<Class<? extends TemplateObject>, String, BuiltIn>
.
The BuiltInRegister
is a Register
implementation and can be used in a custom BuiltInProvider
.
public final class ExampleBuiltInProvider implements BuilInProvider {
@Override
public Register<Class<? extends TemplateObject>, String, BuiltIn> provideBuiltInRegister() {
BuiltInRegister register = new BuiltInRegister();
register.add(TemplateString.class, "example", new ExampleBuiltIn());
return register;
}
}
=== Additional Built-In Variables
The BuiltInVariableProvider
defines the API for Extensions
that wish to add new built-in variables.
Each custom built-in variable has to implement the BuiltInVariable
interface.
public class ExampleBuiltInVariable implements BuiltInVariable {
public TemplateObject apply(ProcessContext context) {
new TemplateString("EXAMPLE");
}
}
(context) -> new TemplateString("EXAMPLE");
Since each BuiltInVariable
has a name, the BuiltInVariableProvider
returns the built-in variables as Map<String,BuiltInVariable>
.
public final class ExampleBuiltInProvider implements BuilInProvider {
public Map<String,BuiltInVariable> provideBuiltIns() {
Map.of("example", (context) -> new TemplateString("EXAMPLE"));
}
}
=== Additional User Directives
The UserDirectiveProvider
defines the API for Extensions
that wish to add new user directives.
Each custom user directive hast to implement the UserDirective
interface.
public class ExampleUserDirective implements UserDirective {
public execute(ProcessContext context, Map<String, TemplateObject> args, Fragment body) {
body.process(context);
body.process(context);
}
}
(context, args, body) -> { body.process(context); body.process(context); };
Since each UserDirective
has a name, the UserDirectiveProvider
returns the user directives as Map<String,UserDirective>
.
public final class ExampleUserDirectiveProvider implements UserDirectiveProvider {
public Map<String,UserDirective> provideUserDirectives() {
Map.of("example", new ExampleUserDirective());
}
}
TBD
=== Additional Functions
The FunctionProvider
defines the API for Extensions
that wish to add new template functions.
Each custom template function hast to implement the TemplateFunction
interface.
public class ExampleFunction implements TemplateFunction {
public TemplateObject execute(ProcessContext context, List<TemplateObject> args) {
return TemplateNumber.of(args.size());
}
}
(context, args) -> TemplateNumber.of(args.size());
Since each TemplateFunction
has a name, the FunctionProvider
returns the user directives as Map<String,TemplateFunction>
.
public final class ExampleFunctionProvider implements UserDirectiveProvider {
public Map<String,TemplateFunction> provideFunctions() {
Map.of("example", (context, args) -> TemplateNumber.of(args.size()));
}
}
=== Additional Formatter
The FormatterProvider
defines the API for Extensions
that wish to add new formatter.
TBD
=== Additional Type Mapper
The TypeMapperProvider
defines the API for Extensions
that wish to add new type mapper.
The type mappers are used in the MappingTemplateObjectProvider
to create template-model objects from Java objects.
The MappingTemplateObjectProvider
ist the first TemplateObjectProvider
in the chain of template-object providers.
Its task is to create corresponding template-model objects for the registered primitive types.
Other TemplateObjectProviders
that follow in the chain are used to process records, beans and collection and custom types.
public class ExampleTypeMapper implements TypeMapper {
public TemplateObject apply(Object o) {
Dimension d = (Dimension) o;
return new TemplateString(d.width + "x" + d.height);
}
}
value -> {
Dimension d = (Dimension) o;
return new TemplateString(d.width + "x" + d.height);
};
The parameter can be cast to the desired type in the apply
method, as the TypeMapper
is only called exactly for this type.
Since each TypeMapper
has a type, the TypeMapperProvider
returns the type-mappers as Map<Class<?>, TypeMapper>
.
public final class ExampleBuiltInProvider implements BuilInProvider {
public Map<String,BuiltInVariable> provideBuiltIns() {
Map.of(Dimension.class, new ExampleTypeMapper());
}
}
=== Additional Template-Object Provider
The TemplateObjectProviders
defines the API for Extensions
that wish to add new template-object providers.
TBD
=== Additional Output Formats
The OutputFormatProvider
defines the API for Extensions
that wish to add new output formats.
public final class OwaspOutputFormatProvider implements OutputFormatProvider {
return Map.of("OWASP", new OutputFormat() {
@Override
public TemplateString escape(TemplateString value) {
return new TemplateString(Sanitizers.FORMATTING.sanitize(value.getValue()));
}
});
}