Freemarker: Transform lists

Freemarker: Transform lists

The following are the key points described in this article:

  • What is Freemarker?
  • How does Freemarker works?
  • How to transform Lists?

What is Freemarker?

Freemarker is a Java Template Engine, it replaces marker strings by dynamical content. The most common use case is generating html in web applications, but Freemarker is also used to generate email message content, source code and json.

How does Freemarker works?

Freemarker replaces variables passed by a Map to the template engine, a simple example looks like this:

<html>
	<head>
    	<title>${title}</title>
	</head>
	<body>
		<h1>${heading}</h1>
		<ul>
		<#list toc as item>
    		<li>${item}</li>
		</#list>
		</ul>
	</body>
</html>

Variables are set programmatically to

Map<String, Object> params = new HashMap<>();
params.put("title", "Demo");
params.put("heading", "Hello World");
params.put("content", Arrays.asList("Books", "Cars"));

After the template is processed the result is:

<html>
	<head>
    	<title>Demo</title>
	</head>
	<body>
		<h1>Hello World</h1>
		<ul>
    		<li>Books</li>
    		<li>Cars</li>
		</ul>
	</body>
</html>

How to transform Lists?

The example above works fine if the the given content strings have only one language, but normally language keys are used to render the content for different target languages. One solution to do this with Freemarker is to use a custom TemplateMethod object:

import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateScalarModel;

import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class LanguageResolver implements TemplateMethodModelEx {

    private static final Map<String, String> langMapping = new HashMap<String, String>() {{
        put("books", "Bücher");
        put("cars", "Autos");
        put("heading", "Hallo Template!");
        put("title", "Demo");
    }};


    public Object exec(List arguments) throws TemplateModelException {
        if(arguments.size() >= 1 && arguments.size() <= 2) {
            TemplateScalarModel value = (TemplateScalarModel)arguments.get(0);
            return langMapping.get(value.getAsString());
        } else {
            throw new TemplateModelException("Wrong arguments!");
        }
    }
}

This method can passed like any other parameter to the template Engine, note in the resolver uses only one language set (DE). You can add any other language set and pass the regarding language selector as a constructor parameter.

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;

import java.io.File;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class FreemarkerDemo {

    public static void main(String[] args) throws Exception {
        Configuration cfg = new Configuration();
        cfg.setDirectoryForTemplateLoading(new File("/templates"));
        cfg.setDefaultEncoding("UTF-8");
        cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);

        Template template = cfg.getTemplate("demo.ftl");

        Map<String, Object> params = new HashMap<>();
        params.put("elements", Arrays.asList("cars", "books"));
        params.put("resolveCaption", new LanguageResolver());

        StringWriter resultWriter = new StringWriter();

        template.process(params, resultWriter);

        System.out.println(resultWriter.toString());
    }
}

The template itself looks a little more complex, for simple captions we are adding only the method call and the key as a simple string parameter. For the content list a little more work is necessary. We are processing each element of the list and each translated item is assigned to a new list called items . After the translation the whole item list is sorted by adding ?sort  and then printed out:

<html>
	<head>
		<title>${resolveCaption("title")}</title>
	</head>
	<body>
		<h1>${resolveCaption("heading")}</h1>
		<ul>
		<#assign items = [] />
		<#if elements??>
			<#list elements as element>
				<#assign items = items + [resolveCaption(element)] />
			</#list>
		</#if>
		<#list items?sort as item>
			<li>${item}</li>
		</#list>
		</ul>
	</body>
</html>

The result is the same as without the language key processing solution, but with different language strings and different order of the elements as for the english language set:

<html>
	<head>
		<title>Demo</title>
	</head>
	<body>
		<h1>Hallo Template!</h1>
		<ul>
			<li>Autos</li>
			<li>Bücher</li>
		</ul>
	</body>
</html>