Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to parse String HTML with Freemarker variables

I am retrieving a String value from the database which contains a mix of HTML and Freemarker syntax.

Like so:

<p>${fragment.title}</p>
<table id='resultsTable' class='material-table'>
    <tr>
        <th>Instruction</th>
        <th>Action</th>
    </tr>
Test
</table>

The following is how I access the above String in the Freemarker template:

<#include "inc/header.ftl">
<body>
<#include "inc/navigation.ftl">
<div class="container">
    <div class="row">
        <#if fragments??>
            <#list fragments as fragment>
                <div class="col-sm-6">
                    <div class="fragment">
                        ${fragment.design?html}
                    </div>
                </div>              
            </#list>
        </#if>
    </div>
</div>
</body>
<#include "inc/footer.ftl">

But the output is not quite right:

<p>${fragment.title}</p> <table id='resultsTable' class='material-table'> <tr> struction</th> </th> </tr> ddd </table>

How do I use Freemarker to parse the HTML and parse the value of ${fragment.title} at the same time?

like image 882
crmepham Avatar asked Oct 18 '25 19:10

crmepham


2 Answers

Instead of ${fragment.design?html}, use <@fragment.design?interpret />.

Related documentation:

  • ?interpret: http://freemarker.org/docs/ref_builtins_expert.html#ref_builtin_interpret

  • ?html, that you don't need here: http://freemarker.org/docs/ref_builtins_string.html#ref_builtin_html

Note that if you need to run a lot of ?interpret-s per second, and you tend to interpret the same strings again and again in your templates, you might need some custom solution that caches and reuses the Template-s made from those strings. But, in most applications, performance is not an issue with ?interpret.

BTW, you don't need that #if fragments?? if you use <#list fragments! as fragment> (note the !).

like image 107
ddekany Avatar answered Oct 20 '25 09:10

ddekany


Option one: Process your string in java controller. This way you don't need to change template

@RequestMapping("/test2")
public ModelAndView test2() throws IOException, TemplateException {
    Map<String, String> fragment = new HashMap<>();
    fragment.put("title", "Some Title");
    ModelAndView model = new ModelAndView("index", "fragment", fragment);

    //your string should go here instead of this default
    String template = "<div>${fragment.title}</div> <div>some other test</div>";
    fragment.put("design", processTemplate(model.getModel(), template));

    return model;
}

private String processTemplate(Map model, String template)
        throws IOException, TemplateException {
    Template t = new Template("TemplateFromDBName", template,
            Environment.getCurrentEnvironment().getConfiguration());
    Writer out = new StringWriter();
    t.process(model, out);
    return out.toString();
}

Template part:

<div class="fragment">
    ${fragment.design}
</div>

Option two: Create custom method expression that you can use in template.

// http://freemarker.org/docs/pgui_datamodel_method.html
public static class ProcessString implements TemplateMethodModelEx {

    public static final String PROCESS_STRING_FUNCTION_NAME = "processString";

    public TemplateModel exec(List args) throws TemplateModelException {
        if (args.size() != 1 ) throw new TemplateModelException("Wrong arguments");

        try {
            Environment env = Environment.getCurrentEnvironment();
            Template t = new Template("TemplateFromDBName", 
                    args.get(0).toString(), env.getConfiguration());

            Writer out = new StringWriter();
            t.process(env.getDataModel(), out);
            return new SimpleScalar(out.toString());
        } catch (IOException | TemplateException e) {
            e.printStackTrace();
        }

        return new SimpleScalar("");

    }
}

Register it in configuration

@Configuration
public class FreeMarkerConfig extends WebMvcConfigurerAdapter {
    @Bean
    @Primary
    public FreeMarkerConfigurer freemarkerConfig( ) {
        FreeMarkerConfigurer freeMarkerConfigurer = new FreeMarkerConfigurer();
        // some other config here

        Map properties = new HashMap();
        properties.put(ProcessString.PROCESS_STRING_FUNCTION_NAME,
                new ProcessString());

        freeMarkerConfigurer.setFreemarkerVariables(properties);
        return freeMarkerConfigurer;
    }
}

And template will look like:

<div class="fragment">
    ${processString(fragment.design)}
</div>

But controller will be clean

@RequestMapping("/test")
public ModelAndView test() throws IOException, TemplateException {
    Map<String, String> fragment = new HashMap<>();
    fragment.put("title", "Some Title");
    fragment.put("design", "<div>${fragment.title}</div><div> other test</div>");
    return new ModelAndView("index", "fragment", fragment);
}
like image 31
varren Avatar answered Oct 20 '25 07:10

varren