FreeMarker: Enumeration of Root - freemarker

I am looking to enumerate the root object in FTL (freeMarker template language). for all hash arrays, and all sequence arrays, and all standard key value objects.
I need output in JSON format.
I have found the posted code for JSON enumeration:
http://ericbrandel.com/2013/03/28/freemarker-container-to-json/
<#macro objectToJsonMacro object>
<#compress single_line=true>
<#if object?is_hash || object?is_hash_ex>
<#assign first="true">
{
<#list object?keys as key>
<#if first="false">,</#if>
<#assign value><#objectToJsonMacro object=object<key> /></#assign>
"${key}" : ${value?trim}
<#assign first="false">
</#list>
}
<#elseif object?is_enumerable>
<#assign first="true">
[
<#list object as item>
<#if first="false">,</#if>
<#assign value><#objectToJsonMacro object=item /></#assign>
${value?trim}
<#assign first="false">
</#list>
]
<#else>
"${object?trim}"
</#if>
</#compress>
</#macro>
But the code does NOT WORK (throws an error) in the latest edition of FreeMarker.
I would appreciate any help that anyone can provide. Also, I don't see a way to "get" the root object.
Thanks,
Dan Linstedt

I solved this by adding a public method to my root object which JSON encodes itself. I used Gson to marshal the object.
/**
* #return JSON serialized version of this object.
*/
public String toJson()
{
GsonBuilder gsonBuilder = new GsonBuilder();
//gsonBuilder.setPrettyPrinting();
//gsonBuilder.disableHtmlEscaping();
// .. and whatever else you need Gson to do
return gsonBuilder.create().toJson(this);
}
I didn't need it in FreeMarker, but you could access it like:
${toJson()}

Related

Keep indentation when including template with FreeMarker

When I include a template with <#include ...> directive, the content of that template is inserted starting from column 0 and not where the include tag was located. Is it possible to tell FreeMarker to respect indentation?
You could use macros and pass them the indentation as a parameter. Consider, for example, building a list of courses in YAML format, where each one has a list of students.
report.ftl:
<#include "course.ftl" parse=true>
<#include "faculty.ftl" parse=true>
<#include "student.ftl" parse=true>
<#list report as course>
<#newCourse course = course indent = 0/>
</#list>
course.ftl:
<#include "faculty.ftl" parse=true>
<#include "student.ftl" parse=true>
<#macro newCourse course indent>
<#local padding = ""?left_pad(4*indent)/>
${padding}course:
${padding} code: ${course.code}
${padding} name: ${course.name}
<#newFaculty code = "${course.faculty.code}" name = "${course.faculty.name}" indent = indent + 1/>
${padding} students:
<#list course.students as student>
<#newStudent code = "${student.code}" name = "${student.name}" indent = indent + 2/>
</#list>
</#macro>
faculty.ftl:
<#macro newFaculty code name indent>
<#local padding = ""?left_pad(4*indent)/>
${padding}faculty:
${padding} code: ${code}
${padding} name: ${name}
</#macro>
student.ftl:
<#macro newStudent code name indent>
<#local padding = ""?left_pad(4*indent)/>
${padding}- student:
${padding} code: ${code}
${padding} name: ${name}
</#macro>
pojos:
class Course {
private String code;
private String name;
private Faculty faculty;
private List<Student> students = new ArrayList<>();
}
class Faculty {
private String code;
private String name;
}
class Student {
private String code;
private String name;
}

Iterating over HashMap in Freemarker template displays map's methods

In Apache OfBiz application, I have such code in controller:
public static String runRequest(HttpServletRequest request, HttpServletResponse response) {
Map<String, Long> typesToCount = getTypesToCount();
request.setAttribute("types", typesToCount);
return HttpFinals.RETURN_SUCCESS;
}
And in freemarker template it's processed/iterated like so:
<table
<#list requestAttributes.types as key, value>
<tr>
<td>${key}</td>
<td>${value}</td>
</tr>
</#list>
</table>
On rendered html page I'm getting both actual map's string key's and map's methods names (put, remove, add etc.).
As for values they are not rendered at all the with following error:
FreeMarker template error: For "${...}" content: Expected a string or something automatically convertible to string (number, date or boolean), or "template output" , but this has evaluated to a method+sequence (wrapper: f.e.b.SimpleMethodModel)
I'm using freemarker 2.3.28
Map.entrySet() method returns a collection (Set<Map.Entry<K, V>>) of the mappings contained in this map. So we can iterate over key-value pair using getKey() and getValue() methods of Map.Entry<K, V>. This method is most common and should be used if you need both map keys and values in the loop.
Try this code to iterate through the values in FTL
<table>
<#list requestAttributes.entrySet() as requestAttribute>
<tr>
<td>${requestAttribute.getKey()}</td>
<td>${requestAttribute.getValue()}</td>
</tr>
</#list>
</table>
Basically, I managed to iterate through the map only after wrapping it in SimpleMapModel like so:
public static String runRequest(HttpServletRequest request, HttpServletResponse response) {
Map<String, Long> typesToCount = getTypesToCount();
request.setAttribute("types", new SimpleMapModel(typesToCount, new DefaultObjectWrapper())));
return HttpFinals.RETURN_SUCCESS;
}
and int ftl template:
<#list requestAttributes.types?keys as key>
<tr>
<td>${key}</td>
<td>${requestAttributes.types[key]}</td>
</tr>
</#list>
That works like that if FreeMarker is configured to use a pure BeansWrapper (as opposed to DefaultObjectWrapper) for its object_wrapper setting, and the BeansWrapper.simpleMapWrapper property is left on its default value, false. Needless to say, it's a quite problematic configuration to work with. Hopefully it isn't the default of OfBiz. Although for old frameworks this can happen, as many many years ago this was a way of working around some limitations of FreeMarker, since this way you can just use the Java API of Map. Later, the introduction of the ?api built-in has made this hack needless.

