martes, 6 de octubre de 2015

Java Freemarker Macro to convert from Multiline Template to Single Line Output

 Use case:

 Our output file is compound of multiple long lines. Each long line presents several conditions which make their definition complex.

 Likewise many other markup languages, we'd like to be able to define those conditions and all the fields that define the line in a more readable format.

 Solution:

 Turn multiline template definition into a single line final output with a reusable generic Freemarker's macro.

 Steps:

1. Download freemarker.jar Java Engine Template library from http://freemarker.org/ and include it in your application's classpath.

2. Define your reusable macro to convert convert multiple-line template to single line output. Define next code within macros_library.ftl file (it should be reachable on classpath):



<#macro compress_lines single_line=false leading_spaces=true trailing_spaces=false >
 <#local content><#nested></#local>
 <#assign replaceRegex = "" >
 <#if leading_spaces >
  <#if (replaceRegex?length > 0) >
   <#assign replaceRegex = replaceRegex + "|" >
  </#if>
  <#assign replaceRegex = replaceRegex + "^\\s+" >
 </#if>
 <#if trailing_spaces >
  <#if (replaceRegex?length > 0) >
   <#assign replaceRegex = replaceRegex + "|" >
  </#if>
  <#assign replaceRegex = replaceRegex + "\\s+$" >
 </#if>
 <#if single_line >
  <#if (replaceRegex?length > 0) >
   <#assign replaceRegex = replaceRegex + "|" >
  </#if>
  <#assign replaceRegex = replaceRegex + "\\n|\\r" >
 </#if>
 <#if (replaceRegex?length > 0) >
${ content?replace(replaceRegex, "", "rm") }<#else><#-- <== a line feed here would be sent to the output -->
  <#-- it doesn't make much sense to use this macro then -->
${ content }
 </#if>
</#macro>


So the name of this macro is compress_lines, and it has three parameters:

single_line. Whose default value is false. It means not to convert lines into single lines. For the convenience of this entry, we will send it as true, so multiline is converted into single line.

leading_spaces. Whose default value is true. If this parameter is set to true, macro will remove all the leading spaces on each line so your indentation serves only for readability purposes and are not included in the output.

trailing_spaces. Whose default value is false. If this parameter is set to true, macro will remove all trailing spaces on each line. This is not common, that's why its default value is set to false.

By combining all these three parameters to true, the result is a multiline-indented template, to be converted into a single line narrowed output (in our example it will be named x.ftl).



<#setting time_zone="America/Mexico_City">
<#include "macros_library.ftl" >

This is multiline content
that will be rendered as multiple lines
in the output because it's outside macro

<@compress_lines single_line=true leading_spaces=true trailing_spaces=true >

"${employee.birthDate?datetime?string("dd/MM/yyyy HH:mm:ss")}"

 ,"${employee}"
 
 ,"${position}"
 
 ,
  <#if employee.employeeDepartment?? && employee.employeeDepartment.name?? >
  "${employee.employeeDepartment.name}"
 </#if>
 ,[
 <#list employee.colleagues?keys as curColleagueName>
  "${curColleagueName}"
   <#sep>, </#sep>
 </#list>
 ]
 
</@compress_lines>



An example output would be similar to next. Let's suppose we have two employees, one without any department and one with a department. It won't matter the line feeds and identation, the output enclosed by the macro will be printed in a single line without spaces:



Output:
This is multiline content
that will be rendered as multiple lines
in the output because it's outside macro

"11/05/1981 12:34:51","Ricardo","Graphic Designer",,["Roberto","Noe"]
"23/08/1984 14:23:01","Valente","Project Manager","Software Factory",["Ismael","Tom"]



 3. Previous output was ran in a Java class like next to run Freemarker template:



package com.x;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

public class X {
 private Template template;

 public static void main(String[] args) {
  @SuppressWarnings("deprecation")
  Configuration cfg = new Configuration();
  cfg.setClassForTemplateLoading(X.class, "/");
  
  template = cfg.getTemplate("x.ftl");

  StringWriter logWriter = new StringWriter();
  
  List<Employee> employees = queryEmployees();
template.process(employees, logWriter); System.out.println("Output:"); System.out.println(logWriter.toString()); } }




Although I designed compress_lines macro to be as generic as possible, have a look at the code and check if you can improve it to suit further needs.

 This entry was adapted from real world application. If there's any typo that disallows this example to be executed it will be appreciated if you let me know.

* Source code was formatted with: http://hilite.me/