1

I'm having a little bit trouble with freemarker right now. What I want to do basically in my template: iterate over a list of elements and create for each element a new file.

<#assign x=3>
<#list 1..x as i>
  ${i}
...create a new file with the output of this loop iteration...
</#list>

I did not find anything about this in the freemarker manual or google. Is there a way to do this?

Balrog
  • 47
  • 1
  • 6

3 Answers3

1

You can implement this with a custom directive. See freemarker.template.TemplateDirectiveModel, and particularly TemplateDirectiveBody. Custom directives can specify the Writer used in their nested content. So you can do something like <@output file="...">...</@output>, where the nested content will be written into the Writer you have provided in your TemplateDirectiveModel implementation, which in this case should write into the file specified. (FMPP does this too: http://fmpp.sourceforge.net/qtour.html#sect4)

ddekany
  • 29,656
  • 4
  • 57
  • 64
1

As ddekany said, you can do that implementing a directive. I have coded a little example:

package spikes;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

import freemarker.core.Environment;
import freemarker.template.Configuration;
import freemarker.template.SimpleScalar;
import freemarker.template.Template;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;

class OutputDirective implements TemplateDirectiveModel {
    
    @Override
    public void execute(
            Environment env, 
            @SuppressWarnings("rawtypes") Map params, 
            TemplateModel[] loopVars, 
            TemplateDirectiveBody body)
            throws TemplateException, IOException {
        
        SimpleScalar file = (SimpleScalar) params.get("file");
        
        FileWriter fw = new FileWriter(new File(file.getAsString()));
        body.render(fw);
        fw.flush();
    }
}

public class FreemarkerTest {   
    
    public static void main(String[] args) throws Exception {
        
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_0);
        cfg.setDefaultEncoding("UTF-8");
        
        JsonObject model = new JsonObject()
                .put("entities", new JsonArray()
                        .add(new JsonObject()
                                .put("name", "Entity1"))
                        .add(new JsonObject()
                                .put("name", "Entity2")));
        
        Template template = new Template("Test", "<#assign model = model?eval_json><#list model.entities as entity><@output file=entity.name + \".txt\">This is ${entity.name} entity\n</@output></#list>", cfg);
        
        Map<String, Object> root = new HashMap<String, Object>();
        root.put("output", new OutputDirective());
        root.put("model", model.encode());
        Writer out = new OutputStreamWriter(System.out);
        template.process(root, out);
    }
}

This will generate two files:

"Entity1.txt": This is Entity1 entity

"Entity2.txt": This is Entity2 entity

:-)

0

You cannot do this using only FreeMarker. Its idea is to produce the single output stream from your template. It doesn't even care whether you will save the result to file, pass directly to TCP socket, store in the memory as string or do anything else.

If you really want to achieve this, you have to handle file separation by yourself. For example, you can insert special line like:

<#assign x=3>
<#list 1..x as i>
  ${i}
%%%%File=output${i}.html
...
</#list>

After that you should post-process FreeMarker output by yourself looking for the lines started with %%%%File= and create a new file at this point.

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334