In Freemarker, how to translate each string in a list individually, then join the result

My model contains a list of two-letter language codes, eg (pseudo-code):
${info.languages} = [en, jp, mi]
I currently have mark-up in my template that formats these as a semicolon-space-separated list:
<#if info.languages??>
${info.languages?join("; ", "")}
</#if>
which gives
en; jp; mi
I'd like to show the English name for each language code in the semicolon-separated list instead. I know I can use Locale#getDisplayLanguage to do the lookup in Java, so I'm not worried about the actual translation part.
My question is how to tie this into the template while still taking advantage of the join built-in. I guess ideally I'd want to be able to chain the operators like so:
${info.languages?displayLanguage?join(", ", "")}
but it appears that the ?xyz syntax is reserved for core built-ins.
Tl;dr: Is there any way to combine a custom function with the join built-in? Or something else useful that I'm overlooking? Or is my only choice to have my custom function do the joining as well as the translation?
?join pretty much only exists for convenience, to address the most common case. In more generic cases you should use #list. For example:
<#list info.languages as lang>${my.displayLanguage(lang)}<#sep>, <#/list>
Of course if you do this on multiple places, you should probably move this snippet into a macro.
As of the ?xyz thing (they are called built-ins), yes, it's reserved for the template language.
Questions on where this sort of processing belongs aside, I figured out how to do what I described in my question.
In order to still use the ?join operator (since it really is the most common case in terms of joining the values in the list), just make sure the custom method returns a list of display names for the languages codes that were passed in.
The following works, assuming that the argument passed in is always a list:
public class DisplayLanguageMethod implements TemplateMethodModelEx {
#Override
public Object exec(List arguments) throws TemplateModelException {
if (arguments == null || arguments.isEmpty()) {
return null;
}
Object argObject = arguments.get(0);
if (argObject == null || !(argObject instanceof TemplateSequenceModel)) {
return Collections.emptyList();
}
TemplateSequenceModel argSequence = ((TemplateSequenceModel) argObject);
List<String> displayLanguages = new ArrayList<>();
for (int i = 0; i < argSequence.size(); i++) {
String languageCode = Objects.toString(argSequence.get(i), null);
String displayName = languageCode;
if (languageCode != null) {
Locale argLocale = Locale.forLanguageTag(languageCode);
if (argLocale != null) {
displayName = argLocale.getDisplayName(Locale.getDefault());
}
}
displayLanguages.add(displayName);
}
return displayLanguages;
}
}
placed into the context like this:
Map<String, Object> data = new HashMap<>();
data.put("displayLanguage", new DisplayLanguageMethod());
// get template, output writer etc as per usual
template.process(data, writer);
and then used in the template like so:
<#if info.languages??>
${displayLanguage(info.languages)?join("; ", "")}
</#if>

Freemarker parse a String as Json

Probably it's not possible,
but I would like to transform a json string in a map with freemarker
ex:
<#assign test = "{\"foo\":\"bar\", \"f\":4, \"text\":\"bla bla\"}">
and be able to get the text key from this string
Use ?eval_json (requires FreeMarker 2.3.31):
<#-- Using '...' instead of "..." for convenience: no need for \" escapes this way. -->
<#assign test = '{"foo":"bar", "f":4, "text":"bla bla"}'>
<#assign m = test?eval_json>
${m.foo} <#-- prints: bar -->
<#-- Dump the whole map: -->
<#list m as k, v>
${k} => ${v}
</#list>
Before 2.3.31, ?eval was popular for this purpose, but that actually expects FreeMarker expressions. That's a problem because it doesn't support null, or \uXXXX escapes (so parsing of such JSON will fail). Also it can be a security problem, because it supports accessing variables, and calling methods/functions, while JSON doesn't.
freemarker.sourceforge.net/docs/pgui_datamodel_method.html
in code:
// a class to parse Json, just add this method to your rendered template data
// with data.put("JsonParser", new FreemarkerJsonParser());
// or in shared variables http://freemarker.sourceforge.net/docs/pgui_config_sharedvariables.html
public class FreemarkerJsonParser implements TemplateMethodModel{
#Override
public Object exec(List args) throws TemplateModelException {
return new Gson().fromJson(s, new TypeToken<Map<String, String>>() {}.getType());((String) args.get(0));
}
}
in the template:
<#assign map = JsonParser("{\"foo\":\"bar\", \"f\":4, \"text\":\"bla bla\"}")>
${map.text}
Sounds like you need to define/implement a template that reads JSON.

In freemarker is it possible to check to see if a file exists before including it?

We are trying to build a system in freemarker where extension files can be optionally added to replace blocks of the standard template.
We have gotten to this point
<#attempt>
<#include "extension.ftl">
<#recover>
Standard output
</#attempt>
So - if the extension.ftl file exists it will be used otherwise the part inside of the recover block is output.
The problem with this is that freemarker always logs the error that caused the recover block to trigger.
So we need one of two things:
Don't call the include if the file doesn't exist - thus the need to check for file existence.
-OR-
A way to prevent the logging of the error inside the recover block without changing the logging to prevent ALL freemarker errors from showing up.
easier solution would be:
<#attempt>
<#import xyz.ftl>
your_code_here
<#recover>
</#attempt>
We've written a custom macro which solves this for us. In early testing, it works well. To include it, add something like this (where mm is a Spring ModelMap):
mm.addAttribute(IncludeIfExistsMacro.MACRO_NAME, new IncludeIfExistsMacro());
import java.io.IOException;
import java.util.Map;
import org.apache.commons.io.FilenameUtils;
import freemarker.cache.TemplateCache;
import freemarker.cache.TemplateLoader;
import freemarker.core.Environment;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
/**
* This macro optionally includes the template given by path. If the template isn't found, it doesn't
* throw an exception; instead it renders the nested content (if any).
*
* For example:
* <#include_if_exists path="module/${behavior}.ftl">
* <#include "default.ftl">
* </#include_if_exists>
*
* #param path the path of the template to be included if it exists
* #param nested (optional) body could be include directive or any other block of code
*/
public class IncludeIfExistsMacro implements TemplateDirectiveModel {
private static final String PATH_PARAM = "path";
public static final String MACRO_NAME = "include_if_exists";
#Override
public void execute(Environment environment, Map map, TemplateModel[] templateModel,
TemplateDirectiveBody directiveBody) throws TemplateException, IOException {
if (! map.containsKey(PATH_PARAM)) {
throw new RuntimeException("missing required parameter '" + PATH_PARAM + "' for macro " + MACRO_NAME);
}
// get the current template's parent directory to use when searching for relative paths
final String currentTemplateName = environment.getTemplate().getName();
final String currentTemplateDir = FilenameUtils.getPath(currentTemplateName);
// look up the path relative to the current working directory (this also works for absolute paths)
final String path = map.get(PATH_PARAM).toString();
final String fullTemplatePath = TemplateCache.getFullTemplatePath(environment, currentTemplateDir, path);
TemplateLoader templateLoader = environment.getConfiguration().getTemplateLoader();
if (templateLoader.findTemplateSource(fullTemplatePath) != null) {
// include the template for the path, if it's found
environment.include(environment.getTemplateForInclusion(fullTemplatePath, null, true));
} else {
// otherwise render the nested content, if there is any
if (directiveBody != null) {
directiveBody.render(environment.getOut());
}
}
}
}
I had this exact need as well but I didn't want to use FreeMarker's ObjectConstructor (it felt too much like a scriptlet for my taste).
I wrote a custom FileTemplateLoader:
public class CustomFileTemplateLoader
extends FileTemplateLoader {
private static final String STUB_FTL = "/tools/empty.ftl";
public CustomFileTemplateLoader(File baseDir) throws IOException {
super(baseDir);
}
#Override
public Object findTemplateSource(String name) throws IOException {
Object result = null;
if (name.startsWith("optional:")) {
result = super.findTemplateSource(name.replace("optional:", ""));
if (result == null) {
result = super.findTemplateSource(STUB_FTL);
}
}
if (result == null) {
result = super.findTemplateSource(name);
}
return result;
}
}
And my corresponding FreeMarker macro:
<#macro optional_include name>
<#include "/optional:" + name>
</#macro>
An empty FTL file was required (/tools/empty.ftl) which just contains a comment explaining its existence.
The result is that an "optional" include will just include this empty FTL if the requested FTL cannot be found.
You can use also use Java method to check file exist or not.
Java Method-
public static boolean checkFileExistance(String filePath){
File tmpDir = new File(filePath);
boolean exists = tmpDir.exists();
return exists;
}
Freemarker Code-
<#assign fileExists = (Static["ClassName"].checkFileExistance("Filename"))?c/>
<#if fileExists = "true">
<#include "/home/demo.ftl"/>
<#else>
<#include "/home/index.ftl">
</#if>
Try this to get the base path:
<#assign objectConstructor = "freemarker.template.utility.ObjectConstructor"?new()>
<#assign file = objectConstructor("java.io.File","")>
<#assign path = file.getAbsolutePath()>
<script type="text/javascript">
alert("${path?string}");
</script>
Then this to walk the directory structure:
<#assign objectConstructor = "freemarker.template.utility.ObjectConstructor"?new()>
<#assign file = objectConstructor("java.io.File","target/test.ftl")>
<#assign exist = file.exists()>
<script type="text/javascript">
alert("${exist?string}");
</script>

Resources